Skip to content

Prop

Frame dataclass

Frame(
    code: Statement,
    lino: int = 0,
    stmt: Statement | None = None,
    globals: dict[str, Any] = dict(),
    entries: dict[SSAValue, ValueType] = dict(),
    worklist: WorkList[Successor[ResultType]] = WorkList(),
    visited: dict[
        Block, set[Successor[ResultType]]
    ] = dict(),
    should_be_pure: set[ir.Statement] = set(),
    frame_is_not_pure: bool = False,
)

Bases: ForwardFrame[Result]

frame_is_not_pure class-attribute instance-attribute

frame_is_not_pure: bool = False

If we hit any non-pure statement.

should_be_pure class-attribute instance-attribute

should_be_pure: set[Statement] = field(default_factory=set)

If any ir.MaybePure is actually pure.

Propagate dataclass

Propagate(
    dialects: DialectGroup,
    *,
    fuel: int | None = None,
    debug: bool = False,
    max_depth: int = 128,
    max_python_recursion_depth: int = 8192
)

Bases: ForwardExtra[Frame, Result]

Forward dataflow analysis for constant propagation.

This analysis is a forward dataflow analysis that propagates constant values through the program. It uses the Result lattice to track the constant values and purity of the values.

The analysis is implemented as a forward dataflow analysis, where the eval_stmt method is overridden to handle the different types of statements in the IR. The analysis uses the interp.Interpreter to evaluate the statements and propagate the constant values.

When a statement is registered under the "constprop" key in the method table, the analysis will call the method to evaluate the statement instead of using the interpreter. This allows for custom handling of statements.

keys class-attribute instance-attribute

keys = ['constprop']

The name of the interpreter to select from dialects by order.

lattice class-attribute instance-attribute

lattice = Result

lattice type for the abstract interpreter.

eval_stmt

eval_stmt(
    frame: Frame, stmt: ir.Statement
) -> interp.StatementResult[Result]

Run a statement within the current frame. This is the entry point of running a statement. It will look up the statement implementation in the dialect registry, or optionally call a fallback implementation.

Parameters:

Name Type Description Default
frame FrameType

the current frame

required
stmt Statement

the statement to run

required

Returns:

Name Type Description
StatementResult StatementResult[ValueType]

the result of running the statement

Note

Overload this method for the following reasons: - to change the source tracking information - to take control of how to run a statement - to change the implementation lookup behavior that cannot acheive by overloading lookup_registry

Example
  • implement an interpreter that only handles MyStmt:
        class MyInterpreter(BaseInterpreter):
            ...
            def eval_stmt(self, frame: FrameType, stmt: Statement) -> StatementResult[ValueType]:
                if isinstance(stmt, MyStmt):
                    return self.run_my_stmt(frame, stmt)
                else:
                    return ()
    
Source code in src/kirin/analysis/const/prop.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def eval_stmt(
    self, frame: Frame, stmt: ir.Statement
) -> interp.StatementResult[Result]:
    method = self.lookup_registry(frame, stmt)
    if method is None:
        if stmt.has_trait(ir.ConstantLike):
            return self._try_eval_const_pure(frame, stmt, ())
        elif stmt.has_trait(ir.Pure):
            values = frame.get_values(stmt.args)
            if types.is_tuple_of(values, Value):
                return self._try_eval_const_pure(frame, stmt, values)

        if stmt.has_trait(ir.Pure):
            return (Unknown(),)  # no implementation but pure
        # not pure, and no implementation, let's say it's not pure
        frame.frame_is_not_pure = True
        return (Unknown(),)

    ret = method(self, frame, stmt)
    if stmt.has_trait(ir.IsTerminator) or stmt.has_trait(ir.Pure):
        return ret
    elif not stmt.has_trait(ir.MaybePure):  # cannot be pure at all
        frame.frame_is_not_pure = True
    elif (
        stmt not in frame.should_be_pure
    ):  # implementation cannot decide if it's pure
        frame.frame_is_not_pure = True
    return ret

initialize

initialize()

Initialize the interpreter global states. This method is called right upon calling run to initialize the interpreter global states.

Default Implementation

This method provides default behavior but may be overridden by subclasses to customize or extend functionality.

Source code in src/kirin/analysis/const/prop.py
50
51
52
53
def initialize(self):
    super().initialize()
    self._interp.initialize()
    return self

new_frame

new_frame(code: ir.Statement) -> Frame

Create a new frame for the given method.

Source code in src/kirin/analysis/const/prop.py
55
56
def new_frame(self, code: ir.Statement) -> Frame:
    return Frame.from_func_like(code)

run_method

run_method(
    method: ir.Method, args: tuple[Result, ...]
) -> tuple[Frame, Result]

How to run a method.

This is defined by subclasses to describe what's the corresponding value of a method during the interpretation. Usually, this method just calls run_callable.

Parameters:

Name Type Description Default
method Method

the method to run.

required
args tuple[ValueType]

the arguments to the method, does not include self.

required

Returns:

Name Type Description
ValueType tuple[FrameType, ValueType]

the result of the method.

Source code in src/kirin/analysis/const/prop.py
113
114
115
116
def run_method(
    self, method: ir.Method, args: tuple[Result, ...]
) -> tuple[Frame, Result]:
    return self.run_callable(method.code, (Value(method),) + args)