Skip to content

Abc

InterpreterABC dataclass

InterpreterABC(
    dialects: ir.DialectGroup,
    *,
    max_depth: int = 800,
    max_python_recursion_depth: int = 131072,
    debug: bool = False
)

Bases: ABC, Generic[FrameType, ValueType]

__eval_lock class-attribute instance-attribute

__eval_lock: bool = field(
    default=False, init=False, repr=False
)

Lock for the eval method.

debug class-attribute instance-attribute

debug: bool = field(default=False, kw_only=True)

Enable debug mode.

dialects instance-attribute

dialects: DialectGroup

The dialects this interpreter supports.

keys class-attribute

keys: tuple[str, ...]

The name of the interpreter to select from dialects by order. First matching key will be used.

max_depth class-attribute instance-attribute

max_depth: int = field(default=800, kw_only=True)

The maximum depth of the interpreter stack.

max_python_recursion_depth class-attribute instance-attribute

max_python_recursion_depth: int = field(
    default=131072, kw_only=True
)

The maximum recursion depth of the Python interpreter.

registry class-attribute instance-attribute

registry: dict[Signature, BoundedDef] = field(
    init=False, compare=False
)

The registry of implementations

state class-attribute instance-attribute

state: InterpreterState[FrameType] = field(
    init=False, compare=False
)

The interpreter state.

symbol_table class-attribute instance-attribute

symbol_table: dict[str, Statement] = field(
    init=False, compare=False
)

The symbol table of the interpreter.

void class-attribute instance-attribute

void: ValueType = field(init=False)

What to return when the interpreter evaluates nothing.

call

call(
    node: ir.Statement | ir.Method,
    *args: ValueType,
    **kwargs: ValueType
) -> tuple[FrameType, ValueType]

Call a given callable node with the given arguments.

This method is used to call a node that has a callable trait and a corresponding implementation of its callable region execution convention in the interpreter.

Parameters:

Name Type Description Default
node Statement | Method

the callable node to call

required
args ValueType

the arguments to pass to the callable node

()
kwargs ValueType

the keyword arguments to pass to the callable node

{}

Returns:

Type Description
tuple[FrameType, ValueType]

tuple[FrameType, ValueType]: the frame and the result of the call

Raises:

Type Description
InterpreterError

if the interpreter is already evaluating

StackOverflowError

if the maximum depth of the interpreter stack is reached

Source code in src/kirin/interp/abc.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def call(
    self, node: ir.Statement | ir.Method, *args: ValueType, **kwargs: ValueType
) -> tuple[FrameType, ValueType]:
    """Call a given callable node with the given arguments.

    This method is used to call a node that has a callable trait and a
    corresponding implementation of its callable region execution convention in
    the interpreter.

    Args:
        node: the callable node to call
        args: the arguments to pass to the callable node
        kwargs: the keyword arguments to pass to the callable node

    Returns:
        tuple[FrameType, ValueType]: the frame and the result of the call

    Raises:
        InterpreterError: if the interpreter is already evaluating
        StackOverflowError: if the maximum depth of the interpreter stack is reached
    """
    if isinstance(node, ir.Method):
        return self.__call_method(node, *args, **kwargs)

    with self.new_frame(node) as frame:
        return frame, self.frame_call(frame, node, *args, **kwargs)

eval_context

eval_context()

Context manager to set the recursion limit and initialize the interpreter.

This context manager sets the recursion limit to the maximum depth of the interpreter stack. It is used to prevent stack overflow when calling recursive functions.

Source code in src/kirin/interp/abc.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@contextmanager
def eval_context(self):
    """Context manager to set the recursion limit and initialize the interpreter.

    This context manager sets the recursion limit to the maximum depth of
    the interpreter stack. It is used to prevent stack overflow when calling
    recursive functions.
    """
    if self.__eval_lock:
        raise InterpreterError(
            f"Interpreter {self.__class__.__name__} is already evaluating, "
            f"consider calling the bare `method.code` instead of the method"
            f" or use the bare `frame_call`/`frame_eval` methods"
        )

    self.__eval_lock = True
    self.initialize()
    current_recursion_limit = sys.getrecursionlimit()
    sys.setrecursionlimit(self.max_python_recursion_depth)
    try:
        yield self.max_python_recursion_depth
    except Exception as e:
        # NOTE: insert the interpreter state into the exception
        # so we can print the stack trace
        setattr(e, KIRIN_INTERP_STATE, self.state)
        raise e
    finally:
        self.__eval_lock = False
        sys.setrecursionlimit(current_recursion_limit)

eval_fallback

eval_fallback(
    frame: FrameType, node: ir.Statement
) -> StatementResult[ValueType]

The fallback implementation of statements.

This is called when no implementation is found for the statement.

Parameters:

Name Type Description Default
frame FrameType

the current frame

required
stmt

the statement to run

required

Returns:

Name Type Description
StatementResult StatementResult[ValueType]

the result of running the statement

Note

Overload this method to provide a fallback implementation for statements.

Source code in src/kirin/interp/abc.py
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
def eval_fallback(
    self, frame: FrameType, node: ir.Statement
) -> StatementResult[ValueType]:
    """The fallback implementation of statements.

    This is called when no implementation is found for the statement.

    Args:
        frame: the current frame
        stmt: the statement to run

    Returns:
        StatementResult: the result of running the statement

    Note:
        Overload this method to provide a fallback implementation for statements.
    """
    raise NotImplementedError(
        f"Missing implementation for {type(node).__name__} at {node.source}"
    )

frame_call

frame_call(
    frame: FrameType,
    node: ir.Statement,
    *args: ValueType,
    **kwargs: ValueType
) -> ValueType

Call a given callable node with the given arguments in a given frame.

This method is used to call a node that has a callable trait and a corresponding implementation of its callable region execution convention in the interpreter.

Source code in src/kirin/interp/abc.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def frame_call(
    self,
    frame: FrameType,
    node: ir.Statement,
    *args: ValueType,
    **kwargs: ValueType,
) -> ValueType:
    """Call a given callable node with the given arguments in a given frame.

    This method is used to call a node that has a callable trait and a
    corresponding implementation of its callable region execution convention in
    the interpreter.
    """
    if entry := node.get_trait(ir.EntryPointInterface):
        node = self.symbol_table[entry.get_entry_point_symbol(node)]
    trait = node.get_present_trait(ir.CallableStmtInterface)
    args = trait.align_input_args(node, *args, **kwargs)
    region = trait.get_callable_region(node)
    if self.state.depth >= self.max_depth:
        return self.recursion_limit_reached()

    ret = self.frame_call_region(frame, node, region, *args)
    if isinstance(ret, ReturnValue):
        return ret.value
    elif not ret:  # empty result or None
        return self.void
    raise InterpreterError(
        f"callable region {node.name} does not return `ReturnValue`, got {ret}"
    )

frame_call_region

frame_call_region(
    frame: FrameType,
    node: ir.Statement,
    region: ir.Region,
    *args: ValueType
) -> RegionResult

Call a given callable region with the given arguments in a given frame.

This method is used to call a region that has a callable trait and a corresponding implementation of its callable region execution convention in the interpreter.

Parameters:

Name Type Description Default
frame FrameType

the frame to call the region in

required
node Statement

the node to call the region on

required
region Region

the region to call

required
args ValueType

the arguments to pass to the region

()

Returns:

Name Type Description
RegionResult RegionResult

the result of the call

Raises:

Type Description
InterpreterError

if cannot find a matching implementation for the region.

Source code in src/kirin/interp/abc.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def frame_call_region(
    self,
    frame: FrameType,
    node: ir.Statement,
    region: ir.Region,
    *args: ValueType,
) -> RegionResult:
    """Call a given callable region with the given arguments in a given frame.

    This method is used to call a region that has a callable trait and a
    corresponding implementation of its callable region execution convention in
    the interpreter.

    Args:
        frame: the frame to call the region in
        node: the node to call the region on
        region: the region to call
        args: the arguments to pass to the region

    Returns:
        RegionResult: the result of the call

    Raises:
        InterpreterError: if cannot find a matching implementation for the region.
    """
    region_trait = node.get_present_trait(ir.RegionInterpretationTrait)
    how = self.registry.get(Signature(region_trait))
    if how is None:
        raise InterpreterError(
            f"Interpreter {self.__class__.__name__} does not "
            f"support {node} using {region_trait} convention"
        )
    region_trait.set_region_input(frame, region, *args)
    return how(self, frame, region)

frame_eval

frame_eval(
    frame: FrameType, node: ir.Statement
) -> StatementResult[ValueType]

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
node Statement

the statement to run

required

Returns:

Name Type Description
StatementResult StatementResult[ValueType]

the result of running the statement

Source code in src/kirin/interp/abc.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
def frame_eval(
    self, frame: FrameType, node: ir.Statement
) -> StatementResult[ValueType]:
    """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.

    Args:
        frame: the current frame
        node: the statement to run

    Returns:
        StatementResult: the result of running the statement
    """
    method = self.lookup_registry(frame, node)
    if method is not None:
        results = method(self, frame, node)
        if self.debug and not isinstance(results, (tuple, SpecialValue)):
            raise InterpreterError(
                f"method must return tuple or SpecialResult, got {results}"
            )
        return results
    elif node.dialect not in self.dialects:
        name = node.dialect.name if node.dialect else "None"
        dialects = ", ".join(d.name for d in self.dialects)
        raise InterpreterError(
            f"Interpreter {self.__class__.__name__} does not "
            f"support {node} using {name} dialect. "
            f"Expected {dialects}"
        )

    return self.eval_fallback(frame, node)

initialize_frame abstractmethod

initialize_frame(
    node: ir.Statement, *, has_parent_access: bool = False
) -> FrameType

Initialize a new call frame for the given callable node.

Source code in src/kirin/interp/abc.py
73
74
75
76
77
78
@abstractmethod
def initialize_frame(
    self, node: ir.Statement, *, has_parent_access: bool = False
) -> FrameType:
    """Initialize a new call frame for the given callable node."""
    ...

new_frame

new_frame(
    node: ir.Statement, *, has_parent_access: bool = False
) -> Generator[FrameType, Any, None]

Create a new frame for the given node.

This method is used to create a new call frame for the given node. The frame is pushed on the stack and popped when the context manager is exited. The frame is initialized with the given node and the given arguments.

Parameters:

Name Type Description Default
node Statement

the node to create the frame for

required
has_parent_access bool

if the frame has access to the parent frame entries (default: False)

False

Returns:

Type Description
None

Generator[FrameType, Any, None]: the frame

Source code in src/kirin/interp/abc.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
@contextmanager
def new_frame(
    self, node: ir.Statement, *, has_parent_access: bool = False
) -> Generator[FrameType, Any, None]:
    """Create a new frame for the given node.

    This method is used to create a new call frame for the given node. The
    frame is pushed on the stack and popped when the context manager
    is exited. The frame is initialized with the given node and the
    given arguments.

    Args:
        node: the node to create the frame for
        has_parent_access: if the frame has access to the parent frame entries
            (default: False)

    Returns:
        Generator[FrameType, Any, None]: the frame
    """
    frame = self.initialize_frame(node, has_parent_access=has_parent_access)
    self.state.push_frame(frame)
    try:
        yield frame
    finally:
        self.state.pop_frame()

recursion_limit_reached

recursion_limit_reached() -> ValueType

Handle the recursion limit reached.

This method is called when the maximum depth of the interpreter stack when calling a callable node is reached. By default a StackOverflowError is raised. Overload this method to provide a custom behavior, e.g. in the case of abstract interpreter, the recursion limit returns a bottom value.

Source code in src/kirin/interp/abc.py
189
190
191
192
193
194
195
196
197
198
199
200
201
def recursion_limit_reached(self) -> ValueType:
    """Handle the recursion limit reached.

    This method is called when the maximum depth of the interpreter stack
    when calling a callable node is reached. By default a `StackOverflowError`
    is raised. Overload this method to provide a custom behavior, e.g. in
    the case of abstract interpreter, the recursion limit returns a bottom
    value.
    """
    raise StackOverflowError(
        f"Interpreter {self.__class__.__name__} stack "
        f"overflow at {self.state.depth}"
    )