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.

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
45
46
47
48
49
50
51
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
54
55
56
57
58
59
60
61
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
73
74
75
76
77
78
79
80
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
237
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
263
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
@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
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
@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
646
647
648
649
650
651
652
653
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
651
def will_interpret_op(self, op: Operation, args: PythonValues) -> None: ...
did_interpret_op(op: Operation, results: PythonValues) -> None
Source code in xdsl/interpreter.py
653
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
682
683
684
685
686
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
688
689
690
691
692
693
694
695
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
697
698
699
700
701
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
703
704
705
706
707
708
709
710
711
712
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
714
715
716
717
718
719
720
721
722
723
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
744
745
746
747
748
749
750
751
752
753
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
755
756
757
758
759
760
761
762
763
764
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
766
767
768
769
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
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
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
819
820
821
822
823
824
825
826
827
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
829
830
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
832
833
834
835
836
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
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
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
863
864
865
866
867
868
869
870
871
872
873
874
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
876
877
878
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
880
881
882
883
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
885
886
887
888
889
890
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
892
893
894
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
897
898
899
900
901
902
903
904
905
906
@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
905
906
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
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
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
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: type[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.

See InterpreterFunctions for more documentation.

Source code in xdsl/interpreter.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
def impl_attr(
    input_type: type[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.

    See `InterpreterFunctions` for more documentation.
    """

    def annot(func: AttrImpl[_FT, AttributeInvT]) -> AttrImpl[_FT, AttributeInvT]:
        setattr(func, _ATTR_IMPL_TYPES_KEY, input_type)
        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
405
406
407
408
409
410
411
412
413
414
415
416
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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
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
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
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)
                if types not in attr_impl_dict:
                    # subclass overrides superclass definition
                    attr_impl_dict[types] = 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