Advance Rewrite
Rewrite if-else control flow
In the main page, we introduce a simple food
dialect example, and described how to use kirin to define a simple compiler. In this section, we want to continue with this exampe, and consider a more compilcated rewrite pass that involves the build-in python dialect if-else control flow.
Goal
When one get really really full, not only does one nap, we also would make random decision. Here specifically, We want to rewrite the existing IfElse
statement defined in the existing py
dialect into a customize RandomBranch
statement we defined in our food dialect.
The execution of RandomBranch
, as stated in its name, randomly execute a branch each time we run it.
Define Custom RandomBranch statement
Lets start by define our RandomBranch
Statement:
from kirin.decl import statement, info
from kirin import ir, types
@statement(dialect=dialect)
class RandomBranch(ir.Statement):
name = "random_br"
traits = frozenset({ir.IsTerminator()}) # (1)!
cond: SSAValue = info.argument(types.Bool) # (2)!
then_arguments: tuple[ir.SSAValue, ...] = info.argument() # (3)!
else_arguments: tuple[ir.SSAValue, ...] = info.argument() # (4)!
then_successor: ir.Block = info.block() # (5)!
else_successor: ir.Block = info.block() # (6)!
- The
traits
field specifies that this statement is a terminator. A terminator is a statement that ends a block. In this case, theRandomBranch
statement is a terminator because it decides which block to go next. - The
cond
field specifies the condition of the branch. It is a boolean value. - The
then_arguments
field specifies the arguments that are passed to thethen_successor
block. Unlike previous examples, thethen_arguments
field is annotated withtuple[ir.SSAValue, ...]
, which means it takes a tuple ofir.SSAValue
objects (like what it means in adataclass
). - The
else_arguments
field specifies the arguments that are passed to theelse_successor
block. - The
then_successor
field specifies the block that the control flow goes to if the condition is true. - The
else_successor
field specifies the block that the control flow goes to if the condition is false.
The RandomBranch
statement is a terminator that takes a boolean condition and two tuples of arguments. However, unlike a normal if else
branching statement, it does not execute the branches based on the condition. Instead, it randomly chooses one of the branches to execute. We will implement the execution behavior of this statement in the following.
Implementation and MethodTable
Recall in the introduction of food dialect we metioned about MethodTable
. Now we have defined the statement, we will need to tell interpreter how to interprete this Statement we defined.
Let's find the FoodMethods
MethodTable that we defined and registered to food
dialect previously:
from kirin.interp import Frame, Successor, Interpreter, MethodTable, impl
@dialect.register
class FoodMethods(MethodTable):
...
Now we want to also implement the execution method and then register to this method table:
@impl(RandomBranch)
def random_branch(self, interp: Interpreter, stmt: RandomBranch, values: tuple):
frame = interp.state.current_frame()
if randint(0, 1):
return Successor(
stmt.then_successor, *frame.get_values(stmt.then_arguments)
)
else:
return Successor(
stmt.else_successor, *frame.get_values(stmt.then_arguments)
)
The random_branch
implementation randomly chooses one of the branches to execute. The return value is a Successor
object that specifies the next block to execute and the arguments to pass to the block.
Rewrite Python if else
statement to RandomBranch
Now we can define a rewrite pass that rewrites Python if else
statement to RandomBranch
statement. This is done by defining a subclass of [RewriteRule
][kirin.rewrite.RewriteRule] and implementing the rewrite_Statement
method. The RewriteRule
class is a standard Python visitor on Kirin's IR.
Here, we only need to implement the rewrite_Statement
method to rewrite the if else
statement to RandomBranch
.
from kirin.dialects import cf # (1)!
from kirin.rewrite import RewriteResult, RewriteRule # (2)!
@dataclass
class RewriteToRandomBranch(RewriteRule):
def rewrite_Statement(self, node: ir.Statement) -> RewriteResult: # (3)!
if not isinstance(node, cf.ConditionalBranch): # (4)!
return RewriteResult()
node.replace_by(
RandomBranch(
cond=node.cond,
then_arguments=node.then_arguments,
then_successor=node.then_successor,
else_arguments=node.else_arguments,
else_successor=node.else_successor,
)
)
return RewriteResult(has_done_something=True) # (5)!
- Import the control flow dialect
cf
which is what Pythonif else
statement compiles to by default in thebasic
dialect group. - Import the
RewriteRule
class from therewrite
module. - This is the signature of
rewrite_Statement
method. Your IDE should hint you the type signature so you can auto-complete it. - Check if the statement is a
ConditionalBranch
statement. If it is not, return an emptyRewriteResult
. - Replace the
ConditionalBranch
statement with aRandomBranch
statement and return aRewriteResult
that indicates the rewrite has been done. Every statement has areplace_by
method that replaces the statement with another statement.
Adding to the decorator
from kirin.ir import dialect_group
from kirin.prelude import basic_no_opt
from kirin.rewrite import Walk, Fixpoint
@dialect_group(basic_no_opt.add(dialect))
def food(self):
# some initialization if you need it
def run_pass(mt, hungry:bool=False, got_lost: bool=True): # (1)!
if drunk:
Walk(NewFoodAndNap()).rewrite(mt.code)
if got_lost:
Fixpoint(Walk(RandomWalkBranch())).rewrite(mt.code) # (2)!
return run_pass
- Lets add an extra
got_lost
option to toggle thisRandomWalkBranch()
rewrite rule. - The
Walk
will walk through the IR and apply the rule. TheFixpoint
then repeatedly walk through the IR until there is nothing to rewrite.