Skip to content

Interpreter

interpreter

PythonValues: TypeAlias = tuple[Any, ...] module-attribute

A tuple of result values corresponding to the results of the operation being interpreted.

The standard dialect implementations represent integer scalars with IntegerAttr and floating-point scalars with FloatAttr, carrying the SSA value's type. Index values use IntegerAttr with an i32 or i64 type according to index_bitwidth. Conditions use IntegerAttr.__bool__, which tests whether the payload is nonzero. ShapedArray and TypedPtr keep native numeric storage; scalar loads and region arguments are wrapped, and stores unwrap attribute payloads. Callers and external function implementations must use the same scalar representations.

Other types have dialect-specific representations; the interpreter dispatch itself does not impose a representation on custom implementations.

TerminatorValue: TypeAlias = ReturnedValues | Successor module-attribute

A terminator operation either yields control to the parent operation or jumps to a successor.

NonTerminatorOpImpl: TypeAlias = Callable[[_FT, 'Interpreter', OperationInvT, PythonValues], PythonValues] module-attribute

TerminatorOpImpl: TypeAlias = Callable[[_FT, 'Interpreter', OperationInvT, PythonValues], tuple[TerminatorValue, PythonValues]] module-attribute

OpImpl: TypeAlias = Callable[[_FT, 'Interpreter', OperationInvT, PythonValues], OpImplResult] module-attribute

CastImpl: TypeAlias = Callable[[_FT, _AttributeInvT0, _AttributeInvT1, Any], Any] module-attribute

AttrImpl: TypeAlias = Callable[[_FT, 'Interpreter', Attribute, AttributeInvT], Any] module-attribute

ExtFuncImpl: TypeAlias = Callable[[_FT, 'Interpreter', Operation, PythonValues], PythonValues] module-attribute

P = ParamSpec('P') module-attribute

AttributeInvNoDefaultT = TypeVar('AttributeInvNoDefaultT', bound=Attribute) module-attribute

ReturnedValues

Bases: NamedTuple

If the terminator exits the region being interpreted, such as for function returns, return these values to yield control back to the parent operation of the block.

Source code in xdsl/interpreter.py
class ReturnedValues(NamedTuple):
    """
    If the terminator exits the region being interpreted, such as for function returns,
    return these values to yield control back to the parent operation of the block.
    """

    values: PythonValues

values: PythonValues instance-attribute

Successor

Bases: NamedTuple

If the terminator jumps to another block within the region being interpreted, such as for jumps, return a block along with the values to pass as arguments.

Source code in xdsl/interpreter.py
class Successor(NamedTuple):
    """
    If the terminator jumps to another block within the region being interpreted, such
    as for jumps, return a block along with the values to pass as arguments.
    """

    block: Block
    args: PythonValues

block: Block instance-attribute

args: PythonValues instance-attribute

OpImplResult

Bases: NamedTuple

The result of interpreting an Operation. If and only if the Operation is a terminator, it must set the terminator_value.

Source code in xdsl/interpreter.py
class OpImplResult(NamedTuple):
    """
    The result of interpreting an Operation. If and only if the Operation is a terminator,
    it must set the terminator_value.
    """

    values: PythonValues
    terminator_value: TerminatorValue | None

values: PythonValues instance-attribute

terminator_value: TerminatorValue | None instance-attribute

InterpreterFunctions dataclass

Holds the Python implementations for Operations. Users should subclass this class, and define the functions to run during interpretation. For example:

@register_impls
class ArithFunctions(InterpreterFunctions):

    @impl(arith.Addi)
    def run_addi(self, interpreter: Interpreter, op: arith.Addi,
                 args: tuple[Any, ...]) -> tuple[Any, ...]:
        lhs, rhs = args
        return lhs + rhs,

The interpreter will take care of fetching the Python values associated with the operand SSAValues, and setting the return values to the appropriate OpResults.

To override the definition of an operation implementation, subclass the class to override, and redefine the functions, annotating them with @impl.

@register_impls
class DebugArithFunctions(ArithFunctions):

    @impl(arith.Addi)
    def run_addi(self, interpreter: Interpreter, op: arith.Addi,
                 args: tuple[Any, ...]) -> tuple[Any, ...]:
        lhs, rhs = args
        print(lhs, rhs, lhs + rhs)
        return lhs + rhs,

To register an implementation of a cast for UnrealizedConversionCastOp, use impl_cast, like so:

@register_impls
class ArithFunctions(InterpreterFunctions):
    @impl_cast(IntegerType, IndexType)
    def cast_integer_to_index(
        self,
        input_type: IntegerType,
        output_type: IndexType,
        value: Any,
    ) -> Any:
        # Both input and output represented by a Python `int`
        return value
Source code in xdsl/interpreter.py
@dataclass
class InterpreterFunctions:
    """
    Holds the Python implementations for Operations. Users should
    subclass this class, and define the functions to run during interpretation.
    For example:

    ``` python
    @register_impls
    class ArithFunctions(InterpreterFunctions):

        @impl(arith.Addi)
        def run_addi(self, interpreter: Interpreter, op: arith.Addi,
                     args: tuple[Any, ...]) -> tuple[Any, ...]:
            lhs, rhs = args
            return lhs + rhs,
    ```

    The interpreter will take care of fetching the Python values associated
    with the operand SSAValues, and setting the return values to the
    appropriate OpResults.

    To override the definition of an operation implementation, subclass the
    class to override, and redefine the functions, annotating them with
    `@impl`.

    ``` python
    @register_impls
    class DebugArithFunctions(ArithFunctions):

        @impl(arith.Addi)
        def run_addi(self, interpreter: Interpreter, op: arith.Addi,
                     args: tuple[Any, ...]) -> tuple[Any, ...]:
            lhs, rhs = args
            print(lhs, rhs, lhs + rhs)
            return lhs + rhs,
    ```

    To register an implementation of a cast for UnrealizedConversionCastOp, use
    `impl_cast`, like so:

    ``` python
    @register_impls
    class ArithFunctions(InterpreterFunctions):
        @impl_cast(IntegerType, IndexType)
        def cast_integer_to_index(
            self,
            input_type: IntegerType,
            output_type: IndexType,
            value: Any,
        ) -> Any:
            # Both input and output represented by a Python `int`
            return value
    ```
    """

    @classmethod
    def _impls(
        cls,
    ) -> Iterable[tuple[type[Operation], OpImpl[InterpreterFunctions, Operation]]]:
        try:
            impl_dict = getattr(cls, _IMPL_DICT_KEY)
            return impl_dict.items()
        except AttributeError as e:
            raise ValueError(f"Use `@register_impls` on class {cls.__name__}") from e

    @classmethod
    def _cast_impls(
        cls,
    ) -> Iterable[
        tuple[
            tuple[type[Attribute], type[Attribute]],
            CastImpl[InterpreterFunctions, Attribute, Attribute],
        ]
    ]:
        try:
            impl_dict = getattr(cls, _CAST_IMPL_DICT_KEY)
            return impl_dict.items()
        except AttributeError as e:
            raise ValueError(f"Use `@register_impls` on class {cls.__name__}") from e

    @classmethod
    def _attr_impls(
        cls,
    ) -> Iterable[
        tuple[
            type[Attribute],
            AttrImpl[InterpreterFunctions, Attribute],
        ]
    ]:
        try:
            impl_dict = getattr(cls, _ATTR_IMPL_DICT_KEY)
            return impl_dict.items()
        except AttributeError as e:
            raise ValueError(f"Use `@register_impls` on class {cls.__name__}") from e

    @classmethod
    def _ext_impls(
        cls,
    ) -> Iterable[tuple[str, ExtFuncImpl[InterpreterFunctions]]]:
        try:
            impl_dict = getattr(cls, _EXT_FUNC_DICT_KEY)
            return impl_dict.items()
        except AttributeError as e:
            raise ValueError(f"Use `@register_impls` on class {cls.__name__}") from e

    @classmethod
    def _callable_impls(
        cls,
    ) -> Iterable[tuple[type[Operation], OpImpl[InterpreterFunctions, Operation]]]:
        try:
            impl_dict = getattr(cls, _CALLABLE_IMPL_DICT_KEY)
            return impl_dict.items()
        except AttributeError as e:
            raise ValueError(f"Use `@register_impls` on class {cls.__name__}") from e

__init__() -> None

Interpreter dataclass

An extensible interpreter, initialised with a Module to interpret. The implementation for each Operation subclass should be provided via a InterpretationFunctions instance. Interpretations can be overridden, and the override must be specified explicitly, by passing override=True to the register_functions method.

Source code in xdsl/interpreter.py
@dataclass
class Interpreter:
    """
    An extensible interpreter, initialised with a Module to interpret. The
    implementation for each Operation subclass should be provided via a
    `InterpretationFunctions` instance. Interpretations can be overridden, and
    the override must be specified explicitly, by passing `override=True` to
    the `register_functions` method.
    """

    class Listener:
        """
        Base class for observing the operations that are interpreted during a run.
        """

        def will_interpret_op(self, op: Operation, args: PythonValues) -> None: ...

        def did_interpret_op(self, op: Operation, results: PythonValues) -> None: ...

    SYSTEM_BITWIDTH: ClassVar[Literal[32, 64] | None] = _get_system_bitwidth()
    DEFAULT_BITWIDTH: ClassVar[Literal[32, 64]] = (
        32 if SYSTEM_BITWIDTH is None else SYSTEM_BITWIDTH
    )

    module: ModuleOp
    index_bitwidth: Literal[32, 64] = field(default=DEFAULT_BITWIDTH)
    """
    Number of bits in the binary representation of the index
    """
    _impls: _InterpreterFunctionImpls = field(default_factory=_InterpreterFunctionImpls)
    _ctx: ScopedDict[SSAValue, Any] = field(
        default_factory=lambda: ScopedDict[SSAValue, Any](name="root")
    )
    """
    Object holding the Python values associated with SSAValues during an
    interpretation context. An environment is a stack of scopes, values are
    assigned to the current scope, but can be fetched from a parent scope.
    """
    file: IO[str] | None = field(default=None)
    _symbol_table: dict[str, Operation] | None = None
    _impl_data: _IMPL_DATA = field(default_factory=_IMPL_DATA)
    """
    Runtime data associated with an interpreter functions implementation.
    """
    listeners: tuple[Listener, ...] = field(default=())

    def get_values(self, values: Iterable[SSAValue]) -> tuple[Any, ...]:
        """
        Get values from current environment.
        """
        return tuple(self._ctx[value] for value in values)

    def set_values(self, pairs: Iterable[tuple[SSAValue, Any]]):
        """
        Set values to current scope.
        Raises InterpretationError if len(ssa_values) != len(result_values), or
        if SSA value already has a Python value in the current scope.
        """
        for ssa_value, result_value in pairs:
            self._ctx[ssa_value] = result_value

    def push_scope(self, name: str | None = None) -> None:
        """
        Create new scope in current environment, with optional custom `name`.
        """
        self._ctx = ScopedDict(name=name, parent=self._ctx)

    def pop_scope(self) -> None:
        """
        Discard the current scope, and all the values registered in it. Sets
        parent scope of current scope to new current scope.
        Raises InterpretationError if current scope is root scope.
        """
        if self._ctx.parent is None:
            raise InterpretationError("Attempting to pop root env")

        self._ctx = self._ctx.parent

    def register_implementations(
        self, impls: InterpreterFunctions, /, override: bool = False
    ) -> None:
        """
        Register implementations for operations defined in given
        `InterpreterFunctions` object. Raise InterpretationError if an
        operation already has an implementation registered, unless override is
        set to True.
        """
        self._impls.register_from(impls, override=override)

    def _run_op(self, op: Operation, inputs: PythonValues) -> OpImplResult:
        if (operands_count := len(op.operands)) != (inputs_count := len(inputs)):
            raise InterpretationError(
                f"Number of operands ({operands_count}) doesn't match the number of inputs ({inputs_count})."
            )
        for listener in self.listeners:
            listener.will_interpret_op(op, inputs)
        result = self._impls.run(self, op, inputs)
        if (results_count := len(op.results)) != (
            actual_result_count := len(result.values)
        ):
            raise InterpretationError(
                f"Number of operation results ({results_count}) doesn't match the "
                f"number of implementation results ({actual_result_count})."
            )
        for listener in self.listeners:
            listener.did_interpret_op(op, result.values)
        return result

    def run_op(
        self, op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()
    ) -> PythonValues:
        """
        Calls the implementation for the given operation.
        """
        if not isinstance(op, Operation):
            op = self.get_op_for_symbol(op)

        return self._run_op(op, inputs).values

    def call_op(
        self, op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()
    ) -> PythonValues:
        """
        Calls the implementation for the given operation.
        """
        if not isinstance(op, Operation):
            op = self.get_op_for_symbol(op)
        results = self._impls.call(self, op, inputs)
        return results

    def call_external(
        self, sym_name: str, op: Operation, inputs: PythonValues = ()
    ) -> PythonValues:
        return self._impls.call_external(self, sym_name, op, inputs)

    def run_ssacfg_region(
        self, region: Region, args: PythonValues, name: str = "unknown"
    ) -> PythonValues:
        """
        Interpret an SSACFG-semantic Region.
        Creates a new scope, then executes the first block in the region. The first block
        is expected to return the results of the region directly.
        """
        results = ()
        if not region.blocks:
            return results

        initial_scope = self._ctx
        block = region.blocks.first

        while block is not None:
            self.push_scope(name)
            self.set_values(zip(block.args, args))

            op: Operation | None = block.first_op
            block = None

            while op is not None:
                inputs = self.get_values(op.operands)
                result = self._run_op(op, inputs)
                self.interpreter_assert(
                    len(op.results) == len(result.values),
                    f"Incorrect number of results for op {op.name}, expected {len(op.results)} but got {len(result.values)}",
                )
                self.set_values(zip(op.results, result.values))

                if result.terminator_value is not None:
                    match result.terminator_value:
                        case ReturnedValues():
                            # update results and break out of outer loop
                            results = result.terminator_value.values
                            break
                        case Successor():
                            # block won't be None, so only break out of inner loop
                            block, args = result.terminator_value
                            break

                # Set up next iteration
                op = op.next_op

        self._ctx = initial_scope
        return results

    def cast_value(self, o: Attribute, r: Attribute, value: Any) -> Any:
        """
        If the type of the operand and result are not the same, then look up the
        user-provided conversion function.
        """
        if o == r:
            return value

        return self._impls.cast(o, r, value)

    def value_for_attribute(self, attr: Attribute, type_attr: Attribute) -> Any:
        return self._impls.attr_value(self, attr, type_attr)

    def get_op_for_symbol(self, symbol: str | SymbolRefAttr) -> Operation:
        op = SymbolTable.lookup_symbol(self.module, symbol)
        if op is not None:
            return op
        raise InterpretationError(f"Could not find symbol {symbol}")

    def get_data(
        self,
        functions: type[InterpreterFunctions],
        key: str,
        factory: Callable[[], Any],
    ) -> Any:
        """
        Get data associated with a specific interpreter functions class, with a given key.
        If the data is missing, the `factory` argument will be executed and stored on the
        interpreter.
        """
        if functions not in self._impl_data:
            functions_data: dict[str, Any] = {}
            self._impl_data[functions] = functions_data
        else:
            functions_data = self._impl_data[functions]

        if key not in functions_data:
            data = factory()
            functions_data[key] = data
        else:
            data = functions_data[key]

        return data

    def set_data(
        self,
        functions: type[InterpreterFunctions],
        key: str,
        value: Any,
    ):
        if functions not in self._impl_data:
            functions_data: dict[str, Any] = {}
            self._impl_data[functions] = functions_data
        else:
            functions_data = self._impl_data[functions]
        functions_data[key] = value

    def print(self, *args: Any, **kwargs: Any):
        """Print to current file."""
        print(*args, **kwargs, file=self.file)

    def interpreter_assert(self, condition: bool, message: str | None = None):
        """Raise InterpretationError if condition is not satisfied."""
        if not condition:
            self.raise_error(message)

    def scope_names(self):
        ctx = self._ctx

        while ctx is not None:
            yield ctx.name or "unknown"
            ctx = ctx.parent

    def raise_error(self, message: str | None = None):
        scope_description = "/".join(self.scope_names())
        raise InterpretationError(f"AssertionError: ({scope_description})({message})")

SYSTEM_BITWIDTH: Literal[32, 64] | None = _get_system_bitwidth() class-attribute

DEFAULT_BITWIDTH: Literal[32, 64] = 32 if SYSTEM_BITWIDTH is None else SYSTEM_BITWIDTH class-attribute

module: ModuleOp instance-attribute

index_bitwidth: Literal[32, 64] = field(default=DEFAULT_BITWIDTH) class-attribute instance-attribute

Number of bits in the binary representation of the index

file: IO[str] | None = field(default=None) class-attribute instance-attribute

listeners: tuple[Listener, ...] = field(default=()) class-attribute instance-attribute

Listener

Base class for observing the operations that are interpreted during a run.

Source code in xdsl/interpreter.py
class Listener:
    """
    Base class for observing the operations that are interpreted during a run.
    """

    def will_interpret_op(self, op: Operation, args: PythonValues) -> None: ...

    def did_interpret_op(self, op: Operation, results: PythonValues) -> None: ...
will_interpret_op(op: Operation, args: PythonValues) -> None
Source code in xdsl/interpreter.py
def will_interpret_op(self, op: Operation, args: PythonValues) -> None: ...
did_interpret_op(op: Operation, results: PythonValues) -> None
Source code in xdsl/interpreter.py
def did_interpret_op(self, op: Operation, results: PythonValues) -> None: ...

__init__(module: ModuleOp, index_bitwidth: Literal[32, 64] = DEFAULT_BITWIDTH, _impls: _InterpreterFunctionImpls = _InterpreterFunctionImpls(), _ctx: ScopedDict[SSAValue, Any] = (lambda: ScopedDict[SSAValue, Any](name='root'))(), file: IO[str] | None = None, _symbol_table: dict[str, Operation] | None = None, _impl_data: _IMPL_DATA = _IMPL_DATA(), listeners: tuple[Listener, ...] = ()) -> None

get_values(values: Iterable[SSAValue]) -> tuple[Any, ...]

Get values from current environment.

Source code in xdsl/interpreter.py
def get_values(self, values: Iterable[SSAValue]) -> tuple[Any, ...]:
    """
    Get values from current environment.
    """
    return tuple(self._ctx[value] for value in values)

set_values(pairs: Iterable[tuple[SSAValue, Any]])

Set values to current scope. Raises InterpretationError if len(ssa_values) != len(result_values), or if SSA value already has a Python value in the current scope.

Source code in xdsl/interpreter.py
def set_values(self, pairs: Iterable[tuple[SSAValue, Any]]):
    """
    Set values to current scope.
    Raises InterpretationError if len(ssa_values) != len(result_values), or
    if SSA value already has a Python value in the current scope.
    """
    for ssa_value, result_value in pairs:
        self._ctx[ssa_value] = result_value

push_scope(name: str | None = None) -> None

Create new scope in current environment, with optional custom name.

Source code in xdsl/interpreter.py
def push_scope(self, name: str | None = None) -> None:
    """
    Create new scope in current environment, with optional custom `name`.
    """
    self._ctx = ScopedDict(name=name, parent=self._ctx)

pop_scope() -> None

Discard the current scope, and all the values registered in it. Sets parent scope of current scope to new current scope. Raises InterpretationError if current scope is root scope.

Source code in xdsl/interpreter.py
def pop_scope(self) -> None:
    """
    Discard the current scope, and all the values registered in it. Sets
    parent scope of current scope to new current scope.
    Raises InterpretationError if current scope is root scope.
    """
    if self._ctx.parent is None:
        raise InterpretationError("Attempting to pop root env")

    self._ctx = self._ctx.parent

register_implementations(impls: InterpreterFunctions, /, override: bool = False) -> None

Register implementations for operations defined in given InterpreterFunctions object. Raise InterpretationError if an operation already has an implementation registered, unless override is set to True.

Source code in xdsl/interpreter.py
def register_implementations(
    self, impls: InterpreterFunctions, /, override: bool = False
) -> None:
    """
    Register implementations for operations defined in given
    `InterpreterFunctions` object. Raise InterpretationError if an
    operation already has an implementation registered, unless override is
    set to True.
    """
    self._impls.register_from(impls, override=override)

run_op(op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()) -> PythonValues

Calls the implementation for the given operation.

Source code in xdsl/interpreter.py
def run_op(
    self, op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()
) -> PythonValues:
    """
    Calls the implementation for the given operation.
    """
    if not isinstance(op, Operation):
        op = self.get_op_for_symbol(op)

    return self._run_op(op, inputs).values

call_op(op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()) -> PythonValues

Calls the implementation for the given operation.

Source code in xdsl/interpreter.py
def call_op(
    self, op: Operation | str | SymbolRefAttr, inputs: PythonValues = ()
) -> PythonValues:
    """
    Calls the implementation for the given operation.
    """
    if not isinstance(op, Operation):
        op = self.get_op_for_symbol(op)
    results = self._impls.call(self, op, inputs)
    return results

call_external(sym_name: str, op: Operation, inputs: PythonValues = ()) -> PythonValues

Source code in xdsl/interpreter.py
def call_external(
    self, sym_name: str, op: Operation, inputs: PythonValues = ()
) -> PythonValues:
    return self._impls.call_external(self, sym_name, op, inputs)

run_ssacfg_region(region: Region, args: PythonValues, name: str = 'unknown') -> PythonValues

Interpret an SSACFG-semantic Region. Creates a new scope, then executes the first block in the region. The first block is expected to return the results of the region directly.

Source code in xdsl/interpreter.py
def run_ssacfg_region(
    self, region: Region, args: PythonValues, name: str = "unknown"
) -> PythonValues:
    """
    Interpret an SSACFG-semantic Region.
    Creates a new scope, then executes the first block in the region. The first block
    is expected to return the results of the region directly.
    """
    results = ()
    if not region.blocks:
        return results

    initial_scope = self._ctx
    block = region.blocks.first

    while block is not None:
        self.push_scope(name)
        self.set_values(zip(block.args, args))

        op: Operation | None = block.first_op
        block = None

        while op is not None:
            inputs = self.get_values(op.operands)
            result = self._run_op(op, inputs)
            self.interpreter_assert(
                len(op.results) == len(result.values),
                f"Incorrect number of results for op {op.name}, expected {len(op.results)} but got {len(result.values)}",
            )
            self.set_values(zip(op.results, result.values))

            if result.terminator_value is not None:
                match result.terminator_value:
                    case ReturnedValues():
                        # update results and break out of outer loop
                        results = result.terminator_value.values
                        break
                    case Successor():
                        # block won't be None, so only break out of inner loop
                        block, args = result.terminator_value
                        break

            # Set up next iteration
            op = op.next_op

    self._ctx = initial_scope
    return results

cast_value(o: Attribute, r: Attribute, value: Any) -> Any

If the type of the operand and result are not the same, then look up the user-provided conversion function.

Source code in xdsl/interpreter.py
def cast_value(self, o: Attribute, r: Attribute, value: Any) -> Any:
    """
    If the type of the operand and result are not the same, then look up the
    user-provided conversion function.
    """
    if o == r:
        return value

    return self._impls.cast(o, r, value)

value_for_attribute(attr: Attribute, type_attr: Attribute) -> Any

Source code in xdsl/interpreter.py
def value_for_attribute(self, attr: Attribute, type_attr: Attribute) -> Any:
    return self._impls.attr_value(self, attr, type_attr)

get_op_for_symbol(symbol: str | SymbolRefAttr) -> Operation

Source code in xdsl/interpreter.py
def get_op_for_symbol(self, symbol: str | SymbolRefAttr) -> Operation:
    op = SymbolTable.lookup_symbol(self.module, symbol)
    if op is not None:
        return op
    raise InterpretationError(f"Could not find symbol {symbol}")

get_data(functions: type[InterpreterFunctions], key: str, factory: Callable[[], Any]) -> Any

Get data associated with a specific interpreter functions class, with a given key. If the data is missing, the factory argument will be executed and stored on the interpreter.

Source code in xdsl/interpreter.py
def get_data(
    self,
    functions: type[InterpreterFunctions],
    key: str,
    factory: Callable[[], Any],
) -> Any:
    """
    Get data associated with a specific interpreter functions class, with a given key.
    If the data is missing, the `factory` argument will be executed and stored on the
    interpreter.
    """
    if functions not in self._impl_data:
        functions_data: dict[str, Any] = {}
        self._impl_data[functions] = functions_data
    else:
        functions_data = self._impl_data[functions]

    if key not in functions_data:
        data = factory()
        functions_data[key] = data
    else:
        data = functions_data[key]

    return data

set_data(functions: type[InterpreterFunctions], key: str, value: Any)

Source code in xdsl/interpreter.py
def set_data(
    self,
    functions: type[InterpreterFunctions],
    key: str,
    value: Any,
):
    if functions not in self._impl_data:
        functions_data: dict[str, Any] = {}
        self._impl_data[functions] = functions_data
    else:
        functions_data = self._impl_data[functions]
    functions_data[key] = value

print(*args: Any, **kwargs: Any)

Print to current file.

Source code in xdsl/interpreter.py
def print(self, *args: Any, **kwargs: Any):
    """Print to current file."""
    print(*args, **kwargs, file=self.file)

interpreter_assert(condition: bool, message: str | None = None)

Raise InterpretationError if condition is not satisfied.

Source code in xdsl/interpreter.py
def interpreter_assert(self, condition: bool, message: str | None = None):
    """Raise InterpretationError if condition is not satisfied."""
    if not condition:
        self.raise_error(message)

scope_names()

Source code in xdsl/interpreter.py
def scope_names(self):
    ctx = self._ctx

    while ctx is not None:
        yield ctx.name or "unknown"
        ctx = ctx.parent

raise_error(message: str | None = None)

Source code in xdsl/interpreter.py
def raise_error(self, message: str | None = None):
    scope_description = "/".join(self.scope_names())
    raise InterpretationError(f"AssertionError: ({scope_description})({message})")

OpCounter dataclass

Bases: Listener

Counts the number of times that an op has been run by the interpreter.

Source code in xdsl/interpreter.py
@dataclass
class OpCounter(Interpreter.Listener):
    """
    Counts the number of times that an op has been run by the interpreter.
    """

    ops: Counter[str] = field(default_factory=Counter[str])

    def will_interpret_op(self, op: Operation, args: PythonValues) -> None:
        self.ops[op.name] += 1

ops: Counter[str] = field(default_factory=Counter[str]) class-attribute instance-attribute

__init__(ops: Counter[str] = Counter[str]()) -> None

will_interpret_op(op: Operation, args: PythonValues) -> None

Source code in xdsl/interpreter.py
def will_interpret_op(self, op: Operation, args: PythonValues) -> None:
    self.ops[op.name] += 1

impl(op_type: type[OperationInvT]) -> Callable[[NonTerminatorOpImpl[_FT, OperationInvT]], OpImpl[_FT, OperationInvT]]

Marks the Python implementation of an xDSL Operation instance, to be used by an Interpreter. The Interpreter will fetch the Python values associated with the operands from the current environment, and pass them as the args parameter. The returned values are assigned to the results values.

See InterpreterFunctions

Source code in xdsl/interpreter.py
def impl(
    op_type: type[OperationInvT],
) -> Callable[[NonTerminatorOpImpl[_FT, OperationInvT]], OpImpl[_FT, OperationInvT]]:
    """
    Marks the Python implementation of an xDSL `Operation` instance, to be used
    by an `Interpreter`. The Interpreter will fetch the Python values
    associated with the operands from the current environment, and pass them as
    the `args` parameter. The returned values are assigned to the `results`
    values.

    See `InterpreterFunctions`
    """

    if op_type.has_trait(IsTerminator):
        raise ValueError(
            "Operations that are terminators must use `impl_terminator` annotation"
        )

    def annot(
        func: NonTerminatorOpImpl[_FT, OperationInvT],
    ) -> OpImpl[_FT, OperationInvT]:
        def impl(
            ft: _FT, interpreter: Interpreter, op: OperationInvT, values: PythonValues
        ) -> OpImplResult:
            return OpImplResult(func(ft, interpreter, op, values), None)

        setattr(impl, _IMPL_OP_TYPE_KEY, op_type)
        return impl

    return annot

impl_terminator(op_type: type[OperationInvT]) -> Callable[[TerminatorOpImpl[_FT, OperationInvT]], OpImpl[_FT, OperationInvT]]

Marks the Python implementation of an xDSL Operation instance, to be used by an Interpreter. The Interpreter will fetch the Python values associated with the operands from the current environment, and pass them as the args parameter. The returned values are assigned to the results values.

See InterpreterFunctions

Source code in xdsl/interpreter.py
def impl_terminator(
    op_type: type[OperationInvT],
) -> Callable[[TerminatorOpImpl[_FT, OperationInvT]], OpImpl[_FT, OperationInvT]]:
    """
    Marks the Python implementation of an xDSL `Operation` instance, to be used
    by an `Interpreter`. The Interpreter will fetch the Python values
    associated with the operands from the current environment, and pass them as
    the `args` parameter. The returned values are assigned to the `results`
    values.

    See `InterpreterFunctions`
    """

    if not op_type.has_trait(IsTerminator):
        raise ValueError(
            "Operations that are not terminators must use `impl` annotation"
        )

    def annot(func: TerminatorOpImpl[_FT, OperationInvT]) -> OpImpl[_FT, OperationInvT]:
        def impl(
            ft: _FT, interpreter: Interpreter, op: OperationInvT, values: PythonValues
        ) -> OpImplResult:
            successor, args = func(ft, interpreter, op, values)
            return OpImplResult(args, successor)

        setattr(impl, _IMPL_OP_TYPE_KEY, op_type)
        return impl

    return annot

impl_cast(input_type: type[_AttributeInvT0], output_type: type[_AttributeInvT1]) -> Callable[[CastImpl[_FT, _AttributeInvT0, _AttributeInvT1]], CastImpl[_FT, _AttributeInvT0, _AttributeInvT1]]

Marks the Python implementation of a value cast from one type to another. The cast_value method on Interpreter will call into this implementation for matching input and output types.

See InterpreterFunctions for more documentation.

Source code in xdsl/interpreter.py
def impl_cast(
    input_type: type[_AttributeInvT0],
    output_type: type[_AttributeInvT1],
) -> Callable[
    [CastImpl[_FT, _AttributeInvT0, _AttributeInvT1]],
    CastImpl[_FT, _AttributeInvT0, _AttributeInvT1],
]:
    """
    Marks the Python implementation of a value cast from one type to another. The
    `cast_value` method on `Interpreter` will call into this implementation for matching
    input and output types.

    See `InterpreterFunctions` for more documentation.
    """

    def annot(
        func: CastImpl[_FT, _AttributeInvT0, _AttributeInvT1],
    ) -> CastImpl[_FT, _AttributeInvT0, _AttributeInvT1]:
        setattr(func, _CAST_IMPL_TYPES_KEY, (input_type, output_type))
        return func

    return annot

impl_attr(input_type: IRDLAttrConstraint[AttributeInvNoDefaultT]) -> Callable[[AttrImpl[_FT, AttributeInvNoDefaultT]], AttrImpl[_FT, AttributeInvNoDefaultT]]

Marks the conversion from an attribute to a Python value. The value_for_attribute method on Interpreter will call into this implementation for matching input and output types. The input must convert to a BaseAttr or an AnyOf of BaseAttr constraints, all with runtime-final attribute classes. Each class is registered separately for exact-class lookup. Constraints on attribute values or parameters are not supported.

See InterpreterFunctions for more documentation.

Source code in xdsl/interpreter.py
def impl_attr(
    input_type: IRDLAttrConstraint[AttributeInvNoDefaultT],
) -> Callable[
    [AttrImpl[_FT, AttributeInvNoDefaultT]],
    AttrImpl[_FT, AttributeInvNoDefaultT],
]:
    """
    Marks the conversion from an attribute to a Python value. The
    `value_for_attribute` method on `Interpreter` will call into this implementation for
    matching input and output types. The input must convert to a `BaseAttr` or an
    `AnyOf` of `BaseAttr` constraints, all with runtime-final attribute classes.
    Each class is registered separately for exact-class lookup. Constraints on
    attribute values or parameters are not supported.

    See `InterpreterFunctions` for more documentation.
    """

    constraint = irdl_to_attr_constraint(input_type)
    constraints = (
        constraint.attr_constrs if isinstance(constraint, AnyOf) else (constraint,)
    )
    types: list[type[Attribute]] = []
    for constraint in constraints:
        if not isinstance(constraint, BaseAttr):
            raise ValueError(
                f"impl_attr requires a BaseAttr or an AnyOf of BaseAttr constraints, "
                f"got {constraint}"
            )
        if constraint.get_bases() is None:
            raise ValueError(
                f"impl_attr requires runtime-final attribute classes, "
                f"got {constraint.attr.__name__}"
            )
        types.append(constraint.attr)

    def annot(func: AttrImpl[_FT, AttributeInvT]) -> AttrImpl[_FT, AttributeInvT]:
        setattr(func, _ATTR_IMPL_TYPES_KEY, types)
        return func

    return annot

impl_external(sym_name: str) -> Callable[[ExtFuncImpl[_FT]], ExtFuncImpl[_FT]]

Marks the Python implementation of an external function.

Source code in xdsl/interpreter.py
def impl_external(
    sym_name: str,
) -> Callable[[ExtFuncImpl[_FT]], ExtFuncImpl[_FT]]:
    """
    Marks the Python implementation of an external function.
    """

    def annot(func: ExtFuncImpl[_FT]) -> ExtFuncImpl[_FT]:
        setattr(func, _EXT_FUNC_NAME_KEY, sym_name)
        return func

    return annot

impl_callable(op_type: type[OperationInvT]) -> Callable[[NonTerminatorOpImpl[_FT, OperationInvT]], NonTerminatorOpImpl[_FT, OperationInvT]]

Marks the Python implementation of a callable operation.

Source code in xdsl/interpreter.py
def impl_callable(
    op_type: type[OperationInvT],
) -> Callable[
    [NonTerminatorOpImpl[_FT, OperationInvT]], NonTerminatorOpImpl[_FT, OperationInvT]
]:
    """
    Marks the Python implementation of a callable operation.
    """

    if not op_type.has_trait(CallableOpInterface):
        raise ValueError("Operations that are not callable must use `impl` annotation")

    def annot(
        impl: NonTerminatorOpImpl[_FT, OperationInvT],
    ) -> NonTerminatorOpImpl[_FT, OperationInvT]:
        setattr(impl, _CALLABLE_OP_TYPE_KEY, op_type)
        return impl

    return annot

register_impls(ft: type[_FT]) -> type[_FT]

Enumerates the methods on a given class, and registers the ones marked with @impl in a way that an Interpreter instance can find them for dynamic dispatch during interpretation.

See InterpreterFunctions

Source code in xdsl/interpreter.py
def register_impls(ft: type[_FT]) -> type[_FT]:
    """
    Enumerates the methods on a given class, and registers the ones marked with
    `@impl` in a way that an `Interpreter` instance can find them for dynamic
    dispatch during interpretation.

    See `InterpreterFunctions`
    """

    impl_dict: _ImplDict = {}
    cast_impl_dict: _CastImplDict = {}
    external_func_dict: _ExtFuncImplDict = {}
    attr_impl_dict: _AttrImplDict = {}
    callable_impl_dict: _CallableImplDict = {}

    for cls in ft.mro():
        # Iterate from subclass through superclasses
        # Assign definitions, unless they've been redefined in a subclass
        for val in cls.__dict__.values():
            if _IMPL_OP_TYPE_KEY in val.__dir__():
                # This is an annotated operation implementation
                op_type = getattr(val, _IMPL_OP_TYPE_KEY)
                if op_type not in impl_dict:
                    # subclass overrides superclass definition
                    impl_dict[op_type] = val
            elif _CAST_IMPL_TYPES_KEY in val.__dir__():
                # This is an annotated cast implementation
                types = getattr(val, _CAST_IMPL_TYPES_KEY)
                if types not in cast_impl_dict:
                    # subclass overrides superclass definition
                    cast_impl_dict[types] = val
            elif _EXT_FUNC_NAME_KEY in val.__dir__():
                # This is an annotated external function
                sym_name = getattr(val, _EXT_FUNC_NAME_KEY)
                assert isinstance(sym_name, str)
                if sym_name not in external_func_dict:
                    # subclass overrides superclass definition
                    external_func_dict[sym_name] = val
            elif _ATTR_IMPL_TYPES_KEY in val.__dir__():
                # This is an attribute value implementation
                types = getattr(val, _ATTR_IMPL_TYPES_KEY)
                for attr_type in types:
                    if attr_type not in attr_impl_dict:
                        # subclass overrides superclass definition
                        attr_impl_dict[attr_type] = val
            elif _CALLABLE_OP_TYPE_KEY in val.__dir__():
                op_type = getattr(val, _CALLABLE_OP_TYPE_KEY)
                if op_type not in callable_impl_dict:
                    # subclass overrides superclass definition
                    callable_impl_dict[op_type] = val

    setattr(ft, _IMPL_DICT_KEY, impl_dict)
    setattr(ft, _CAST_IMPL_DICT_KEY, cast_impl_dict)
    setattr(ft, _ATTR_IMPL_DICT_KEY, attr_impl_dict)
    setattr(ft, _EXT_FUNC_DICT_KEY, external_func_dict)
    setattr(ft, _CALLABLE_IMPL_DICT_KEY, callable_impl_dict)

    return ft