Skip to content

Core

core

OpT = TypeVar('OpT', bound='Operation') module-attribute

DialectInterfaceT = TypeVar('DialectInterfaceT', bound=DialectInterface) module-attribute

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

DataElement = TypeVar('DataElement', covariant=True, bound=Hashable) module-attribute

AttributeCovT = TypeVar('AttributeCovT', bound=Attribute, covariant=True, default=Attribute) module-attribute

AttributeInvT = TypeVar('AttributeInvT', bound=Attribute, default=Attribute) module-attribute

EnumType = TypeVar('EnumType', bound=StrEnum) module-attribute

SSAValueCovT = TypeVar('SSAValueCovT', bound=(SSAValue[Attribute]), default=(SSAValue[Attribute])) module-attribute

OperationInvT = TypeVar('OperationInvT', bound=Operation) module-attribute

IRNode: TypeAlias = Operation | Region | Block module-attribute

Dialect dataclass

Contains the operations and attributes of a specific dialect

Source code in xdsl/ir/core.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@dataclass
class Dialect:
    """Contains the operations and attributes of a specific dialect"""

    _name: str

    _operations: list[type[Operation]] = field(
        default_factory=list[type["Operation"]], init=True, repr=True
    )
    _attributes: list[type[Attribute]] = field(
        default_factory=list[type["Attribute"]], init=True, repr=True
    )
    _interfaces: list[DialectInterface] = field(
        default_factory=list[DialectInterface], init=True, repr=True
    )

    @property
    def operations(self) -> Iterator[type[Operation]]:
        return iter(self._operations)

    @property
    def attributes(self) -> Iterator[type[Attribute]]:
        return iter(self._attributes)

    @property
    def name(self) -> str:
        return self._name

    @staticmethod
    def split_name(name: str) -> tuple[str, str]:
        try:
            names = name.split(".", 1)
            first, second = names
            return (first, second)
        except ValueError as e:
            raise ValueError(f"Invalid operation or attribute name {name}.") from e

    def get_interface(
        self, interface: type[DialectInterfaceT]
    ) -> DialectInterfaceT | None:
        """
        Return a class that implements the 'interface' if it exists.
        """
        for i in self._interfaces:
            if isinstance(i, interface):
                return i
        return None

    def has_interface(self, interface: type[DialectInterfaceT]) -> bool:
        return self.get_interface(interface) is not None

operations: Iterator[type[Operation]] property

attributes: Iterator[type[Attribute]] property

name: str property

__init__(_name: str, _operations: list[type[Operation]] = list[type['Operation']](), _attributes: list[type[Attribute]] = list[type['Attribute']](), _interfaces: list[DialectInterface] = list[DialectInterface]()) -> None

split_name(name: str) -> tuple[str, str] staticmethod

Source code in xdsl/ir/core.py
77
78
79
80
81
82
83
84
@staticmethod
def split_name(name: str) -> tuple[str, str]:
    try:
        names = name.split(".", 1)
        first, second = names
        return (first, second)
    except ValueError as e:
        raise ValueError(f"Invalid operation or attribute name {name}.") from e

get_interface(interface: type[DialectInterfaceT]) -> DialectInterfaceT | None

Return a class that implements the 'interface' if it exists.

Source code in xdsl/ir/core.py
86
87
88
89
90
91
92
93
94
95
def get_interface(
    self, interface: type[DialectInterfaceT]
) -> DialectInterfaceT | None:
    """
    Return a class that implements the 'interface' if it exists.
    """
    for i in self._interfaces:
        if isinstance(i, interface):
            return i
    return None

has_interface(interface: type[DialectInterfaceT]) -> bool

Source code in xdsl/ir/core.py
97
98
def has_interface(self, interface: type[DialectInterfaceT]) -> bool:
    return self.get_interface(interface) is not None

Attribute dataclass

Bases: ABC

A compile-time value. Attributes are used to represent SSA variable types, and can be attached on operations to give extra information.

Source code in xdsl/ir/core.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
@dataclass(frozen=True)
class Attribute(ABC):
    """
    A compile-time value.
    Attributes are used to represent SSA variable types, and can be attached
    on operations to give extra information.
    """

    name: ClassVar[str] = field(init=False, repr=False)
    """The attribute name should be a static field in the attribute classes."""

    def __post_init__(self):
        self._verify()
        if not isinstance(self, Data | ParametrizedAttribute):
            raise TypeError("Attributes should only be Data or ParametrizedAttribute")

    def _verify(self):
        self.verify()

    def verify(self) -> None:
        """
        Check that the attribute parameters satisfy the expected invariants.
        Raise a VerifyException otherwise.
        """
        pass

    def __str__(self) -> str:
        from xdsl.printer import Printer

        res = StringIO()
        printer = Printer(stream=res)
        printer.print_attribute(self)
        return res.getvalue()

name: str = field(init=False, repr=False) class-attribute

The attribute name should be a static field in the attribute classes.

__init__() -> None

__post_init__()

Source code in xdsl/ir/core.py
115
116
117
118
def __post_init__(self):
    self._verify()
    if not isinstance(self, Data | ParametrizedAttribute):
        raise TypeError("Attributes should only be Data or ParametrizedAttribute")

verify() -> None

Check that the attribute parameters satisfy the expected invariants. Raise a VerifyException otherwise.

Source code in xdsl/ir/core.py
123
124
125
126
127
128
def verify(self) -> None:
    """
    Check that the attribute parameters satisfy the expected invariants.
    Raise a VerifyException otherwise.
    """
    pass

__str__() -> str

Source code in xdsl/ir/core.py
130
131
132
133
134
135
136
def __str__(self) -> str:
    from xdsl.printer import Printer

    res = StringIO()
    printer = Printer(stream=res)
    printer.print_attribute(self)
    return res.getvalue()

BuiltinAttribute dataclass

Bases: Attribute, ABC

This class is used to mark builtin attributes. Unlike other attributes in MLIR, parsing of Builtin attributes is handled directly by the parser. Printing of these attributes is handled by the print_builtin function, which must be implemented by all Builtin attributes. Attributes outside of the builtin dialect should not inherit from BuiltinAttribute.

Source code in xdsl/ir/core.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@dataclass(frozen=True, init=False)
class BuiltinAttribute(Attribute, ABC):
    """
    This class is used to mark builtin attributes.
    Unlike other attributes in MLIR, parsing of *Builtin* attributes
    is handled directly by the parser.
    Printing of these attributes is handled by the `print_builtin` function, which must
    be implemented by all *Builtin* attributes.
    Attributes outside of the `builtin` dialect should not inherit from `BuiltinAttribute`.
    """

    @abstractmethod
    def print_builtin(self, printer: Printer) -> None:
        """
        Prints the attribute using the supplied printer.
        `BuiltinAttribute`s need not follow the same rules as other attributes, for example
        they do not need to be prefixed by `!` or `#` and do not need to print their name.
        """
        ...

__init__() -> None

print_builtin(printer: Printer) -> None abstractmethod

Prints the attribute using the supplied printer. BuiltinAttributes need not follow the same rules as other attributes, for example they do not need to be prefixed by ! or # and do not need to print their name.

Source code in xdsl/ir/core.py
150
151
152
153
154
155
156
157
@abstractmethod
def print_builtin(self, printer: Printer) -> None:
    """
    Prints the attribute using the supplied printer.
    `BuiltinAttribute`s need not follow the same rules as other attributes, for example
    they do not need to be prefixed by `!` or `#` and do not need to print their name.
    """
    ...

TypeAttribute dataclass

Bases: Attribute

This class should only be inherited by classes inheriting Attribute. This class is only used for printing attributes in the MLIR format, inheriting this class prefix the attribute by ! instead of #.

Source code in xdsl/ir/core.py
160
161
162
163
164
165
166
167
class TypeAttribute(Attribute):
    """
    This class should only be inherited by classes inheriting Attribute.
    This class is only used for printing attributes in the MLIR format,
    inheriting this class prefix the attribute by `!` instead of `#`.
    """

    pass

OpaqueSyntaxAttribute dataclass

Bases: Attribute

This class should only be inherited by classes inheriting Attribute. This class is only used for printing attributes in the opaque form.

See external documentation.

Source code in xdsl/ir/core.py
170
171
172
173
174
175
176
177
178
class OpaqueSyntaxAttribute(Attribute):
    """
    This class should only be inherited by classes inheriting Attribute.
    This class is only used for printing attributes in the opaque form.

    See external [documentation](https://mlir.llvm.org/docs/LangRef/#dialect-attribute-values.).
    """

    pass

SpacedOpaqueSyntaxAttribute dataclass

Bases: OpaqueSyntaxAttribute

This class should only be inherited by classes inheriting Attribute. This class is only used for printing attributes in the opaque form.

See external documentation.

Source code in xdsl/ir/core.py
181
182
183
184
185
186
187
188
189
class SpacedOpaqueSyntaxAttribute(OpaqueSyntaxAttribute):
    """
    This class should only be inherited by classes inheriting Attribute.
    This class is only used for printing attributes in the opaque form.

    See external [documentation](https://mlir.llvm.org/docs/LangRef/#dialect-attribute-values.).
    """

    pass

Data dataclass

Bases: Attribute, ABC, Generic[DataElement]

An attribute represented by a Python structure.

Source code in xdsl/ir/core.py
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
@dataclass(frozen=True)
class Data(Attribute, ABC, Generic[DataElement]):
    """An attribute represented by a Python structure."""

    data: DataElement

    @classmethod
    def new(cls: type[Self], params: Any) -> Self:
        """
        Create a new `Data` given its parameter.

        The `params` argument should be of the same type as the `Data` generic
        argument.

        This function should be preferred over `__init__` when instantiating
        attributes in a generic way (i.e., without knowing their concrete type
        statically).
        """
        # Create the new attribute object, without calling its __init__.
        # We do this to allow users to redefine their own __init__.
        attr = cls.__new__(cls)

        # Call the __init__ of Data, which will set the parameters field.
        Data.__init__(attr, params)  # pyright: ignore[reportUnknownMemberType]
        return attr

    @classmethod
    def get(cls, attr: DataElement | Self) -> Self:
        """
        Creates an element of this class from `DataElement`,
        or returns the input when it is already an instance of this class.

        This function is useful for `__init__` methods, for example when a we
        would like to accept either a `StringAttr` or a `str`.
        """
        if not isinstance(attr, cls):
            attr = cls.new(attr)
        return attr

    @classmethod
    @abstractmethod
    def parse_parameter(cls, parser: AttrParser) -> DataElement:
        """Parse the attribute parameter."""

    @abstractmethod
    def print_parameter(self, printer: Printer) -> None:
        """Print the attribute parameter."""

data: DataElement instance-attribute

__init__(data: DataElement) -> None

new(params: Any) -> Self classmethod

Create a new Data given its parameter.

The params argument should be of the same type as the Data generic argument.

This function should be preferred over __init__ when instantiating attributes in a generic way (i.e., without knowing their concrete type statically).

Source code in xdsl/ir/core.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
@classmethod
def new(cls: type[Self], params: Any) -> Self:
    """
    Create a new `Data` given its parameter.

    The `params` argument should be of the same type as the `Data` generic
    argument.

    This function should be preferred over `__init__` when instantiating
    attributes in a generic way (i.e., without knowing their concrete type
    statically).
    """
    # Create the new attribute object, without calling its __init__.
    # We do this to allow users to redefine their own __init__.
    attr = cls.__new__(cls)

    # Call the __init__ of Data, which will set the parameters field.
    Data.__init__(attr, params)  # pyright: ignore[reportUnknownMemberType]
    return attr

get(attr: DataElement | Self) -> Self classmethod

Creates an element of this class from DataElement, or returns the input when it is already an instance of this class.

This function is useful for __init__ methods, for example when a we would like to accept either a StringAttr or a str.

Source code in xdsl/ir/core.py
226
227
228
229
230
231
232
233
234
235
236
237
@classmethod
def get(cls, attr: DataElement | Self) -> Self:
    """
    Creates an element of this class from `DataElement`,
    or returns the input when it is already an instance of this class.

    This function is useful for `__init__` methods, for example when a we
    would like to accept either a `StringAttr` or a `str`.
    """
    if not isinstance(attr, cls):
        attr = cls.new(attr)
    return attr

parse_parameter(parser: AttrParser) -> DataElement abstractmethod classmethod

Parse the attribute parameter.

Source code in xdsl/ir/core.py
239
240
241
242
@classmethod
@abstractmethod
def parse_parameter(cls, parser: AttrParser) -> DataElement:
    """Parse the attribute parameter."""

print_parameter(printer: Printer) -> None abstractmethod

Print the attribute parameter.

Source code in xdsl/ir/core.py
244
245
246
@abstractmethod
def print_parameter(self, printer: Printer) -> None:
    """Print the attribute parameter."""

EnumAttribute dataclass

Bases: Data[EnumType]

Core helper for Enum Attributes. Takes a StrEnum type parameter, and defines parsing/printing automatically from its values, restricted to be parsable as identifiers.

example:

class MyEnum(StrEnum):
    First = auto()
    Second = auto()

class MyEnumAttribute(EnumAttribute[MyEnum], SpacedOpaqueSyntaxAttribute):
    name = "example.my_enum"

To use this attribute suffices to have a textual representation of example<my_enum first> and example<my_enum second>

Source code in xdsl/ir/core.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
class EnumAttribute(Data[EnumType]):
    """
    Core helper for Enum Attributes. Takes a StrEnum type parameter, and defines
    parsing/printing automatically from its values, restricted to be parsable as
    identifiers.

    example:
    ```python
    class MyEnum(StrEnum):
        First = auto()
        Second = auto()

    class MyEnumAttribute(EnumAttribute[MyEnum], SpacedOpaqueSyntaxAttribute):
        name = "example.my_enum"
    ```
    To use this attribute suffices to have a textual representation
    of `example<my_enum first>` and ``example<my_enum second>``

    """

    enum_type: ClassVar[type[StrEnum]]

    def __init_subclass__(cls) -> None:
        _check_enum_constraints(cls)

    def print_parameter(self, printer: Printer) -> None:
        printer.print_identifier_or_string_literal(self.data.value)

    @classmethod
    def parse_parameter(cls, parser: AttrParser) -> EnumType:
        return cast(EnumType, parser.parse_str_enum(cls.enum_type))

enum_type: type[StrEnum] class-attribute

__init_subclass__() -> None

Source code in xdsl/ir/core.py
302
303
def __init_subclass__(cls) -> None:
    _check_enum_constraints(cls)

print_parameter(printer: Printer) -> None

Source code in xdsl/ir/core.py
305
306
def print_parameter(self, printer: Printer) -> None:
    printer.print_identifier_or_string_literal(self.data.value)

parse_parameter(parser: AttrParser) -> EnumType classmethod

Source code in xdsl/ir/core.py
308
309
310
@classmethod
def parse_parameter(cls, parser: AttrParser) -> EnumType:
    return cast(EnumType, parser.parse_str_enum(cls.enum_type))

BitEnumAttribute dataclass

Bases: Data[tuple[EnumType, ...]], Generic[EnumType]

Core helper for BitEnumAttributes. Takes a StrEnum type parameter, and defines parsing/printing automatically from its values.

Additionally, two values can be given to designate all/none bits being set.

example: ```python class MyBitEnum(StrEnum): First = auto() Second = auto()

class MyBitEnumAttribute(BitEnumAttribute[MyBitEnum]): name = "example.my_bit_enum" none_value = "none" all_value = "all"

Source code in xdsl/ir/core.py
313
314
315
316
317
318
319
320
321
322
323
324
325
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
@dataclass(frozen=True, init=False)
class BitEnumAttribute(Data[tuple[EnumType, ...]], Generic[EnumType]):
    """
    Core helper for BitEnumAttributes. Takes a StrEnum type parameter, and
    defines parsing/printing automatically from its values.

    Additionally, two values can be given to designate all/none bits being set.

    example:
    ```python
    class MyBitEnum(StrEnum):
        First = auto()
        Second = auto()

    class MyBitEnumAttribute(BitEnumAttribute[MyBitEnum]):
        name = "example.my_bit_enum"
        none_value = "none"
        all_value = "all"

    """

    enum_type: ClassVar[type[StrEnum]]
    none_value: ClassVar[str | None] = None
    all_value: ClassVar[str | None] = None

    def __init__(self, flags: None | Sequence[EnumType] | str) -> None:
        flags_: set[EnumType]
        match flags:
            case self.none_value | None:
                flags_ = set()
            case self.all_value:
                flags_ = cast(set[EnumType], set(self.enum_type))
            case other if isinstance(other, str):
                raise TypeError(
                    f"expected string parameter to be one of {self.none_value} or {self.all_value}, got {other}"
                )
            case other:
                assert not isinstance(other, str)
                flags_ = set(other)

        super().__init__(tuple(flags_))

    def __init_subclass__(cls) -> None:
        _check_enum_constraints(cls)

    @property
    def flags(self) -> set[EnumType]:
        return set(self.data)

    @classmethod
    def parse_parameter(cls, parser: AttrParser) -> tuple[EnumType, ...]:
        def parse_optional_element() -> set[EnumType] | None:
            if (
                cls.none_value is not None
                and parser.parse_optional_keyword(cls.none_value) is not None
            ):
                return set()
            if (
                cls.all_value is not None
                and parser.parse_optional_keyword(cls.all_value) is not None
            ):
                return set(cast(Iterable[EnumType], cls.enum_type))
            value = parser.parse_optional_str_enum(cls.enum_type)
            if value is None:
                return None

            return {cast(type[EnumType], cls.enum_type)(value)}

        def parse_element() -> set[EnumType]:
            if (
                cls.none_value is not None
                and parser.parse_optional_keyword(cls.none_value) is not None
            ):
                return set()
            if (
                cls.all_value is not None
                and parser.parse_optional_keyword(cls.all_value) is not None
            ):
                return set(cast(Iterable[EnumType], cls.enum_type))
            value = parser.parse_str_enum(cls.enum_type)
            return {cast(type[EnumType], cls.enum_type)(value)}

        with parser.in_angle_brackets():
            flags: list[set[EnumType]] | None = (
                parser.parse_optional_undelimited_comma_separated_list(
                    parse_optional_element, parse_element
                )
            )
            if flags is None:
                return tuple()

            res = set[EnumType]()

            for flag_set in flags:
                res |= flag_set

            return tuple(res)

    def print_parameter(self, printer: Printer):
        with printer.in_angle_brackets():
            flags = self.data
            if len(flags) == 0 and self.none_value is not None:
                printer.print_string(self.none_value)
            elif len(flags) == len(self.enum_type) and self.all_value is not None:
                printer.print_string(self.all_value)
            else:
                # make sure we emit flags in a consistent order
                printer.print_list(
                    tuple(flag.value for flag in self.enum_type if flag in flags),
                    printer.print_string,
                    ",",
                )

enum_type: type[StrEnum] class-attribute

none_value: str | None = None class-attribute

all_value: str | None = None class-attribute

flags: set[EnumType] property

__init__(flags: None | Sequence[EnumType] | str) -> None

Source code in xdsl/ir/core.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
def __init__(self, flags: None | Sequence[EnumType] | str) -> None:
    flags_: set[EnumType]
    match flags:
        case self.none_value | None:
            flags_ = set()
        case self.all_value:
            flags_ = cast(set[EnumType], set(self.enum_type))
        case other if isinstance(other, str):
            raise TypeError(
                f"expected string parameter to be one of {self.none_value} or {self.all_value}, got {other}"
            )
        case other:
            assert not isinstance(other, str)
            flags_ = set(other)

    super().__init__(tuple(flags_))

__init_subclass__() -> None

Source code in xdsl/ir/core.py
355
356
def __init_subclass__(cls) -> None:
    _check_enum_constraints(cls)

parse_parameter(parser: AttrParser) -> tuple[EnumType, ...] classmethod

Source code in xdsl/ir/core.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
@classmethod
def parse_parameter(cls, parser: AttrParser) -> tuple[EnumType, ...]:
    def parse_optional_element() -> set[EnumType] | None:
        if (
            cls.none_value is not None
            and parser.parse_optional_keyword(cls.none_value) is not None
        ):
            return set()
        if (
            cls.all_value is not None
            and parser.parse_optional_keyword(cls.all_value) is not None
        ):
            return set(cast(Iterable[EnumType], cls.enum_type))
        value = parser.parse_optional_str_enum(cls.enum_type)
        if value is None:
            return None

        return {cast(type[EnumType], cls.enum_type)(value)}

    def parse_element() -> set[EnumType]:
        if (
            cls.none_value is not None
            and parser.parse_optional_keyword(cls.none_value) is not None
        ):
            return set()
        if (
            cls.all_value is not None
            and parser.parse_optional_keyword(cls.all_value) is not None
        ):
            return set(cast(Iterable[EnumType], cls.enum_type))
        value = parser.parse_str_enum(cls.enum_type)
        return {cast(type[EnumType], cls.enum_type)(value)}

    with parser.in_angle_brackets():
        flags: list[set[EnumType]] | None = (
            parser.parse_optional_undelimited_comma_separated_list(
                parse_optional_element, parse_element
            )
        )
        if flags is None:
            return tuple()

        res = set[EnumType]()

        for flag_set in flags:
            res |= flag_set

        return tuple(res)

print_parameter(printer: Printer)

Source code in xdsl/ir/core.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
def print_parameter(self, printer: Printer):
    with printer.in_angle_brackets():
        flags = self.data
        if len(flags) == 0 and self.none_value is not None:
            printer.print_string(self.none_value)
        elif len(flags) == len(self.enum_type) and self.all_value is not None:
            printer.print_string(self.all_value)
        else:
            # make sure we emit flags in a consistent order
            printer.print_list(
                tuple(flag.value for flag in self.enum_type if flag in flags),
                printer.print_string,
                ",",
            )

ParametrizedAttribute dataclass

Bases: Attribute

An attribute parametrized by other attributes.

Source code in xdsl/ir/core.py
427
428
429
430
431
432
433
434
435
436
437
438
439
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
@dataclass(frozen=True, init=False)
class ParametrizedAttribute(Attribute):
    """An attribute parametrized by other attributes."""

    def __init__(self, *parameters: Any):
        irdl_def = self.get_irdl_definition()
        for (f, d), param in zip(irdl_def.parameters, parameters, strict=True):
            if d.converter is not None:
                param = d.converter(param)
            object.__setattr__(self, f, param)
        super().__init__()

    @property
    def parameters(self) -> tuple[Attribute, ...]:
        return (
            *(
                self.__getattribute__(field)
                for field, _ in self.get_irdl_definition().parameters
            ),
        )

    @classmethod
    def new(cls: type[Self], params: Sequence[Attribute]) -> Self:
        """
        Create a new `ParametrizedAttribute` given its parameters.

        This function should be preferred over `__init__` when instantiating
        attributes in a generic way (i.e., without knowing their concrete type
        statically).
        """
        # Create the new attribute object, without calling its __init__.
        # We do this to allow users to redefine their own __init__.
        attr = cls.__new__(cls)

        # Set the parameters based on the definition
        for (f, _), param in zip(
            cls.get_irdl_definition().parameters, params, strict=True
        ):
            object.__setattr__(attr, f, param)
        attr.__post_init__()

        return attr

    @classmethod
    def parse_parameters(cls, parser: AttrParser) -> Sequence[Attribute]:
        """Parse the attribute parameters."""
        return parser.parse_paramattr_parameters()

    def print_parameters(self, printer: Printer) -> None:
        """Print the attribute parameters."""
        printer.print_paramattr_parameters(self.parameters)

    @classmethod
    def get_irdl_definition(cls) -> ParamAttrDef:
        """Get the IRDL attribute definition."""
        ...

    def _verify(self):
        # Verifier generated by irdl_attr_def
        t: type[ParametrizedAttribute] = type(self)
        attr_def = t.get_irdl_definition()
        attr_def.verify(self)
        super()._verify()

parameters: tuple[Attribute, ...] property

__init__(*parameters: Any)

Source code in xdsl/ir/core.py
431
432
433
434
435
436
437
def __init__(self, *parameters: Any):
    irdl_def = self.get_irdl_definition()
    for (f, d), param in zip(irdl_def.parameters, parameters, strict=True):
        if d.converter is not None:
            param = d.converter(param)
        object.__setattr__(self, f, param)
    super().__init__()

new(params: Sequence[Attribute]) -> Self classmethod

Create a new ParametrizedAttribute given its parameters.

This function should be preferred over __init__ when instantiating attributes in a generic way (i.e., without knowing their concrete type statically).

Source code in xdsl/ir/core.py
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
@classmethod
def new(cls: type[Self], params: Sequence[Attribute]) -> Self:
    """
    Create a new `ParametrizedAttribute` given its parameters.

    This function should be preferred over `__init__` when instantiating
    attributes in a generic way (i.e., without knowing their concrete type
    statically).
    """
    # Create the new attribute object, without calling its __init__.
    # We do this to allow users to redefine their own __init__.
    attr = cls.__new__(cls)

    # Set the parameters based on the definition
    for (f, _), param in zip(
        cls.get_irdl_definition().parameters, params, strict=True
    ):
        object.__setattr__(attr, f, param)
    attr.__post_init__()

    return attr

parse_parameters(parser: AttrParser) -> Sequence[Attribute] classmethod

Parse the attribute parameters.

Source code in xdsl/ir/core.py
470
471
472
473
@classmethod
def parse_parameters(cls, parser: AttrParser) -> Sequence[Attribute]:
    """Parse the attribute parameters."""
    return parser.parse_paramattr_parameters()

print_parameters(printer: Printer) -> None

Print the attribute parameters.

Source code in xdsl/ir/core.py
475
476
477
def print_parameters(self, printer: Printer) -> None:
    """Print the attribute parameters."""
    printer.print_paramattr_parameters(self.parameters)

get_irdl_definition() -> ParamAttrDef classmethod

Get the IRDL attribute definition.

Source code in xdsl/ir/core.py
479
480
481
482
@classmethod
def get_irdl_definition(cls) -> ParamAttrDef:
    """Get the IRDL attribute definition."""
    ...

TypedAttribute dataclass

Bases: ParametrizedAttribute, ABC

An attribute with a type.

Source code in xdsl/ir/core.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
class TypedAttribute(ParametrizedAttribute, ABC):
    """
    An attribute with a type.
    """

    @classmethod
    def get_type_index(cls) -> int: ...

    def get_type(self) -> Attribute:
        return self.parameters[self.get_type_index()]

    @staticmethod
    def parse_with_type(
        parser: AttrParser,
        type: Attribute,
    ) -> TypedAttribute:
        """
        Parse the attribute with the given type.
        """
        ...

    @abstractmethod
    def print_without_type(self, printer: Printer): ...

get_type_index() -> int classmethod

Source code in xdsl/ir/core.py
497
498
@classmethod
def get_type_index(cls) -> int: ...

get_type() -> Attribute

Source code in xdsl/ir/core.py
500
501
def get_type(self) -> Attribute:
    return self.parameters[self.get_type_index()]

parse_with_type(parser: AttrParser, type: Attribute) -> TypedAttribute staticmethod

Parse the attribute with the given type.

Source code in xdsl/ir/core.py
503
504
505
506
507
508
509
510
511
@staticmethod
def parse_with_type(
    parser: AttrParser,
    type: Attribute,
) -> TypedAttribute:
    """
    Parse the attribute with the given type.
    """
    ...

print_without_type(printer: Printer) abstractmethod

Source code in xdsl/ir/core.py
513
514
@abstractmethod
def print_without_type(self, printer: Printer): ...

Use dataclass

The use of a SSA value.

Source code in xdsl/ir/core.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
@dataclass(repr=False)
class Use:
    """The use of a SSA value."""

    _operation: Operation
    """The operation using the value."""

    _index: int
    """The index of the operand using the value in the operation."""

    _prev_use: Use | None = None
    """The previous use of the value in the use list."""

    _next_use: Use | None = None
    """The next use of the value in the use list."""

    @property
    def operation(self) -> Operation:
        """The operation using the value."""
        return self._operation

    @property
    def index(self) -> int:
        """The index of the operand using the value in the operation."""
        return self._index

    def __repr__(self) -> str:
        return (
            f"<Use {id(self)}(_operation={_short_repr(self.operation)}, "
            f"_index={self.index}, _prev_use={_short_repr(self._prev_use)}, "
            f"_next_use={_short_repr(self._next_use)})>"
        )

    def __hash__(self) -> int:
        return id(self)

    def __eq__(self, other: object) -> bool:
        return self is other

operation: Operation property

The operation using the value.

index: int property

The index of the operand using the value in the operation.

__init__(_operation: Operation, _index: int, _prev_use: Use | None = None, _next_use: Use | None = None) -> None

__repr__() -> str

Source code in xdsl/ir/core.py
543
544
545
546
547
548
def __repr__(self) -> str:
    return (
        f"<Use {id(self)}(_operation={_short_repr(self.operation)}, "
        f"_index={self.index}, _prev_use={_short_repr(self._prev_use)}, "
        f"_next_use={_short_repr(self._next_use)})>"
    )

__hash__() -> int

Source code in xdsl/ir/core.py
550
551
def __hash__(self) -> int:
    return id(self)

__eq__(other: object) -> bool

Source code in xdsl/ir/core.py
553
554
def __eq__(self, other: object) -> bool:
    return self is other

IRUses dataclass

Bases: Iterable[Use]

Multi-pass iterable of the uses of an IR value (SSAValue or Block).

Source code in xdsl/ir/core.py
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
@dataclass
class IRUses(Iterable[Use]):
    """
    Multi-pass iterable of the uses of an IR value (SSAValue or Block).
    """

    ir: IRWithUses

    def __iter__(self):
        use = self.ir.first_use
        while use is not None:
            yield use
            use = use._next_use  # pyright: ignore[reportPrivateUsage]

    def __bool__(self) -> bool:
        """Returns `True` if there are operations in this block."""
        return self.ir.first_use is not None

    def get_length(self) -> int:
        """
        Returns the number of uses.
        We do not expose it as `__len__` as it is expensive to compute `O(len)`.
        """
        use = self.ir.first_use
        count = 0
        while use is not None:
            count += 1
            use = use._next_use  # pyright: ignore[reportPrivateUsage]
        return count

ir: IRWithUses instance-attribute

__init__(ir: IRWithUses) -> None

__iter__()

Source code in xdsl/ir/core.py
565
566
567
568
569
def __iter__(self):
    use = self.ir.first_use
    while use is not None:
        yield use
        use = use._next_use  # pyright: ignore[reportPrivateUsage]

__bool__() -> bool

Returns True if there are operations in this block.

Source code in xdsl/ir/core.py
571
572
573
def __bool__(self) -> bool:
    """Returns `True` if there are operations in this block."""
    return self.ir.first_use is not None

get_length() -> int

Returns the number of uses. We do not expose it as __len__ as it is expensive to compute O(len).

Source code in xdsl/ir/core.py
575
576
577
578
579
580
581
582
583
584
585
def get_length(self) -> int:
    """
    Returns the number of uses.
    We do not expose it as `__len__` as it is expensive to compute `O(len)`.
    """
    use = self.ir.first_use
    count = 0
    while use is not None:
        count += 1
        use = use._next_use  # pyright: ignore[reportPrivateUsage]
    return count

IRWithUses dataclass

Bases: ABC

IRNode which stores a list of its uses.

Source code in xdsl/ir/core.py
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
@dataclass(eq=False)
class IRWithUses(ABC):
    """IRNode which stores a list of its uses."""

    first_use: Use | None = field(init=False, default=None, repr=False)
    """The first use of the value in the use list."""

    @property
    def uses(self) -> IRUses:
        """Returns an iterable of all uses of the value."""
        return IRUses(self)

    def add_use(self, use: Use):
        """Add a new use of the value."""
        first_use = self.first_use
        use._next_use = first_use  # pyright: ignore[reportPrivateUsage]
        use._prev_use = None  # pyright: ignore[reportPrivateUsage]
        if first_use is not None:
            first_use._prev_use = use  # pyright: ignore[reportPrivateUsage]
        self.first_use = use

    def remove_use(self, use: Use):
        """Remove a use of the value."""
        prev_use = use._prev_use  # pyright: ignore[reportPrivateUsage]
        next_use = use._next_use  # pyright: ignore[reportPrivateUsage]
        if prev_use is not None:
            prev_use._next_use = next_use  # pyright: ignore[reportPrivateUsage]
        if next_use is not None:
            next_use._prev_use = prev_use  # pyright: ignore[reportPrivateUsage]

        if prev_use is None:
            self.first_use = next_use

    def has_one_use(self) -> bool:
        """Returns true if the value has exactly one use."""
        first_use = self.first_use
        return (
            first_use is not None and first_use._next_use is None  # pyright: ignore[reportPrivateUsage]
        )

    def has_more_than_one_use(self) -> bool:
        """Returns true if the value has more than one use."""
        if (first_use := self.first_use) is None:
            return False
        return first_use._next_use is not None  # pyright: ignore[reportPrivateUsage]

    def get_unique_use(self) -> Use | None:
        """
        Returns the single use of the value, or None if there are no uses or
        more than one use.
        """
        if (first_use := self.first_use) is None:
            return None
        if first_use._next_use is not None:  # pyright: ignore[reportPrivateUsage]
            return None
        return first_use

    def get_user_of_unique_use(self) -> Operation | None:
        """
        Returns the user of the single use of the value.
        If there are no uses or more than one use, returns None.
        """
        if (use := self.get_unique_use()) is not None:
            return use.operation
        return None

first_use: Use | None = field(init=False, default=None, repr=False) class-attribute instance-attribute

The first use of the value in the use list.

uses: IRUses property

Returns an iterable of all uses of the value.

__init__() -> None

add_use(use: Use)

Add a new use of the value.

Source code in xdsl/ir/core.py
600
601
602
603
604
605
606
607
def add_use(self, use: Use):
    """Add a new use of the value."""
    first_use = self.first_use
    use._next_use = first_use  # pyright: ignore[reportPrivateUsage]
    use._prev_use = None  # pyright: ignore[reportPrivateUsage]
    if first_use is not None:
        first_use._prev_use = use  # pyright: ignore[reportPrivateUsage]
    self.first_use = use

remove_use(use: Use)

Remove a use of the value.

Source code in xdsl/ir/core.py
609
610
611
612
613
614
615
616
617
618
619
def remove_use(self, use: Use):
    """Remove a use of the value."""
    prev_use = use._prev_use  # pyright: ignore[reportPrivateUsage]
    next_use = use._next_use  # pyright: ignore[reportPrivateUsage]
    if prev_use is not None:
        prev_use._next_use = next_use  # pyright: ignore[reportPrivateUsage]
    if next_use is not None:
        next_use._prev_use = prev_use  # pyright: ignore[reportPrivateUsage]

    if prev_use is None:
        self.first_use = next_use

has_one_use() -> bool

Returns true if the value has exactly one use.

Source code in xdsl/ir/core.py
621
622
623
624
625
626
def has_one_use(self) -> bool:
    """Returns true if the value has exactly one use."""
    first_use = self.first_use
    return (
        first_use is not None and first_use._next_use is None  # pyright: ignore[reportPrivateUsage]
    )

has_more_than_one_use() -> bool

Returns true if the value has more than one use.

Source code in xdsl/ir/core.py
628
629
630
631
632
def has_more_than_one_use(self) -> bool:
    """Returns true if the value has more than one use."""
    if (first_use := self.first_use) is None:
        return False
    return first_use._next_use is not None  # pyright: ignore[reportPrivateUsage]

get_unique_use() -> Use | None

Returns the single use of the value, or None if there are no uses or more than one use.

Source code in xdsl/ir/core.py
634
635
636
637
638
639
640
641
642
643
def get_unique_use(self) -> Use | None:
    """
    Returns the single use of the value, or None if there are no uses or
    more than one use.
    """
    if (first_use := self.first_use) is None:
        return None
    if first_use._next_use is not None:  # pyright: ignore[reportPrivateUsage]
        return None
    return first_use

get_user_of_unique_use() -> Operation | None

Returns the user of the single use of the value. If there are no uses or more than one use, returns None.

Source code in xdsl/ir/core.py
645
646
647
648
649
650
651
652
def get_user_of_unique_use(self) -> Operation | None:
    """
    Returns the user of the single use of the value.
    If there are no uses or more than one use, returns None.
    """
    if (use := self.get_unique_use()) is not None:
        return use.operation
    return None

IRWithName dataclass

Bases: ABC

Source code in xdsl/ir/core.py
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
@dataclass(eq=False)
class IRWithName(ABC):
    _name: str | None = field(init=False, default=None)

    @property
    def name_hint(self) -> str | None:
        return self._name

    @name_hint.setter
    def name_hint(self, name: str | None):
        self._name = self.extract_valid_name(name)

    @classmethod
    def is_valid_name(cls, name: str | None):
        """
        Returns `True` if `name` is None or a valid name for an identifier.
        """
        return name is None or _VALUE_NAME_PATTERN.fullmatch(name)

    @overload
    @classmethod
    def extract_valid_name(cls, name: str) -> str: ...

    @overload
    @classmethod
    def extract_valid_name(cls, name: None) -> None: ...

    @classmethod
    def extract_valid_name(cls, name: str | None) -> str | None:
        """
        If the name is valid, extracts the name before an optional `_\\d+` suffix.
        Raises ValueError otherwise.
        """
        if name is None:
            return
        if _VALUE_NAME_PATTERN.fullmatch(name) is None:
            raise ValueError(
                f"Invalid {cls.__name__} name format `{name}`.",
                r"Make sure names contain only characters of [A-Za-z0-9_$.-] and don't start with a number.",
            )

        if match := _VALUE_NAME_SUFFIX_PATTERN.search(name):
            # Remove `_` followed by numbers at the end of the name
            return name[: match.start()]

        return name

name_hint: str | None property writable

__init__() -> None

is_valid_name(name: str | None) classmethod

Returns True if name is None or a valid name for an identifier.

Source code in xdsl/ir/core.py
674
675
676
677
678
679
@classmethod
def is_valid_name(cls, name: str | None):
    """
    Returns `True` if `name` is None or a valid name for an identifier.
    """
    return name is None or _VALUE_NAME_PATTERN.fullmatch(name)

extract_valid_name(name: str | None) -> str | None classmethod

extract_valid_name(name: str) -> str
extract_valid_name(name: None) -> None

If the name is valid, extracts the name before an optional _\d+ suffix. Raises ValueError otherwise.

Source code in xdsl/ir/core.py
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
@classmethod
def extract_valid_name(cls, name: str | None) -> str | None:
    """
    If the name is valid, extracts the name before an optional `_\\d+` suffix.
    Raises ValueError otherwise.
    """
    if name is None:
        return
    if _VALUE_NAME_PATTERN.fullmatch(name) is None:
        raise ValueError(
            f"Invalid {cls.__name__} name format `{name}`.",
            r"Make sure names contain only characters of [A-Za-z0-9_$.-] and don't start with a number.",
        )

    if match := _VALUE_NAME_SUFFIX_PATTERN.search(name):
        # Remove `_` followed by numbers at the end of the name
        return name[: match.start()]

    return name

SSAValue dataclass

Bases: IRWithUses, IRWithName, ABC, Generic[AttributeCovT]

A reference to an SSA variable. An SSA variable is either an operation result, or a basic block argument.

Source code in xdsl/ir/core.py
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
@dataclass(eq=False)
class SSAValue(IRWithUses, IRWithName, ABC, Generic[AttributeCovT]):
    """
    A reference to an SSA variable.
    An SSA variable is either an operation result, or a basic block argument.
    """

    _type: AttributeCovT
    """Each SSA variable is associated to a type."""

    @property
    def type(self) -> AttributeCovT:
        return self._type

    @property
    @abstractmethod
    def owner(self) -> Operation | Block:
        """
        An SSA variable is either an operation result, or a basic block argument.
        This property returns the Operation or Block that currently defines a specific value.
        """
        pass

    @staticmethod
    def get(
        arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute
    ) -> SSAValue[AttributeInvT]:
        """
        Get a new SSAValue from either a SSAValue, or an operation with a single result.
        Checks that the resulting SSAValue is of the supplied type, if provided.
        """
        from xdsl.utils.hints import isa

        match arg:
            case SSAValue():
                if type is Attribute or isa(arg.type, type):
                    return cast(SSAValue[AttributeInvT], arg)
                raise ValueError(
                    f"SSAValue.get: Expected {type} but got SSAValue with type {arg.type}."
                )
            case Operation():
                if len(arg.results) == 1:
                    return SSAValue.get(arg.results[0], type=type)
                raise ValueError(
                    "SSAValue.get: expected operation with a single result."
                )

    @deprecated("Please use `self.replace_all_uses_with(value)`")
    def replace_by(self, value: SSAValue) -> None:
        return self.replace_all_uses_with(value)

    def replace_all_uses_with(self, value: SSAValue) -> None:
        """Replace the value by another value in all its uses."""
        for use in tuple(self.uses):
            use.operation.operands[use.index] = value
        # carry over name if possible
        if value.name_hint is None:
            value.name_hint = self.name_hint
        assert self.first_use is None, "unexpected error in xdsl"

    @deprecated(
        "Please use `self.replace_uses_with_if(value, lambda use: True)` (or `rewriter.replace_uses_if` if applicable)."
    )
    def replace_by_if(self, value: SSAValue, test: Callable[[Use], bool]) -> None:
        return self.replace_uses_with_if(value, test)

    def replace_uses_with_if(self, value: SSAValue, predicate: Callable[[Use], bool]):
        """
        Replace the value by another value in all its uses that pass the given test
        function.
        """
        for use in tuple(self.uses):
            if predicate(use):
                use.operation.operands[use.index] = value
        # carry over name if possible
        if value.name_hint is None:
            value.name_hint = self.name_hint

    def erase(self, safe_erase: bool = True) -> None:
        """
        Erase the value.
        If safe_erase is True, then check that no operations use the value anymore.
        If safe_erase is False, then replace its uses by an ErasedSSAValue.
        """
        if safe_erase and self.first_use is not None:
            raise ValueError(
                "Attempting to delete SSA value that still has uses of result "
                f"of operation:\n{self.owner}"
            )
        self.replace_all_uses_with(ErasedSSAValue(self.type, self))

    def __hash__(self):
        """
        Make SSAValue hashable. Two SSA Values are never the same, therefore
        the use of `id` is allowed here.
        """
        return id(self)

    def __eq__(self, other: object) -> bool:
        return self is other

type: AttributeCovT property

owner: Operation | Block abstractmethod property

An SSA variable is either an operation result, or a basic block argument. This property returns the Operation or Block that currently defines a specific value.

__init__(_type: AttributeCovT) -> None

get(arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute) -> SSAValue[AttributeInvT] staticmethod

Get a new SSAValue from either a SSAValue, or an operation with a single result. Checks that the resulting SSAValue is of the supplied type, if provided.

Source code in xdsl/ir/core.py
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
@staticmethod
def get(
    arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute
) -> SSAValue[AttributeInvT]:
    """
    Get a new SSAValue from either a SSAValue, or an operation with a single result.
    Checks that the resulting SSAValue is of the supplied type, if provided.
    """
    from xdsl.utils.hints import isa

    match arg:
        case SSAValue():
            if type is Attribute or isa(arg.type, type):
                return cast(SSAValue[AttributeInvT], arg)
            raise ValueError(
                f"SSAValue.get: Expected {type} but got SSAValue with type {arg.type}."
            )
        case Operation():
            if len(arg.results) == 1:
                return SSAValue.get(arg.results[0], type=type)
            raise ValueError(
                "SSAValue.get: expected operation with a single result."
            )

replace_by(value: SSAValue) -> None

Source code in xdsl/ir/core.py
757
758
759
@deprecated("Please use `self.replace_all_uses_with(value)`")
def replace_by(self, value: SSAValue) -> None:
    return self.replace_all_uses_with(value)

replace_all_uses_with(value: SSAValue) -> None

Replace the value by another value in all its uses.

Source code in xdsl/ir/core.py
761
762
763
764
765
766
767
768
def replace_all_uses_with(self, value: SSAValue) -> None:
    """Replace the value by another value in all its uses."""
    for use in tuple(self.uses):
        use.operation.operands[use.index] = value
    # carry over name if possible
    if value.name_hint is None:
        value.name_hint = self.name_hint
    assert self.first_use is None, "unexpected error in xdsl"

replace_by_if(value: SSAValue, test: Callable[[Use], bool]) -> None

Source code in xdsl/ir/core.py
770
771
772
773
774
@deprecated(
    "Please use `self.replace_uses_with_if(value, lambda use: True)` (or `rewriter.replace_uses_if` if applicable)."
)
def replace_by_if(self, value: SSAValue, test: Callable[[Use], bool]) -> None:
    return self.replace_uses_with_if(value, test)

replace_uses_with_if(value: SSAValue, predicate: Callable[[Use], bool])

Replace the value by another value in all its uses that pass the given test function.

Source code in xdsl/ir/core.py
776
777
778
779
780
781
782
783
784
785
786
def replace_uses_with_if(self, value: SSAValue, predicate: Callable[[Use], bool]):
    """
    Replace the value by another value in all its uses that pass the given test
    function.
    """
    for use in tuple(self.uses):
        if predicate(use):
            use.operation.operands[use.index] = value
    # carry over name if possible
    if value.name_hint is None:
        value.name_hint = self.name_hint

erase(safe_erase: bool = True) -> None

Erase the value. If safe_erase is True, then check that no operations use the value anymore. If safe_erase is False, then replace its uses by an ErasedSSAValue.

Source code in xdsl/ir/core.py
788
789
790
791
792
793
794
795
796
797
798
799
def erase(self, safe_erase: bool = True) -> None:
    """
    Erase the value.
    If safe_erase is True, then check that no operations use the value anymore.
    If safe_erase is False, then replace its uses by an ErasedSSAValue.
    """
    if safe_erase and self.first_use is not None:
        raise ValueError(
            "Attempting to delete SSA value that still has uses of result "
            f"of operation:\n{self.owner}"
        )
    self.replace_all_uses_with(ErasedSSAValue(self.type, self))

__hash__()

Make SSAValue hashable. Two SSA Values are never the same, therefore the use of id is allowed here.

Source code in xdsl/ir/core.py
801
802
803
804
805
806
def __hash__(self):
    """
    Make SSAValue hashable. Two SSA Values are never the same, therefore
    the use of `id` is allowed here.
    """
    return id(self)

__eq__(other: object) -> bool

Source code in xdsl/ir/core.py
808
809
def __eq__(self, other: object) -> bool:
    return self is other

OpResult dataclass

Bases: SSAValue[AttributeCovT], Generic[AttributeCovT]

A reference to an SSA variable defined by an operation result.

Source code in xdsl/ir/core.py
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
@dataclass(eq=False)
class OpResult(SSAValue[AttributeCovT], Generic[AttributeCovT]):
    """A reference to an SSA variable defined by an operation result."""

    op: Operation
    """The operation defining the variable."""

    index: int
    """The index of the result in the defining operation."""

    @property
    def owner(self) -> Operation:
        return self.op

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__}[{self.type}]"
            f" name_hint: {self.name_hint},"
            f" index: {self.index},"
            f" operation: {self.op.name},"
            f" uses: {self.uses.get_length()}>"
        )

op: Operation instance-attribute

The operation defining the variable.

index: int instance-attribute

The index of the result in the defining operation.

owner: Operation property

__init__(_type: AttributeCovT, op: Operation, index: int) -> None

__repr__() -> str

Source code in xdsl/ir/core.py
826
827
828
829
830
831
832
833
def __repr__(self) -> str:
    return (
        f"<{self.__class__.__name__}[{self.type}]"
        f" name_hint: {self.name_hint},"
        f" index: {self.index},"
        f" operation: {self.op.name},"
        f" uses: {self.uses.get_length()}>"
    )

BlockArgument dataclass

Bases: SSAValue[AttributeCovT], Generic[AttributeCovT]

A reference to an SSA variable defined by a basic block argument.

Source code in xdsl/ir/core.py
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
@dataclass(eq=False)
class BlockArgument(SSAValue[AttributeCovT], Generic[AttributeCovT]):
    """A reference to an SSA variable defined by a basic block argument."""

    block: Block
    """The block defining the variable."""

    index: int
    """The index of the variable in the block arguments."""

    @property
    def owner(self) -> Block:
        return self.block

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__}[{self.type}]"
            f" name_hint: {self.name_hint},"
            f" index: {self.index},"
            f" uses: {self.uses.get_length()}>"
        )

block: Block instance-attribute

The block defining the variable.

index: int instance-attribute

The index of the variable in the block arguments.

owner: Block property

__init__(_type: AttributeCovT, block: Block, index: int) -> None

__repr__() -> str

Source code in xdsl/ir/core.py
850
851
852
853
854
855
856
def __repr__(self) -> str:
    return (
        f"<{self.__class__.__name__}[{self.type}]"
        f" name_hint: {self.name_hint},"
        f" index: {self.index},"
        f" uses: {self.uses.get_length()}>"
    )

ErasedSSAValue dataclass

Bases: SSAValue

An erased SSA variable. This is used during transformations when a SSA variable is destroyed but still used.

Source code in xdsl/ir/core.py
859
860
861
862
863
864
865
866
867
868
869
870
@dataclass(eq=False)
class ErasedSSAValue(SSAValue):
    """
    An erased SSA variable.
    This is used during transformations when a SSA variable is destroyed but still used.
    """

    old_value: SSAValue

    @property
    def owner(self) -> Operation | Block:
        return self.old_value.owner

old_value: SSAValue instance-attribute

owner: Operation | Block property

__init__(_type: AttributeCovT, old_value: SSAValue) -> None

SSAValues

Bases: tuple[SSAValueCovT, ...], Generic[SSAValueCovT]

A helper data structure for a sequence of SSAValues.

Source code in xdsl/ir/core.py
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
class SSAValues(tuple[SSAValueCovT, ...], Generic[SSAValueCovT]):
    """
    A helper data structure for a sequence of SSAValues.
    """

    @property
    def types(self: tuple[SSAValue[AttributeInvT], ...]) -> tuple[AttributeInvT, ...]:
        return tuple(o.type for o in self)

    @overload
    def __getitem__(self, idx: int) -> SSAValueCovT: ...

    @overload
    def __getitem__(self, idx: slice) -> SSAValues[SSAValueCovT]: ...

    def __getitem__(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, idx: int | slice
    ) -> SSAValueCovT | SSAValues[SSAValueCovT]:
        if isinstance(idx, int):
            return super().__getitem__(idx)
        else:
            return SSAValues(super().__getitem__(idx))

types: tuple[AttributeInvT, ...] property

__getitem__(idx: int | slice) -> SSAValueCovT | SSAValues[SSAValueCovT]

__getitem__(idx: int) -> SSAValueCovT
__getitem__(idx: slice) -> SSAValues[SSAValueCovT]
Source code in xdsl/ir/core.py
933
934
935
936
937
938
939
def __getitem__(  # pyright: ignore[reportIncompatibleMethodOverride]
    self, idx: int | slice
) -> SSAValueCovT | SSAValues[SSAValueCovT]:
    if isinstance(idx, int):
        return super().__getitem__(idx)
    else:
        return SSAValues(super().__getitem__(idx))

OpOperands dataclass

Bases: Sequence[SSAValue]

A view of the operand list of an operation. Any modification to the view is reflected on the operation.

Source code in xdsl/ir/core.py
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
@dataclass
class OpOperands(Sequence[SSAValue]):
    """
    A view of the operand list of an operation.
    Any modification to the view is reflected on the operation.
    """

    _op: Operation
    """The operation owning the operands."""

    @overload
    def __getitem__(self, idx: int) -> SSAValue: ...

    @overload
    def __getitem__(self, idx: slice) -> Sequence[SSAValue]: ...

    def __getitem__(self, idx: int | slice) -> SSAValue | Sequence[SSAValue]:
        return self._op._operands[idx]  # pyright: ignore[reportPrivateUsage]

    def __setitem__(self, idx: int, operand: SSAValue) -> None:
        operands = self._op._operands  # pyright: ignore[reportPrivateUsage]
        operand_uses = self._op._operand_uses  # pyright: ignore[reportPrivateUsage]
        operands[idx].remove_use(operand_uses[idx])
        operand.add_use(operand_uses[idx])
        new_operands = SSAValues((*operands[:idx], operand, *operands[idx + 1 :]))
        self._op._operands = new_operands  # pyright: ignore[reportPrivateUsage]

    def __iter__(self) -> Iterator[SSAValue]:
        return iter(self._op._operands)  # pyright: ignore[reportPrivateUsage]

    def __len__(self) -> int:
        return len(self._op._operands)  # pyright: ignore[reportPrivateUsage]

    def __eq__(self, other: object):
        if not isinstance(other, OpOperands):
            return False
        return (
            self._op._operands  # pyright: ignore[reportPrivateUsage]
            == other._op._operands  # pyright: ignore[reportPrivateUsage]
        )

    def __hash__(self):
        return hash(self._op._operands)  # pyright: ignore[reportPrivateUsage]

__init__(_op: Operation) -> None

__getitem__(idx: int | slice) -> SSAValue | Sequence[SSAValue]

__getitem__(idx: int) -> SSAValue
__getitem__(idx: slice) -> Sequence[SSAValue]
Source code in xdsl/ir/core.py
958
959
def __getitem__(self, idx: int | slice) -> SSAValue | Sequence[SSAValue]:
    return self._op._operands[idx]  # pyright: ignore[reportPrivateUsage]

__setitem__(idx: int, operand: SSAValue) -> None

Source code in xdsl/ir/core.py
961
962
963
964
965
966
967
def __setitem__(self, idx: int, operand: SSAValue) -> None:
    operands = self._op._operands  # pyright: ignore[reportPrivateUsage]
    operand_uses = self._op._operand_uses  # pyright: ignore[reportPrivateUsage]
    operands[idx].remove_use(operand_uses[idx])
    operand.add_use(operand_uses[idx])
    new_operands = SSAValues((*operands[:idx], operand, *operands[idx + 1 :]))
    self._op._operands = new_operands  # pyright: ignore[reportPrivateUsage]

__iter__() -> Iterator[SSAValue]

Source code in xdsl/ir/core.py
969
970
def __iter__(self) -> Iterator[SSAValue]:
    return iter(self._op._operands)  # pyright: ignore[reportPrivateUsage]

__len__() -> int

Source code in xdsl/ir/core.py
972
973
def __len__(self) -> int:
    return len(self._op._operands)  # pyright: ignore[reportPrivateUsage]

__eq__(other: object)

Source code in xdsl/ir/core.py
975
976
977
978
979
980
981
def __eq__(self, other: object):
    if not isinstance(other, OpOperands):
        return False
    return (
        self._op._operands  # pyright: ignore[reportPrivateUsage]
        == other._op._operands  # pyright: ignore[reportPrivateUsage]
    )

__hash__()

Source code in xdsl/ir/core.py
983
984
def __hash__(self):
    return hash(self._op._operands)  # pyright: ignore[reportPrivateUsage]

OpTraits

Bases: Iterable[OpTrait]

An operation's traits. Some operations have mutually recursive traits, such as one is always the parent operation of the other. For this case, the operation's traits can be declared lazily and resolved only at the first use.

Source code in xdsl/ir/core.py
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
class OpTraits(Iterable[OpTrait]):
    """
    An operation's traits.
    Some operations have mutually recursive traits, such as one is always the parent
    operation of the other.
    For this case, the operation's traits can be declared lazily and resolved only at
    the first use.
    """

    gen_traits: Callable[[], tuple[OpTrait, ...]]
    """
    Factory method that lazily populates the traits on first use.
    """
    _traits: frozenset[OpTrait] | None
    """
    The traits of this operation, can be updated via `add_trait`.
    """

    def __init__(self, gen_traits: Callable[[], tuple[OpTrait, ...]]) -> None:
        self.gen_traits = gen_traits
        self._traits = None

    @property
    def traits(self) -> frozenset[OpTrait]:
        """Returns a copy of this instance's traits."""
        if self._traits is None:
            self._traits = frozenset(self.gen_traits())
        return self._traits

    def add_trait(self, trait: OpTrait):
        """Adds a trait to the class."""
        self._traits = self.traits.union((trait,))

    def __iter__(self) -> Iterator[OpTrait]:
        return iter(self.traits)

    def __eq__(self, value: object, /) -> bool:
        return isinstance(value, OpTraits) and self.traits == value.traits

gen_traits: Callable[[], tuple[OpTrait, ...]] = gen_traits instance-attribute

Factory method that lazily populates the traits on first use.

traits: frozenset[OpTrait] property

Returns a copy of this instance's traits.

__init__(gen_traits: Callable[[], tuple[OpTrait, ...]]) -> None

Source code in xdsl/ir/core.py
1005
1006
1007
def __init__(self, gen_traits: Callable[[], tuple[OpTrait, ...]]) -> None:
    self.gen_traits = gen_traits
    self._traits = None

add_trait(trait: OpTrait)

Adds a trait to the class.

Source code in xdsl/ir/core.py
1016
1017
1018
def add_trait(self, trait: OpTrait):
    """Adds a trait to the class."""
    self._traits = self.traits.union((trait,))

__iter__() -> Iterator[OpTrait]

Source code in xdsl/ir/core.py
1020
1021
def __iter__(self) -> Iterator[OpTrait]:
    return iter(self.traits)

__eq__(value: object) -> bool

Source code in xdsl/ir/core.py
1023
1024
def __eq__(self, value: object, /) -> bool:
    return isinstance(value, OpTraits) and self.traits == value.traits

Operation dataclass

Bases: _IRNode

A generic operation. Operation definitions inherit this class.

Source code in xdsl/ir/core.py
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
@dataclass(eq=False, unsafe_hash=False, repr=False)
class Operation(_IRNode):
    """A generic operation. Operation definitions inherit this class."""

    name: ClassVar[str]
    """The operation name. Should be a static member of the class"""

    _operands: SSAValues = field(default=SSAValues())
    """The operation operands."""

    _operand_uses: tuple[Use, ...] = field(default=())
    """
    The uses for each operand.
    They are stored separately from the operands to allow for more efficient
    access to the operand SSAValue.
    """

    results: SSAValues[OpResult] = field(default=SSAValues())
    """The results created by the operation."""

    _successors: tuple[Block, ...] = field(default=())
    """
    The basic blocks that the operation may give control to.
    This list should be empty for non-terminator operations.
    """

    _successor_uses: tuple[Use, ...] = field(default=())
    """
    The uses for each successor.
    They are stored separately from the successors to allow for more efficient
    access to the successor Block.
    """

    properties: dict[str, Attribute] = field(default_factory=dict[str, Attribute])
    """
    The properties attached to the operation.
    Properties are inherent to the definition of an operation's semantics, and
    thus cannot be discarded by transformations.
    """

    attributes: dict[str, Attribute] = field(default_factory=dict[str, Attribute])
    """The attributes attached to the operation."""

    regions: tuple[Region, ...] = field(default=())
    """Regions arguments of the operation."""

    parent: Block | None = field(default=None)
    """The block containing this operation."""

    _next_op: Operation | None = field(default=None)
    """Next operation in block containing this operation."""

    _prev_op: Operation | None = field(default=None)
    """Previous operation in block containing this operation."""

    traits: ClassVar[OpTraits]
    """
    Traits attached to an operation definition.
    This is a static field, and is made empty by default by PyRDL if not set
    by the operation definition.
    """

    @property
    def parent_node(self) -> IRNode | None:
        return self.parent

    @property
    def result_types(self) -> Sequence[Attribute]:
        return tuple(r.type for r in self.results)

    @property
    def operand_types(self) -> Sequence[Attribute]:
        return tuple(operand.type for operand in self.operands)

    def parent_op(self) -> Operation | None:
        if p := self.parent_region():
            return p.parent
        return None

    def parent_region(self) -> Region | None:
        if (p := self.parent_block()) is not None:
            return p.parent
        return None

    def parent_block(self) -> Block | None:
        return self.parent

    @property
    def next_op(self) -> Operation | None:
        """
        Next operation in block containing this operation.
        """
        return self._next_op

    def _insert_next_op(self, new_op: Operation) -> None:
        """
        Sets `next_op` on `self`, and `prev_op` on `self.next_op`.
        """

        if self._next_op is not None:
            # update next node
            self._next_op._prev_op = new_op

        # set next and previous on new node
        new_op._prev_op = self
        new_op._next_op = self._next_op

        # update self
        self._next_op = new_op

    @property
    def prev_op(self) -> Operation | None:
        """
        Previous operation in block containing this operation.
        """
        return self._prev_op

    def _insert_prev_op(self, new_op: Operation) -> None:
        """
        Sets `prev_op` on `self`, and `next_op` on `self.prev_op`.
        """

        if self._prev_op is not None:
            # update prev node
            self._prev_op._next_op = new_op

        # set next and previous on new node
        new_op._prev_op = self._prev_op
        new_op._next_op = self

        # update self
        self._prev_op = new_op

    @property
    def operands(self) -> OpOperands:
        return OpOperands(self)

    @operands.setter
    def operands(self, new: Sequence[SSAValue]):
        new = SSAValues(new)
        new_uses = tuple(Use(self, idx) for idx in range(len(new)))
        for operand, use in zip(self._operands, self._operand_uses):
            operand.remove_use(use)
        for operand, use in zip(new, new_uses):
            operand.add_use(use)
        self._operands = new
        self._operand_uses = new_uses

    @property
    def successors(self) -> OpSuccessors:
        return OpSuccessors(self)

    @successors.setter
    def successors(self, new: Sequence[Block]):
        new = tuple(new)
        new_uses = tuple(Use(self, idx) for idx in range(len(new)))
        for successor, use in zip(self._successors, self._successor_uses):
            successor.remove_use(use)
        for successor, use in zip(new, new_uses):
            successor.add_use(use)
        self._successors = new
        self._successor_uses = new_uses

    @property
    def successor_uses(self) -> Sequence[Use]:
        return self._successor_uses

    def __post_init__(self):
        assert self.name != ""
        assert isinstance(self.name, str)

    def __init__(
        self,
        *,
        operands: Sequence[SSAValue] = (),
        result_types: Sequence[Attribute] = (),
        properties: Mapping[str, Attribute] = {},
        attributes: Mapping[str, Attribute] = {},
        successors: Sequence[Block] = (),
        regions: Sequence[Region] = (),
    ) -> None:
        super().__init__()

        # This is assumed to exist by Operation.operand setter.
        self.operands = operands

        self.results = SSAValues(
            OpResult(result_type, self, idx)
            for (idx, result_type) in enumerate(result_types)
        )
        self.properties = dict(properties)
        self.attributes = dict(attributes)
        self.successors = list(successors)
        self.regions = ()
        for region in regions:
            self.add_region(region)

        self.__post_init__()

    @classmethod
    def create(
        cls: type[Self],
        *,
        operands: Sequence[SSAValue] = (),
        result_types: Sequence[Attribute] = (),
        properties: Mapping[str, Attribute] = {},
        attributes: Mapping[str, Attribute] = {},
        successors: Sequence[Block] = (),
        regions: Sequence[Region] = (),
    ) -> Self:
        op = cls.__new__(cls)
        Operation.__init__(
            op,
            operands=operands,
            result_types=result_types,
            properties=properties,
            attributes=attributes,
            successors=successors,
            regions=regions,
        )
        return op

    def add_region(self, region: Region) -> None:
        """Add an unattached region to the operation."""
        if region.parent:
            raise ValueError(
                "Cannot add region that is already attached on an operation."
            )
        self.regions += (region,)
        region.parent = self

    def get_region_index(self, region: Region) -> int:
        """Get the region position in the operation."""
        if region.parent is not self:
            raise ValueError("Region is not attached to the operation.")
        return next(
            idx for idx, curr_region in enumerate(self.regions) if curr_region is region
        )

    def detach_region(self, region: int | Region) -> Region:
        """
        Detach a region from the operation.
        Returns the detached region.
        """
        if isinstance(region, Region):
            region_idx = self.get_region_index(region)
        else:
            region_idx = region
            region = self.regions[region_idx]
        region.parent = None
        self.regions = self.regions[:region_idx] + self.regions[region_idx + 1 :]
        return region

    def drop_all_references(self) -> None:
        """
        Drop all references to other operations.
        This function is called prior to deleting an operation.
        """
        self.parent = None
        for operand, use in zip(self._operands, self._operand_uses):
            operand.remove_use(use)
        for region in self.regions:
            region.drop_all_references()

    def walk(
        self, *, reverse: bool = False, region_first: bool = False
    ) -> Iterator[Operation]:
        """
        Iterate all operations contained in the operation (including this one).
        If region_first is set, then the operation regions are iterated before the
        operation. If reverse is set, then the region, block, and operation lists are
        iterated in reverse order.
        """
        if not region_first:
            yield self
        for region in reversed(self.regions) if reverse else self.regions:
            yield from region.walk(reverse=reverse, region_first=region_first)
        if region_first:
            yield self

    def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
        """
        Iterate over all the blocks nested in the region.
        Iterate in reverse order if reverse is True.
        """
        for region in reversed(self.regions) if reverse else self.regions:
            for block in reversed(region.blocks) if reverse else region.blocks:
                yield from block.walk_blocks(reverse=reverse)

    def get_attr_or_prop(self, name: str) -> Attribute | None:
        """
        Get a named attribute or property.
        It first look into the property dictionary, then into the attribute dictionary.
        """
        if name in self.properties:
            return self.properties[name]
        if name in self.attributes:
            return self.attributes[name]
        return None

    def is_before_in_block(self, other_op: Operation) -> bool:
        """
        Return true if the current operation is located strictly before other_op.
        False otherwise.
        """
        if (
            parent_block := self.parent_block()
        ) is None or other_op.parent_block() is not parent_block:
            return False

        op = self.next_op
        while op is not None:
            if op is other_op:
                return True
            op = op.next_op
        return False

    def verify(self, verify_nested_ops: bool = True) -> None:
        for operand in self.operands:
            if isinstance(operand, ErasedSSAValue):
                raise ValueError("Erased SSA value is used by the operation")

        parent_block = self.parent
        parent_region = None if parent_block is None else parent_block.parent

        if self.successors:
            if parent_block is None or parent_region is None:
                raise VerifyException(
                    f"Operation {self.name} with block successors does not belong to a block or a region"
                )

            if parent_block.last_op is not self:
                raise VerifyException(
                    f"Operation {self.name} with block successors must terminate its parent block"
                )

            for succ in self.successors:
                if succ.parent != parent_block.parent:
                    raise VerifyException(
                        f"Operation {self.name} is branching to a block of a different region"
                    )

        if parent_block is not None and parent_region is not None:
            if parent_block.last_op == self:
                if len(parent_region.blocks) == 1:
                    if (
                        parent_op := parent_region.parent
                    ) is not None and not parent_op.has_trait(NoTerminator):
                        if not self.has_trait(IsTerminator):
                            raise VerifyException(
                                f"Operation {self.name} terminates block in "
                                "single-block region but is not a terminator"
                            )
                elif len(parent_region.blocks) > 1:
                    if not self.has_trait(IsTerminator):
                        raise VerifyException(
                            f"Operation {self.name} terminates block in multi-block "
                            "region but is not a terminator"
                        )

        if verify_nested_ops:
            for region in self.regions:
                region.verify()

        # Custom verifier
        try:
            self.verify_()
        except VerifyException as err:
            self.emit_error(
                f"Operation does not verify: {err}",
                err,
            )

    def verify_(self) -> None:
        pass

    _OperationType = TypeVar("_OperationType", bound="Operation")

    @classmethod
    def parse(cls, parser: Parser) -> Self:
        parser.raise_error(f"Operation {cls.name} does not have a custom format.")

    def print(self, printer: Printer):
        return printer.print_op_with_default_format(self)

    def clone_without_regions(
        self,
        value_mapper: dict[SSAValue, SSAValue] | None = None,
        block_mapper: dict[Block, Block] | None = None,
        *,
        clone_name_hints: bool = True,
        clone_operands: bool = True,
    ) -> Self:
        """Clone an operation, with empty regions instead."""
        if value_mapper is None:
            value_mapper = {}
        if block_mapper is None:
            block_mapper = {}
        if clone_operands:
            operands = tuple(
                value_mapper.get(operand, operand) for operand in self._operands
            )
        else:
            operands = ()
        result_types = self.result_types
        attributes = self.attributes.copy()
        properties = self.properties.copy()
        successors = [
            (block_mapper[successor] if successor in block_mapper else successor)
            for successor in self._successors
        ]
        regions = [Region() for _ in self.regions]
        cloned_op = self.create(
            operands=operands,
            result_types=result_types,
            attributes=attributes,
            properties=properties,
            successors=successors,
            regions=regions,
        )
        for self_result, cloned_result in zip(
            self.results, cloned_op.results, strict=True
        ):
            value_mapper[self_result] = cloned_result
            if clone_name_hints:
                cloned_result.name_hint = self_result.name_hint
        return cloned_op

    def clone(
        self,
        value_mapper: dict[SSAValue, SSAValue] | None = None,
        block_mapper: dict[Block, Block] | None = None,
        *,
        clone_name_hints: bool = True,
        clone_operands: bool = True,
    ) -> Self:
        """Clone an operation with all its regions and operations in them."""
        if value_mapper is None:
            value_mapper = {}
        if block_mapper is None:
            block_mapper = {}
        op = self.clone_without_regions(
            value_mapper,
            block_mapper,
            clone_name_hints=clone_name_hints,
            clone_operands=False,
        )
        for idx, region in enumerate(self.regions):
            region.clone_into(
                op.regions[idx],
                0,
                value_mapper,
                block_mapper,
                clone_name_hints=clone_name_hints,
                clone_operands=False,
            )
        if clone_operands:
            for old, new in zip(self.walk(), op.walk()):
                new.operands = tuple(
                    value_mapper.get(operand, operand) for operand in old.operands
                )
        return op

    @classmethod
    def has_trait(
        cls,
        trait: type[OpTrait] | OpTrait,
        *,
        value_if_unregistered: bool = True,
    ) -> bool:
        """
        Check if the operation implements a trait with the given parameters.
        If the operation is not registered, return value_if_unregisteed instead.
        """
        return cls.get_trait(trait) is not None

    @classmethod
    def get_trait(cls, trait: type[OpTraitInvT] | OpTraitInvT) -> OpTraitInvT | None:
        """
        Return a trait with the given type and parameters, if it exists.
        """
        if isinstance(trait, type):
            for t in cls.traits:
                if isinstance(t, cast(type[OpTraitInvT], trait)):
                    return t
        else:
            for t in cls.traits:
                if t == trait:
                    return cast(OpTraitInvT, t)
        return None

    @classmethod
    def get_traits_of_type(cls, trait_type: type[OpTraitInvT]) -> list[OpTraitInvT]:
        """
        Get all the traits of the given type satisfied by this operation.
        """
        return [t for t in cls.traits if isinstance(t, trait_type)]

    def erase(self, safe_erase: bool = True, drop_references: bool = True) -> None:
        """
        Erase the operation, and remove all its references to other operations.
        If safe_erase is specified, check that the operation results are not used.
        """
        assert self.parent is None, (
            "Operation with parents should first be detached " + "before erasure."
        )
        if drop_references:
            self.drop_all_references()
        for result in self.results:
            result.erase(safe_erase=safe_erase)

    def detach(self):
        """Detach the operation from its parent block."""
        if self.parent is None:
            raise ValueError("Cannot detach a toplevel operation.")
        self.parent.detach_op(self)

    def is_structurally_equivalent(
        self,
        other: IRNode,
        context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
    ) -> bool:
        """
        Check if two operations are structurally equivalent.
        The context is a mapping of IR nodes to IR nodes that are already known
        to be equivalent. This enables checking whether the use dependencies and
        successors are equivalent.
        """
        if context is None:
            context = {}
        if not isinstance(other, Operation):
            return False
        if self.name != other.name:
            return False
        if (
            len(self.operands) != len(other.operands)
            or len(self.results) != len(other.results)
            or len(self.regions) != len(other.regions)
            or len(self.successors) != len(other.successors)
            or self.attributes != other.attributes
            or self.properties != other.properties
        ):
            return False
        if (
            self.parent is not None
            and other.parent is not None
            and context.get(self.parent) != other.parent
        ):
            return False
        if not all(
            context.get(operand, operand) == other_operand
            for operand, other_operand in zip(self.operands, other.operands)
        ):
            return False
        if not all(
            context.get(successor, successor) == other_successor
            for successor, other_successor in zip(self.successors, other.successors)
        ):
            return False
        if not all(
            region.is_structurally_equivalent(other_region, context)
            for region, other_region in zip(self.regions, other.regions)
        ):
            return False
        # Add results of this operation to the context
        for result, other_result in zip(self.results, other.results):
            context[result] = other_result

        return True

    def emit_error(
        self,
        message: str,
        underlying_error: Exception,
    ) -> NoReturn:
        """Emit an error with the given message."""
        from xdsl.utils.diagnostic import Diagnostic

        diagnostic = Diagnostic()
        diagnostic.add_message(self, message)
        diagnostic.raise_exception(self, underlying_error)

    @classmethod
    def dialect_name(cls) -> str:
        return Dialect.split_name(cls.name)[0]

    def __repr__(self) -> str:
        operands = ", ".join(_short_repr(operand) for operand in self.operands)
        results = ", ".join(_short_repr(result) for result in self.results)
        successors = ", ".join(_short_repr(successor) for successor in self.successors)
        regions = ", ".join(_short_repr(region) for region in self.regions)

        return (
            f"<{self.__class__.__name__} {id(self)}("
            f"operands=[{operands}], "
            f"results=[{results}], "
            f"successors=[{successors}], "
            f"properties={repr(self.properties)}, "
            f"attributes={repr(self.attributes)}, "
            f"regions=[{regions}], "
            f"parent={_short_repr(self.parent)}, "
            f"_next_op={_short_repr(self.next_op)}, "
            f"_prev_op={_short_repr(self._prev_op)}"
            ")>"
        )

    def __str__(self) -> str:
        from xdsl.printer import Printer

        res = StringIO()
        printer = Printer(stream=res)
        printer.print_op(self)
        return res.getvalue()

    def __format__(self, format_spec: str, /) -> str:
        desc = str(self)
        if "\n" in desc:
            # Description is multi-line, indent each line
            desc = "\n".join("\t" + line for line in desc.splitlines())
            # Add newline before and after
            desc = f"\n{desc}\n"
        return f"{self.__class__.__qualname__}({desc})"

name: str class-attribute

The operation name. Should be a static member of the class

parent: Block | None = field(default=None) class-attribute instance-attribute

The block containing this operation.

traits: OpTraits class-attribute

Traits attached to an operation definition. This is a static field, and is made empty by default by PyRDL if not set by the operation definition.

parent_node: IRNode | None property

result_types: Sequence[Attribute] property

operand_types: Sequence[Attribute] property

next_op: Operation | None property

Next operation in block containing this operation.

prev_op: Operation | None property

Previous operation in block containing this operation.

successor_uses: Sequence[Use] property

operands: OpOperands = operands instance-attribute property writable

results: SSAValues[OpResult] = SSAValues((OpResult(result_type, self, idx)) for idx, result_type in (enumerate(result_types))) class-attribute instance-attribute

The results created by the operation.

properties: dict[str, Attribute] = dict(properties) class-attribute instance-attribute

The properties attached to the operation. Properties are inherent to the definition of an operation's semantics, and thus cannot be discarded by transformations.

attributes: dict[str, Attribute] = dict(attributes) class-attribute instance-attribute

The attributes attached to the operation.

successors: OpSuccessors = list(successors) instance-attribute property writable

regions: tuple[Region, ...] = () class-attribute instance-attribute

Regions arguments of the operation.

parent_op() -> Operation | None

Source code in xdsl/ir/core.py
1101
1102
1103
1104
def parent_op(self) -> Operation | None:
    if p := self.parent_region():
        return p.parent
    return None

parent_region() -> Region | None

Source code in xdsl/ir/core.py
1106
1107
1108
1109
def parent_region(self) -> Region | None:
    if (p := self.parent_block()) is not None:
        return p.parent
    return None

parent_block() -> Block | None

Source code in xdsl/ir/core.py
1111
1112
def parent_block(self) -> Block | None:
    return self.parent

__post_init__()

Source code in xdsl/ir/core.py
1194
1195
1196
def __post_init__(self):
    assert self.name != ""
    assert isinstance(self.name, str)

__init__(*, operands: Sequence[SSAValue] = (), result_types: Sequence[Attribute] = (), properties: Mapping[str, Attribute] = {}, attributes: Mapping[str, Attribute] = {}, successors: Sequence[Block] = (), regions: Sequence[Region] = ()) -> None

Source code in xdsl/ir/core.py
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
def __init__(
    self,
    *,
    operands: Sequence[SSAValue] = (),
    result_types: Sequence[Attribute] = (),
    properties: Mapping[str, Attribute] = {},
    attributes: Mapping[str, Attribute] = {},
    successors: Sequence[Block] = (),
    regions: Sequence[Region] = (),
) -> None:
    super().__init__()

    # This is assumed to exist by Operation.operand setter.
    self.operands = operands

    self.results = SSAValues(
        OpResult(result_type, self, idx)
        for (idx, result_type) in enumerate(result_types)
    )
    self.properties = dict(properties)
    self.attributes = dict(attributes)
    self.successors = list(successors)
    self.regions = ()
    for region in regions:
        self.add_region(region)

    self.__post_init__()

create(*, operands: Sequence[SSAValue] = (), result_types: Sequence[Attribute] = (), properties: Mapping[str, Attribute] = {}, attributes: Mapping[str, Attribute] = {}, successors: Sequence[Block] = (), regions: Sequence[Region] = ()) -> Self classmethod

Source code in xdsl/ir/core.py
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
@classmethod
def create(
    cls: type[Self],
    *,
    operands: Sequence[SSAValue] = (),
    result_types: Sequence[Attribute] = (),
    properties: Mapping[str, Attribute] = {},
    attributes: Mapping[str, Attribute] = {},
    successors: Sequence[Block] = (),
    regions: Sequence[Region] = (),
) -> Self:
    op = cls.__new__(cls)
    Operation.__init__(
        op,
        operands=operands,
        result_types=result_types,
        properties=properties,
        attributes=attributes,
        successors=successors,
        regions=regions,
    )
    return op

add_region(region: Region) -> None

Add an unattached region to the operation.

Source code in xdsl/ir/core.py
1249
1250
1251
1252
1253
1254
1255
1256
def add_region(self, region: Region) -> None:
    """Add an unattached region to the operation."""
    if region.parent:
        raise ValueError(
            "Cannot add region that is already attached on an operation."
        )
    self.regions += (region,)
    region.parent = self

get_region_index(region: Region) -> int

Get the region position in the operation.

Source code in xdsl/ir/core.py
1258
1259
1260
1261
1262
1263
1264
def get_region_index(self, region: Region) -> int:
    """Get the region position in the operation."""
    if region.parent is not self:
        raise ValueError("Region is not attached to the operation.")
    return next(
        idx for idx, curr_region in enumerate(self.regions) if curr_region is region
    )

detach_region(region: int | Region) -> Region

Detach a region from the operation. Returns the detached region.

Source code in xdsl/ir/core.py
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
def detach_region(self, region: int | Region) -> Region:
    """
    Detach a region from the operation.
    Returns the detached region.
    """
    if isinstance(region, Region):
        region_idx = self.get_region_index(region)
    else:
        region_idx = region
        region = self.regions[region_idx]
    region.parent = None
    self.regions = self.regions[:region_idx] + self.regions[region_idx + 1 :]
    return region

drop_all_references() -> None

Drop all references to other operations. This function is called prior to deleting an operation.

Source code in xdsl/ir/core.py
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
def drop_all_references(self) -> None:
    """
    Drop all references to other operations.
    This function is called prior to deleting an operation.
    """
    self.parent = None
    for operand, use in zip(self._operands, self._operand_uses):
        operand.remove_use(use)
    for region in self.regions:
        region.drop_all_references()

walk(*, reverse: bool = False, region_first: bool = False) -> Iterator[Operation]

Iterate all operations contained in the operation (including this one). If region_first is set, then the operation regions are iterated before the operation. If reverse is set, then the region, block, and operation lists are iterated in reverse order.

Source code in xdsl/ir/core.py
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
def walk(
    self, *, reverse: bool = False, region_first: bool = False
) -> Iterator[Operation]:
    """
    Iterate all operations contained in the operation (including this one).
    If region_first is set, then the operation regions are iterated before the
    operation. If reverse is set, then the region, block, and operation lists are
    iterated in reverse order.
    """
    if not region_first:
        yield self
    for region in reversed(self.regions) if reverse else self.regions:
        yield from region.walk(reverse=reverse, region_first=region_first)
    if region_first:
        yield self

walk_blocks(*, reverse: bool = False) -> Iterator[Block]

Iterate over all the blocks nested in the region. Iterate in reverse order if reverse is True.

Source code in xdsl/ir/core.py
1307
1308
1309
1310
1311
1312
1313
1314
def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
    """
    Iterate over all the blocks nested in the region.
    Iterate in reverse order if reverse is True.
    """
    for region in reversed(self.regions) if reverse else self.regions:
        for block in reversed(region.blocks) if reverse else region.blocks:
            yield from block.walk_blocks(reverse=reverse)

get_attr_or_prop(name: str) -> Attribute | None

Get a named attribute or property. It first look into the property dictionary, then into the attribute dictionary.

Source code in xdsl/ir/core.py
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
def get_attr_or_prop(self, name: str) -> Attribute | None:
    """
    Get a named attribute or property.
    It first look into the property dictionary, then into the attribute dictionary.
    """
    if name in self.properties:
        return self.properties[name]
    if name in self.attributes:
        return self.attributes[name]
    return None

is_before_in_block(other_op: Operation) -> bool

Return true if the current operation is located strictly before other_op. False otherwise.

Source code in xdsl/ir/core.py
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
def is_before_in_block(self, other_op: Operation) -> bool:
    """
    Return true if the current operation is located strictly before other_op.
    False otherwise.
    """
    if (
        parent_block := self.parent_block()
    ) is None or other_op.parent_block() is not parent_block:
        return False

    op = self.next_op
    while op is not None:
        if op is other_op:
            return True
        op = op.next_op
    return False

verify(verify_nested_ops: bool = True) -> None

Source code in xdsl/ir/core.py
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
def verify(self, verify_nested_ops: bool = True) -> None:
    for operand in self.operands:
        if isinstance(operand, ErasedSSAValue):
            raise ValueError("Erased SSA value is used by the operation")

    parent_block = self.parent
    parent_region = None if parent_block is None else parent_block.parent

    if self.successors:
        if parent_block is None or parent_region is None:
            raise VerifyException(
                f"Operation {self.name} with block successors does not belong to a block or a region"
            )

        if parent_block.last_op is not self:
            raise VerifyException(
                f"Operation {self.name} with block successors must terminate its parent block"
            )

        for succ in self.successors:
            if succ.parent != parent_block.parent:
                raise VerifyException(
                    f"Operation {self.name} is branching to a block of a different region"
                )

    if parent_block is not None and parent_region is not None:
        if parent_block.last_op == self:
            if len(parent_region.blocks) == 1:
                if (
                    parent_op := parent_region.parent
                ) is not None and not parent_op.has_trait(NoTerminator):
                    if not self.has_trait(IsTerminator):
                        raise VerifyException(
                            f"Operation {self.name} terminates block in "
                            "single-block region but is not a terminator"
                        )
            elif len(parent_region.blocks) > 1:
                if not self.has_trait(IsTerminator):
                    raise VerifyException(
                        f"Operation {self.name} terminates block in multi-block "
                        "region but is not a terminator"
                    )

    if verify_nested_ops:
        for region in self.regions:
            region.verify()

    # Custom verifier
    try:
        self.verify_()
    except VerifyException as err:
        self.emit_error(
            f"Operation does not verify: {err}",
            err,
        )

verify_() -> None

Source code in xdsl/ir/core.py
1400
1401
def verify_(self) -> None:
    pass

parse(parser: Parser) -> Self classmethod

Source code in xdsl/ir/core.py
1405
1406
1407
@classmethod
def parse(cls, parser: Parser) -> Self:
    parser.raise_error(f"Operation {cls.name} does not have a custom format.")

print(printer: Printer)

Source code in xdsl/ir/core.py
1409
1410
def print(self, printer: Printer):
    return printer.print_op_with_default_format(self)

clone_without_regions(value_mapper: dict[SSAValue, SSAValue] | None = None, block_mapper: dict[Block, Block] | None = None, *, clone_name_hints: bool = True, clone_operands: bool = True) -> Self

Clone an operation, with empty regions instead.

Source code in xdsl/ir/core.py
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
def clone_without_regions(
    self,
    value_mapper: dict[SSAValue, SSAValue] | None = None,
    block_mapper: dict[Block, Block] | None = None,
    *,
    clone_name_hints: bool = True,
    clone_operands: bool = True,
) -> Self:
    """Clone an operation, with empty regions instead."""
    if value_mapper is None:
        value_mapper = {}
    if block_mapper is None:
        block_mapper = {}
    if clone_operands:
        operands = tuple(
            value_mapper.get(operand, operand) for operand in self._operands
        )
    else:
        operands = ()
    result_types = self.result_types
    attributes = self.attributes.copy()
    properties = self.properties.copy()
    successors = [
        (block_mapper[successor] if successor in block_mapper else successor)
        for successor in self._successors
    ]
    regions = [Region() for _ in self.regions]
    cloned_op = self.create(
        operands=operands,
        result_types=result_types,
        attributes=attributes,
        properties=properties,
        successors=successors,
        regions=regions,
    )
    for self_result, cloned_result in zip(
        self.results, cloned_op.results, strict=True
    ):
        value_mapper[self_result] = cloned_result
        if clone_name_hints:
            cloned_result.name_hint = self_result.name_hint
    return cloned_op

clone(value_mapper: dict[SSAValue, SSAValue] | None = None, block_mapper: dict[Block, Block] | None = None, *, clone_name_hints: bool = True, clone_operands: bool = True) -> Self

Clone an operation with all its regions and operations in them.

Source code in xdsl/ir/core.py
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
def clone(
    self,
    value_mapper: dict[SSAValue, SSAValue] | None = None,
    block_mapper: dict[Block, Block] | None = None,
    *,
    clone_name_hints: bool = True,
    clone_operands: bool = True,
) -> Self:
    """Clone an operation with all its regions and operations in them."""
    if value_mapper is None:
        value_mapper = {}
    if block_mapper is None:
        block_mapper = {}
    op = self.clone_without_regions(
        value_mapper,
        block_mapper,
        clone_name_hints=clone_name_hints,
        clone_operands=False,
    )
    for idx, region in enumerate(self.regions):
        region.clone_into(
            op.regions[idx],
            0,
            value_mapper,
            block_mapper,
            clone_name_hints=clone_name_hints,
            clone_operands=False,
        )
    if clone_operands:
        for old, new in zip(self.walk(), op.walk()):
            new.operands = tuple(
                value_mapper.get(operand, operand) for operand in old.operands
            )
    return op

has_trait(trait: type[OpTrait] | OpTrait, *, value_if_unregistered: bool = True) -> bool classmethod

Check if the operation implements a trait with the given parameters. If the operation is not registered, return value_if_unregisteed instead.

Source code in xdsl/ir/core.py
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
@classmethod
def has_trait(
    cls,
    trait: type[OpTrait] | OpTrait,
    *,
    value_if_unregistered: bool = True,
) -> bool:
    """
    Check if the operation implements a trait with the given parameters.
    If the operation is not registered, return value_if_unregisteed instead.
    """
    return cls.get_trait(trait) is not None

get_trait(trait: type[OpTraitInvT] | OpTraitInvT) -> OpTraitInvT | None classmethod

Return a trait with the given type and parameters, if it exists.

Source code in xdsl/ir/core.py
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
@classmethod
def get_trait(cls, trait: type[OpTraitInvT] | OpTraitInvT) -> OpTraitInvT | None:
    """
    Return a trait with the given type and parameters, if it exists.
    """
    if isinstance(trait, type):
        for t in cls.traits:
            if isinstance(t, cast(type[OpTraitInvT], trait)):
                return t
    else:
        for t in cls.traits:
            if t == trait:
                return cast(OpTraitInvT, t)
    return None

get_traits_of_type(trait_type: type[OpTraitInvT]) -> list[OpTraitInvT] classmethod

Get all the traits of the given type satisfied by this operation.

Source code in xdsl/ir/core.py
1518
1519
1520
1521
1522
1523
@classmethod
def get_traits_of_type(cls, trait_type: type[OpTraitInvT]) -> list[OpTraitInvT]:
    """
    Get all the traits of the given type satisfied by this operation.
    """
    return [t for t in cls.traits if isinstance(t, trait_type)]

erase(safe_erase: bool = True, drop_references: bool = True) -> None

Erase the operation, and remove all its references to other operations. If safe_erase is specified, check that the operation results are not used.

Source code in xdsl/ir/core.py
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
def erase(self, safe_erase: bool = True, drop_references: bool = True) -> None:
    """
    Erase the operation, and remove all its references to other operations.
    If safe_erase is specified, check that the operation results are not used.
    """
    assert self.parent is None, (
        "Operation with parents should first be detached " + "before erasure."
    )
    if drop_references:
        self.drop_all_references()
    for result in self.results:
        result.erase(safe_erase=safe_erase)

detach()

Detach the operation from its parent block.

Source code in xdsl/ir/core.py
1538
1539
1540
1541
1542
def detach(self):
    """Detach the operation from its parent block."""
    if self.parent is None:
        raise ValueError("Cannot detach a toplevel operation.")
    self.parent.detach_op(self)

is_structurally_equivalent(other: IRNode, context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None) -> bool

Check if two operations are structurally equivalent. The context is a mapping of IR nodes to IR nodes that are already known to be equivalent. This enables checking whether the use dependencies and successors are equivalent.

Source code in xdsl/ir/core.py
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
def is_structurally_equivalent(
    self,
    other: IRNode,
    context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
) -> bool:
    """
    Check if two operations are structurally equivalent.
    The context is a mapping of IR nodes to IR nodes that are already known
    to be equivalent. This enables checking whether the use dependencies and
    successors are equivalent.
    """
    if context is None:
        context = {}
    if not isinstance(other, Operation):
        return False
    if self.name != other.name:
        return False
    if (
        len(self.operands) != len(other.operands)
        or len(self.results) != len(other.results)
        or len(self.regions) != len(other.regions)
        or len(self.successors) != len(other.successors)
        or self.attributes != other.attributes
        or self.properties != other.properties
    ):
        return False
    if (
        self.parent is not None
        and other.parent is not None
        and context.get(self.parent) != other.parent
    ):
        return False
    if not all(
        context.get(operand, operand) == other_operand
        for operand, other_operand in zip(self.operands, other.operands)
    ):
        return False
    if not all(
        context.get(successor, successor) == other_successor
        for successor, other_successor in zip(self.successors, other.successors)
    ):
        return False
    if not all(
        region.is_structurally_equivalent(other_region, context)
        for region, other_region in zip(self.regions, other.regions)
    ):
        return False
    # Add results of this operation to the context
    for result, other_result in zip(self.results, other.results):
        context[result] = other_result

    return True

emit_error(message: str, underlying_error: Exception) -> NoReturn

Emit an error with the given message.

Source code in xdsl/ir/core.py
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
def emit_error(
    self,
    message: str,
    underlying_error: Exception,
) -> NoReturn:
    """Emit an error with the given message."""
    from xdsl.utils.diagnostic import Diagnostic

    diagnostic = Diagnostic()
    diagnostic.add_message(self, message)
    diagnostic.raise_exception(self, underlying_error)

dialect_name() -> str classmethod

Source code in xdsl/ir/core.py
1609
1610
1611
@classmethod
def dialect_name(cls) -> str:
    return Dialect.split_name(cls.name)[0]

__repr__() -> str

Source code in xdsl/ir/core.py
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
def __repr__(self) -> str:
    operands = ", ".join(_short_repr(operand) for operand in self.operands)
    results = ", ".join(_short_repr(result) for result in self.results)
    successors = ", ".join(_short_repr(successor) for successor in self.successors)
    regions = ", ".join(_short_repr(region) for region in self.regions)

    return (
        f"<{self.__class__.__name__} {id(self)}("
        f"operands=[{operands}], "
        f"results=[{results}], "
        f"successors=[{successors}], "
        f"properties={repr(self.properties)}, "
        f"attributes={repr(self.attributes)}, "
        f"regions=[{regions}], "
        f"parent={_short_repr(self.parent)}, "
        f"_next_op={_short_repr(self.next_op)}, "
        f"_prev_op={_short_repr(self._prev_op)}"
        ")>"
    )

__str__() -> str

Source code in xdsl/ir/core.py
1633
1634
1635
1636
1637
1638
1639
def __str__(self) -> str:
    from xdsl.printer import Printer

    res = StringIO()
    printer = Printer(stream=res)
    printer.print_op(self)
    return res.getvalue()

__format__(format_spec: str) -> str

Source code in xdsl/ir/core.py
1641
1642
1643
1644
1645
1646
1647
1648
def __format__(self, format_spec: str, /) -> str:
    desc = str(self)
    if "\n" in desc:
        # Description is multi-line, indent each line
        desc = "\n".join("\t" + line for line in desc.splitlines())
        # Add newline before and after
        desc = f"\n{desc}\n"
    return f"{self.__class__.__qualname__}({desc})"

BlockOps dataclass

Bases: Reversible[Operation], Iterable[Operation]

Multi-pass iterable of the operations in a block. Follows the next_op for each operation.

Source code in xdsl/ir/core.py
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
@dataclass
class BlockOps(Reversible[Operation], Iterable[Operation]):
    """
    Multi-pass iterable of the operations in a block. Follows the next_op for
    each operation.
    """

    block: Block

    def __iter__(self):
        return _BlockOpsIterator(self.first)

    def __len__(self):
        result = 0
        for _ in self:
            result += 1
        return result

    def __bool__(self) -> bool:
        """Returns `True` if there are operations in this block."""
        return not self.block.is_empty

    def __reversed__(self):
        return _BlockOpsReverseIterator(self.block.last_op)

    @property
    def first(self) -> Operation | None:
        """
        First operation in the block, None if block is empty.
        """
        return self.block.first_op

    @property
    def last(self) -> Operation | None:
        """
        Last operation in the block, None if block is empty.
        """
        return self.block.last_op

block: Block instance-attribute

first: Operation | None property

First operation in the block, None if block is empty.

last: Operation | None property

Last operation in the block, None if block is empty.

__init__(block: Block) -> None

__iter__()

Source code in xdsl/ir/core.py
1703
1704
def __iter__(self):
    return _BlockOpsIterator(self.first)

__len__()

Source code in xdsl/ir/core.py
1706
1707
1708
1709
1710
def __len__(self):
    result = 0
    for _ in self:
        result += 1
    return result

__bool__() -> bool

Returns True if there are operations in this block.

Source code in xdsl/ir/core.py
1712
1713
1714
def __bool__(self) -> bool:
    """Returns `True` if there are operations in this block."""
    return not self.block.is_empty

__reversed__()

Source code in xdsl/ir/core.py
1716
1717
def __reversed__(self):
    return _BlockOpsReverseIterator(self.block.last_op)

Block dataclass

Bases: _IRNode, IRWithUses, IRWithName

A sequence of operations

Source code in xdsl/ir/core.py
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
@dataclass(init=False, eq=False, unsafe_hash=False)
class Block(_IRNode, IRWithUses, IRWithName):
    """A sequence of operations"""

    _args: tuple[BlockArgument, ...]
    """The basic block arguments."""

    _first_op: Operation | None = field(repr=False)
    _last_op: Operation | None = field(repr=False)

    _next_block: Block | None = field(default=None, repr=False)
    _prev_block: Block | None = field(default=None, repr=False)

    parent: Region | None = field(default=None, repr=False)
    """Parent region containing the block."""

    @staticmethod
    def is_default_block_name(name: str) -> bool:
        """Check if a name matches the default block naming pattern (bb followed by digits)."""
        return name.startswith("bb") and name[2:].isdigit() and name[2:] != ""

    def __init__(
        self,
        ops: Iterable[Operation] = (),
        *,
        arg_types: Iterable[Attribute] = (),
    ):
        super().__init__()
        self._args = tuple(
            BlockArgument(arg_type, self, index)
            for index, arg_type in enumerate(arg_types)
        )
        self._first_op = None
        self._last_op = None

        self.add_ops(ops)

    @property
    def arg_types(self) -> Sequence[Attribute]:
        return tuple(arg.type for arg in self._args)

    @property
    def parent_node(self) -> IRNode | None:
        return self.parent

    @property
    def ops(self) -> BlockOps:
        """Returns a multi-pass Iterable of this block's operations."""
        return BlockOps(self)

    @property
    def next_block(self) -> Block | None:
        """The next block in the parent region"""
        return self._next_block

    @property
    def prev_block(self) -> Block | None:
        """The previous block in the parent region"""
        return self._prev_block

    def predecessors(self) -> tuple[Block, ...]:
        return tuple(
            p for use in self.uses if (p := use.operation.parent_block()) is not None
        )

    def parent_op(self) -> Operation | None:
        return self.parent.parent if self.parent else None

    def parent_region(self) -> Region | None:
        return self.parent

    def parent_block(self) -> Block | None:
        return self.parent.parent.parent if self.parent and self.parent.parent else None

    def __repr__(self) -> str:
        return f"<Block {id(self)}(_args={repr(self._args)}, num_ops={len(self.ops)})>"

    @property
    def args(self) -> tuple[BlockArgument, ...]:
        """Returns the block arguments."""
        return self._args

    def insert_arg(self, arg_type: Attribute, index: int) -> BlockArgument:
        """
        Insert a new argument with a given type to the arguments list at a specific index.
        Returns the new argument.
        """
        if index < 0 or index > len(self._args):
            raise ValueError("Unexpected index")
        new_arg = BlockArgument(arg_type, self, index)
        for arg in self._args[index:]:
            arg.index += 1
        self._args = tuple(chain(self._args[:index], [new_arg], self._args[index:]))
        return new_arg

    def erase_arg(self, arg: BlockArgument, safe_erase: bool = True) -> None:
        """
        Erase a block argument.
        If safe_erase is True, check that the block argument is not used.
        If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.
        """
        if arg.block is not self:
            raise ValueError("Attempting to delete an argument of the wrong block")
        for block_arg in self._args[arg.index + 1 :]:
            block_arg.index -= 1
        self._args = tuple(chain(self._args[: arg.index], self._args[arg.index + 1 :]))
        arg.erase(safe_erase=safe_erase)

    def _attach_op(self, operation: Operation) -> None:
        """Attach an operation to the block, and check that it has no parents."""
        if operation.parent:
            raise ValueError(
                "Can't add to a block an operation already attached to a block."
            )
        if operation.is_ancestor(self):
            raise ValueError(
                "Can't add an operation to a block contained in the operation."
            )
        operation.parent = self

    @property
    def is_empty(self) -> bool:
        """Returns `True` if there are no operations in this block."""
        return self._first_op is None

    @property
    def first_op(self) -> Operation | None:
        """The first operation in this block."""
        return self._first_op

    @property
    def last_op(self) -> Operation | None:
        """The last operation in this block."""
        return self._last_op

    def insert_op_after(self, new_op: Operation, existing_op: Operation) -> None:
        """
        Inserts `new_op` into this block, after `existing_op`.
        `new_op` should not be attached to a block.
        """
        if existing_op.parent is not self:
            raise ValueError(
                "Can't insert operation after operation not in this block."
            )

        self._attach_op(new_op)

        next_op = existing_op.next_op
        existing_op._insert_next_op(new_op)  # pyright: ignore[reportPrivateUsage]
        if next_op is None:
            # No `next_op`, means `prev_op` is the last op in the block.
            self._last_op = new_op

    def insert_op_before(self, new_op: Operation, existing_op: Operation) -> None:
        """
        Inserts `new_op` into this block, before `existing_op`.
        `new_op` should not be attached to a block.
        """
        if existing_op.parent is not self:
            raise ValueError(
                "Can't insert operation before operation not in current block"
            )

        self._attach_op(new_op)

        prev_op = existing_op.prev_op
        existing_op._insert_prev_op(new_op)  # pyright: ignore[reportPrivateUsage]
        if prev_op is None:
            # No `prev_op`, means `next_op` is the first op in the block.
            self._first_op = new_op

    def add_op(self, operation: Operation) -> None:
        """
        Add an operation at the end of the block.
        The operation should not be attached to another block already.
        """
        if self._last_op is None:
            self._attach_op(operation)
            self._first_op = operation
            self._last_op = operation
        else:
            self.insert_op_after(operation, self._last_op)

    def add_ops(self, ops: Iterable[Operation]) -> None:
        """
        Add operations at the end of the block.
        The operations should not be attached to another block.
        """
        for op in ops:
            self.add_op(op)

    def insert_ops_before(
        self, ops: Sequence[Operation], existing_op: Operation
    ) -> None:
        for op in ops:
            self.insert_op_before(op, existing_op)

    def insert_ops_after(
        self, ops: Sequence[Operation], existing_op: Operation
    ) -> None:
        for op in ops:
            self.insert_op_after(op, existing_op)

            existing_op = op

    def split_before(
        self,
        b_first: Operation,
        *,
        arg_types: Iterable[Attribute] = (),
    ) -> Block:
        """
        Split the block into two blocks before the specified operation.

        Note that all operations before the one given stay as part of the original basic
        block, and the rest of the operations in the original block are moved to the new
        block, including the old terminator.
        The original block is left without a terminator.
        The newly formed block is inserted into the parent region immediately after `self`
        and returned.
        """
        # Use `a` for new contents of `self`, and `b` for new block.
        if b_first.parent is not self:
            raise ValueError("Cannot split block on operation outside of the block.")

        parent = self.parent
        if parent is None:
            raise ValueError("Cannot split block with no parent.")

        first_of_self = self._first_op
        assert first_of_self is not None

        last_of_self = self._last_op
        assert last_of_self is not None

        a_last = b_first.prev_op
        b_last = last_of_self
        if a_last is None:
            # `before` is the first op in the Block, so all the ops move to the new block
            a_first = None
        else:
            a_first = first_of_self

        # Update first and last ops of self
        self._first_op = a_first
        self._last_op = a_last

        b = Block(arg_types=arg_types)
        a_index = parent.get_block_index(self)
        parent.insert_block(b, a_index + 1)

        b._first_op = b_first
        b._last_op = b_last

        # Update parent for moved ops
        b_iter: Operation | None = b_first
        while b_iter is not None:
            b_iter.parent = b
            b_iter = b_iter.next_op

        # Update next op for self.last
        if a_last is not None:
            a_last._next_op = None  # pyright: ignore[reportPrivateUsage]

        # Update previous op for b.first
        b_first._prev_op = None  # pyright: ignore[reportPrivateUsage]

        return b

    def get_operation_index(self, op: Operation) -> int:
        """Get the operation position in a block."""
        if op.parent is not self:
            raise ValueError("Operation is not a child of the block.")
        return next(idx for idx, block_op in enumerate(self.ops) if block_op is op)

    def detach_op(self, op: Operation) -> Operation:
        """
        Detach an operation from the block.
        Returns the detached operation.
        """
        if op.parent is not self:
            raise ValueError("Cannot detach operation from a different block.")
        op.parent = None

        prev_op = op.prev_op
        next_op = op.next_op

        if prev_op is not None:
            # detach op from linked list
            prev_op._next_op = next_op  # pyright: ignore[reportPrivateUsage]
            # detach linked list from op
            op._prev_op = None  # pyright: ignore[reportPrivateUsage]
        else:
            # reattach linked list if op is first op this block
            assert self._first_op is op
            self._first_op = next_op

        if next_op is not None:
            # detach op from linked list
            next_op._prev_op = prev_op  # pyright: ignore[reportPrivateUsage]
            # detach linked list from op
            op._next_op = None  # pyright: ignore[reportPrivateUsage]
        else:
            # reattach linked list if op is last op in this block
            assert self._last_op is op
            self._last_op = prev_op

        return op

    def erase_op(self, op: Operation, safe_erase: bool = True) -> None:
        """
        Erase an operation from the block.
        If safe_erase is True, check that the operation has no uses.
        """
        op = self.detach_op(op)
        op.erase(safe_erase=safe_erase)

    def walk(
        self, *, reverse: bool = False, region_first: bool = False
    ) -> Iterable[Operation]:
        """
        Iterate over all operations contained in the block.
        If region_first is set, then the operation regions are iterated before the
        operation. If reverse is set, then the region, block, and operation lists are
        iterated in reverse order.
        """
        for op in reversed(self.ops) if reverse else self.ops:
            yield from op.walk(reverse=reverse, region_first=region_first)

    def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
        """
        Iterate over all the blocks nested within this block, including self, in the
        order in which they are printed in the IR.
        Iterate in reverse order if reverse is True.
        """
        if not reverse:
            yield self
        for op in reversed(self.ops) if reverse else self.ops:
            yield from op.walk_blocks(reverse=reverse)
        if reverse:
            yield self

    def verify(self) -> None:
        for operation in self.ops:
            if operation.parent != self:
                raise ValueError(
                    "Parent pointer of operation does not refer to containing region"
                )
            operation.verify()

        if len(self.ops) == 0:
            if (region_parent := self.parent) is not None and (
                parent_op := region_parent.parent
            ) is not None:
                if len(region_parent.blocks) == 1 and not parent_op.has_trait(
                    NoTerminator
                ):
                    raise VerifyException(
                        f"Operation {parent_op.name} contains empty block in "
                        "single-block region that expects at least a terminator"
                    )

    def drop_all_references(self) -> None:
        """
        Drop all references to other operations.
        This function is called prior to deleting a block.
        """
        self.parent = None
        self._next_block = None
        self._prev_block = None
        for op in self.ops:
            op.drop_all_references()

    def find_ancestor_op_in_block(self, op: Operation) -> Operation | None:
        """
        Traverse up the operation hierarchy starting from op to find the ancestor
        operation that resides in the block.

        Returns None if no ancestor is found.
        """
        curr_op = op
        while curr_op.parent_block() != self:
            if (curr_op := curr_op.parent_op()) is None:
                return None

        return curr_op

    def erase(self, safe_erase: bool = True) -> None:
        """
        Erase the block, and remove all its references to other operations.
        If safe_erase is specified, check that no operation results are used outside
        the block.
        """
        assert self.parent is None, (
            "Blocks with parents should first be detached " + "before erasure."
        )
        self.drop_all_references()
        for op in self.ops:
            op.erase(safe_erase=safe_erase, drop_references=False)

    def is_structurally_equivalent(
        self,
        other: IRNode,
        context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
    ) -> bool:
        """
        Check if two blocks are structurally equivalent.
        The context is a mapping of IR nodes to IR nodes that are already known
        to be equivalent. This enables checking whether the use dependencies and
        successors are equivalent.
        """
        if context is None:
            context = {}
        if not isinstance(other, Block):
            return False
        if len(self.args) != len(other.args) or len(self.ops) != len(other.ops):
            return False
        for arg, other_arg in zip(self.args, other.args):
            if arg.type != other_arg.type:
                return False
            context[arg] = other_arg
        # Add self to the context so Operations can check for identical parents
        context[self] = other
        if not all(
            op.is_structurally_equivalent(other_op, context)
            for op, other_op in zip(self.ops, other.ops)
        ):
            return False

        return True

parent: Region | None = field(default=None, repr=False) class-attribute instance-attribute

Parent region containing the block.

arg_types: Sequence[Attribute] property

parent_node: IRNode | None property

ops: BlockOps property

Returns a multi-pass Iterable of this block's operations.

next_block: Block | None property

The next block in the parent region

prev_block: Block | None property

The previous block in the parent region

args: tuple[BlockArgument, ...] property

Returns the block arguments.

is_empty: bool property

Returns True if there are no operations in this block.

first_op: Operation | None property

The first operation in this block.

last_op: Operation | None property

The last operation in this block.

is_default_block_name(name: str) -> bool staticmethod

Check if a name matches the default block naming pattern (bb followed by digits).

Source code in xdsl/ir/core.py
1750
1751
1752
1753
@staticmethod
def is_default_block_name(name: str) -> bool:
    """Check if a name matches the default block naming pattern (bb followed by digits)."""
    return name.startswith("bb") and name[2:].isdigit() and name[2:] != ""

__init__(ops: Iterable[Operation] = (), *, arg_types: Iterable[Attribute] = ())

Source code in xdsl/ir/core.py
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
def __init__(
    self,
    ops: Iterable[Operation] = (),
    *,
    arg_types: Iterable[Attribute] = (),
):
    super().__init__()
    self._args = tuple(
        BlockArgument(arg_type, self, index)
        for index, arg_type in enumerate(arg_types)
    )
    self._first_op = None
    self._last_op = None

    self.add_ops(ops)

predecessors() -> tuple[Block, ...]

Source code in xdsl/ir/core.py
1794
1795
1796
1797
def predecessors(self) -> tuple[Block, ...]:
    return tuple(
        p for use in self.uses if (p := use.operation.parent_block()) is not None
    )

parent_op() -> Operation | None

Source code in xdsl/ir/core.py
1799
1800
def parent_op(self) -> Operation | None:
    return self.parent.parent if self.parent else None

parent_region() -> Region | None

Source code in xdsl/ir/core.py
1802
1803
def parent_region(self) -> Region | None:
    return self.parent

parent_block() -> Block | None

Source code in xdsl/ir/core.py
1805
1806
def parent_block(self) -> Block | None:
    return self.parent.parent.parent if self.parent and self.parent.parent else None

__repr__() -> str

Source code in xdsl/ir/core.py
1808
1809
def __repr__(self) -> str:
    return f"<Block {id(self)}(_args={repr(self._args)}, num_ops={len(self.ops)})>"

insert_arg(arg_type: Attribute, index: int) -> BlockArgument

Insert a new argument with a given type to the arguments list at a specific index. Returns the new argument.

Source code in xdsl/ir/core.py
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
def insert_arg(self, arg_type: Attribute, index: int) -> BlockArgument:
    """
    Insert a new argument with a given type to the arguments list at a specific index.
    Returns the new argument.
    """
    if index < 0 or index > len(self._args):
        raise ValueError("Unexpected index")
    new_arg = BlockArgument(arg_type, self, index)
    for arg in self._args[index:]:
        arg.index += 1
    self._args = tuple(chain(self._args[:index], [new_arg], self._args[index:]))
    return new_arg

erase_arg(arg: BlockArgument, safe_erase: bool = True) -> None

Erase a block argument. If safe_erase is True, check that the block argument is not used. If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.

Source code in xdsl/ir/core.py
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
def erase_arg(self, arg: BlockArgument, safe_erase: bool = True) -> None:
    """
    Erase a block argument.
    If safe_erase is True, check that the block argument is not used.
    If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.
    """
    if arg.block is not self:
        raise ValueError("Attempting to delete an argument of the wrong block")
    for block_arg in self._args[arg.index + 1 :]:
        block_arg.index -= 1
    self._args = tuple(chain(self._args[: arg.index], self._args[arg.index + 1 :]))
    arg.erase(safe_erase=safe_erase)

insert_op_after(new_op: Operation, existing_op: Operation) -> None

Inserts new_op into this block, after existing_op. new_op should not be attached to a block.

Source code in xdsl/ir/core.py
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
def insert_op_after(self, new_op: Operation, existing_op: Operation) -> None:
    """
    Inserts `new_op` into this block, after `existing_op`.
    `new_op` should not be attached to a block.
    """
    if existing_op.parent is not self:
        raise ValueError(
            "Can't insert operation after operation not in this block."
        )

    self._attach_op(new_op)

    next_op = existing_op.next_op
    existing_op._insert_next_op(new_op)  # pyright: ignore[reportPrivateUsage]
    if next_op is None:
        # No `next_op`, means `prev_op` is the last op in the block.
        self._last_op = new_op

insert_op_before(new_op: Operation, existing_op: Operation) -> None

Inserts new_op into this block, before existing_op. new_op should not be attached to a block.

Source code in xdsl/ir/core.py
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
def insert_op_before(self, new_op: Operation, existing_op: Operation) -> None:
    """
    Inserts `new_op` into this block, before `existing_op`.
    `new_op` should not be attached to a block.
    """
    if existing_op.parent is not self:
        raise ValueError(
            "Can't insert operation before operation not in current block"
        )

    self._attach_op(new_op)

    prev_op = existing_op.prev_op
    existing_op._insert_prev_op(new_op)  # pyright: ignore[reportPrivateUsage]
    if prev_op is None:
        # No `prev_op`, means `next_op` is the first op in the block.
        self._first_op = new_op

add_op(operation: Operation) -> None

Add an operation at the end of the block. The operation should not be attached to another block already.

Source code in xdsl/ir/core.py
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
def add_op(self, operation: Operation) -> None:
    """
    Add an operation at the end of the block.
    The operation should not be attached to another block already.
    """
    if self._last_op is None:
        self._attach_op(operation)
        self._first_op = operation
        self._last_op = operation
    else:
        self.insert_op_after(operation, self._last_op)

add_ops(ops: Iterable[Operation]) -> None

Add operations at the end of the block. The operations should not be attached to another block.

Source code in xdsl/ir/core.py
1917
1918
1919
1920
1921
1922
1923
def add_ops(self, ops: Iterable[Operation]) -> None:
    """
    Add operations at the end of the block.
    The operations should not be attached to another block.
    """
    for op in ops:
        self.add_op(op)

insert_ops_before(ops: Sequence[Operation], existing_op: Operation) -> None

Source code in xdsl/ir/core.py
1925
1926
1927
1928
1929
def insert_ops_before(
    self, ops: Sequence[Operation], existing_op: Operation
) -> None:
    for op in ops:
        self.insert_op_before(op, existing_op)

insert_ops_after(ops: Sequence[Operation], existing_op: Operation) -> None

Source code in xdsl/ir/core.py
1931
1932
1933
1934
1935
1936
1937
def insert_ops_after(
    self, ops: Sequence[Operation], existing_op: Operation
) -> None:
    for op in ops:
        self.insert_op_after(op, existing_op)

        existing_op = op

split_before(b_first: Operation, *, arg_types: Iterable[Attribute] = ()) -> Block

Split the block into two blocks before the specified operation.

Note that all operations before the one given stay as part of the original basic block, and the rest of the operations in the original block are moved to the new block, including the old terminator. The original block is left without a terminator. The newly formed block is inserted into the parent region immediately after self and returned.

Source code in xdsl/ir/core.py
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
def split_before(
    self,
    b_first: Operation,
    *,
    arg_types: Iterable[Attribute] = (),
) -> Block:
    """
    Split the block into two blocks before the specified operation.

    Note that all operations before the one given stay as part of the original basic
    block, and the rest of the operations in the original block are moved to the new
    block, including the old terminator.
    The original block is left without a terminator.
    The newly formed block is inserted into the parent region immediately after `self`
    and returned.
    """
    # Use `a` for new contents of `self`, and `b` for new block.
    if b_first.parent is not self:
        raise ValueError("Cannot split block on operation outside of the block.")

    parent = self.parent
    if parent is None:
        raise ValueError("Cannot split block with no parent.")

    first_of_self = self._first_op
    assert first_of_self is not None

    last_of_self = self._last_op
    assert last_of_self is not None

    a_last = b_first.prev_op
    b_last = last_of_self
    if a_last is None:
        # `before` is the first op in the Block, so all the ops move to the new block
        a_first = None
    else:
        a_first = first_of_self

    # Update first and last ops of self
    self._first_op = a_first
    self._last_op = a_last

    b = Block(arg_types=arg_types)
    a_index = parent.get_block_index(self)
    parent.insert_block(b, a_index + 1)

    b._first_op = b_first
    b._last_op = b_last

    # Update parent for moved ops
    b_iter: Operation | None = b_first
    while b_iter is not None:
        b_iter.parent = b
        b_iter = b_iter.next_op

    # Update next op for self.last
    if a_last is not None:
        a_last._next_op = None  # pyright: ignore[reportPrivateUsage]

    # Update previous op for b.first
    b_first._prev_op = None  # pyright: ignore[reportPrivateUsage]

    return b

get_operation_index(op: Operation) -> int

Get the operation position in a block.

Source code in xdsl/ir/core.py
2003
2004
2005
2006
2007
def get_operation_index(self, op: Operation) -> int:
    """Get the operation position in a block."""
    if op.parent is not self:
        raise ValueError("Operation is not a child of the block.")
    return next(idx for idx, block_op in enumerate(self.ops) if block_op is op)

detach_op(op: Operation) -> Operation

Detach an operation from the block. Returns the detached operation.

Source code in xdsl/ir/core.py
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
def detach_op(self, op: Operation) -> Operation:
    """
    Detach an operation from the block.
    Returns the detached operation.
    """
    if op.parent is not self:
        raise ValueError("Cannot detach operation from a different block.")
    op.parent = None

    prev_op = op.prev_op
    next_op = op.next_op

    if prev_op is not None:
        # detach op from linked list
        prev_op._next_op = next_op  # pyright: ignore[reportPrivateUsage]
        # detach linked list from op
        op._prev_op = None  # pyright: ignore[reportPrivateUsage]
    else:
        # reattach linked list if op is first op this block
        assert self._first_op is op
        self._first_op = next_op

    if next_op is not None:
        # detach op from linked list
        next_op._prev_op = prev_op  # pyright: ignore[reportPrivateUsage]
        # detach linked list from op
        op._next_op = None  # pyright: ignore[reportPrivateUsage]
    else:
        # reattach linked list if op is last op in this block
        assert self._last_op is op
        self._last_op = prev_op

    return op

erase_op(op: Operation, safe_erase: bool = True) -> None

Erase an operation from the block. If safe_erase is True, check that the operation has no uses.

Source code in xdsl/ir/core.py
2043
2044
2045
2046
2047
2048
2049
def erase_op(self, op: Operation, safe_erase: bool = True) -> None:
    """
    Erase an operation from the block.
    If safe_erase is True, check that the operation has no uses.
    """
    op = self.detach_op(op)
    op.erase(safe_erase=safe_erase)

walk(*, reverse: bool = False, region_first: bool = False) -> Iterable[Operation]

Iterate over all operations contained in the block. If region_first is set, then the operation regions are iterated before the operation. If reverse is set, then the region, block, and operation lists are iterated in reverse order.

Source code in xdsl/ir/core.py
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
def walk(
    self, *, reverse: bool = False, region_first: bool = False
) -> Iterable[Operation]:
    """
    Iterate over all operations contained in the block.
    If region_first is set, then the operation regions are iterated before the
    operation. If reverse is set, then the region, block, and operation lists are
    iterated in reverse order.
    """
    for op in reversed(self.ops) if reverse else self.ops:
        yield from op.walk(reverse=reverse, region_first=region_first)

walk_blocks(*, reverse: bool = False) -> Iterator[Block]

Iterate over all the blocks nested within this block, including self, in the order in which they are printed in the IR. Iterate in reverse order if reverse is True.

Source code in xdsl/ir/core.py
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
    """
    Iterate over all the blocks nested within this block, including self, in the
    order in which they are printed in the IR.
    Iterate in reverse order if reverse is True.
    """
    if not reverse:
        yield self
    for op in reversed(self.ops) if reverse else self.ops:
        yield from op.walk_blocks(reverse=reverse)
    if reverse:
        yield self

verify() -> None

Source code in xdsl/ir/core.py
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
def verify(self) -> None:
    for operation in self.ops:
        if operation.parent != self:
            raise ValueError(
                "Parent pointer of operation does not refer to containing region"
            )
        operation.verify()

    if len(self.ops) == 0:
        if (region_parent := self.parent) is not None and (
            parent_op := region_parent.parent
        ) is not None:
            if len(region_parent.blocks) == 1 and not parent_op.has_trait(
                NoTerminator
            ):
                raise VerifyException(
                    f"Operation {parent_op.name} contains empty block in "
                    "single-block region that expects at least a terminator"
                )

drop_all_references() -> None

Drop all references to other operations. This function is called prior to deleting a block.

Source code in xdsl/ir/core.py
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
def drop_all_references(self) -> None:
    """
    Drop all references to other operations.
    This function is called prior to deleting a block.
    """
    self.parent = None
    self._next_block = None
    self._prev_block = None
    for op in self.ops:
        op.drop_all_references()

find_ancestor_op_in_block(op: Operation) -> Operation | None

Traverse up the operation hierarchy starting from op to find the ancestor operation that resides in the block.

Returns None if no ancestor is found.

Source code in xdsl/ir/core.py
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
def find_ancestor_op_in_block(self, op: Operation) -> Operation | None:
    """
    Traverse up the operation hierarchy starting from op to find the ancestor
    operation that resides in the block.

    Returns None if no ancestor is found.
    """
    curr_op = op
    while curr_op.parent_block() != self:
        if (curr_op := curr_op.parent_op()) is None:
            return None

    return curr_op

erase(safe_erase: bool = True) -> None

Erase the block, and remove all its references to other operations. If safe_erase is specified, check that no operation results are used outside the block.

Source code in xdsl/ir/core.py
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
def erase(self, safe_erase: bool = True) -> None:
    """
    Erase the block, and remove all its references to other operations.
    If safe_erase is specified, check that no operation results are used outside
    the block.
    """
    assert self.parent is None, (
        "Blocks with parents should first be detached " + "before erasure."
    )
    self.drop_all_references()
    for op in self.ops:
        op.erase(safe_erase=safe_erase, drop_references=False)

is_structurally_equivalent(other: IRNode, context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None) -> bool

Check if two blocks are structurally equivalent. The context is a mapping of IR nodes to IR nodes that are already known to be equivalent. This enables checking whether the use dependencies and successors are equivalent.

Source code in xdsl/ir/core.py
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
def is_structurally_equivalent(
    self,
    other: IRNode,
    context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
) -> bool:
    """
    Check if two blocks are structurally equivalent.
    The context is a mapping of IR nodes to IR nodes that are already known
    to be equivalent. This enables checking whether the use dependencies and
    successors are equivalent.
    """
    if context is None:
        context = {}
    if not isinstance(other, Block):
        return False
    if len(self.args) != len(other.args) or len(self.ops) != len(other.ops):
        return False
    for arg, other_arg in zip(self.args, other.args):
        if arg.type != other_arg.type:
            return False
        context[arg] = other_arg
    # Add self to the context so Operations can check for identical parents
    context[self] = other
    if not all(
        op.is_structurally_equivalent(other_op, context)
        for op, other_op in zip(self.ops, other.ops)
    ):
        return False

    return True

OpSuccessors dataclass

Bases: Sequence[Block]

A view of the successor list of an operation. Any modification to the view is reflected on the operation.

Source code in xdsl/ir/core.py
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
@dataclass
class OpSuccessors(Sequence[Block]):
    """
    A view of the successor list of an operation.
    Any modification to the view is reflected on the operation.
    """

    _op: Operation
    """The operation owning the successors."""

    @overload
    def __getitem__(self, idx: int) -> Block: ...

    @overload
    def __getitem__(self, idx: slice) -> Sequence[Block]: ...

    def __getitem__(self, idx: int | slice) -> Block | Sequence[Block]:
        return self._op._successors[idx]  # pyright: ignore[reportPrivateUsage]

    def __setitem__(self, idx: int, successor: Block) -> None:
        successors = self._op._successors  # pyright: ignore[reportPrivateUsage]
        successor_uses = self._op._successor_uses  # pyright: ignore[reportPrivateUsage]
        successors[idx].remove_use(successor_uses[idx])
        successor.add_use(successor_uses[idx])
        new_successors = (*successors[:idx], successor, *successors[idx + 1 :])
        self._op._successors = new_successors  # pyright: ignore[reportPrivateUsage]

    def __iter__(self) -> Iterator[Block]:
        return iter(self._op._successors)  # pyright: ignore[reportPrivateUsage]

    def __len__(self) -> int:
        return len(self._op._successors)  # pyright: ignore[reportPrivateUsage]

    def __eq__(self, other: object):
        if not isinstance(other, OpSuccessors):
            return False
        return (
            self._op._successors  # pyright: ignore[reportPrivateUsage]
            == other._op._successors  # pyright: ignore[reportPrivateUsage]
        )

    def __hash__(self):
        return hash(self._op._successors)  # pyright: ignore[reportPrivateUsage]

__init__(_op: Operation) -> None

__getitem__(idx: int | slice) -> Block | Sequence[Block]

__getitem__(idx: int) -> Block
__getitem__(idx: slice) -> Sequence[Block]
Source code in xdsl/ir/core.py
2202
2203
def __getitem__(self, idx: int | slice) -> Block | Sequence[Block]:
    return self._op._successors[idx]  # pyright: ignore[reportPrivateUsage]

__setitem__(idx: int, successor: Block) -> None

Source code in xdsl/ir/core.py
2205
2206
2207
2208
2209
2210
2211
def __setitem__(self, idx: int, successor: Block) -> None:
    successors = self._op._successors  # pyright: ignore[reportPrivateUsage]
    successor_uses = self._op._successor_uses  # pyright: ignore[reportPrivateUsage]
    successors[idx].remove_use(successor_uses[idx])
    successor.add_use(successor_uses[idx])
    new_successors = (*successors[:idx], successor, *successors[idx + 1 :])
    self._op._successors = new_successors  # pyright: ignore[reportPrivateUsage]

__iter__() -> Iterator[Block]

Source code in xdsl/ir/core.py
2213
2214
def __iter__(self) -> Iterator[Block]:
    return iter(self._op._successors)  # pyright: ignore[reportPrivateUsage]

__len__() -> int

Source code in xdsl/ir/core.py
2216
2217
def __len__(self) -> int:
    return len(self._op._successors)  # pyright: ignore[reportPrivateUsage]

__eq__(other: object)

Source code in xdsl/ir/core.py
2219
2220
2221
2222
2223
2224
2225
def __eq__(self, other: object):
    if not isinstance(other, OpSuccessors):
        return False
    return (
        self._op._successors  # pyright: ignore[reportPrivateUsage]
        == other._op._successors  # pyright: ignore[reportPrivateUsage]
    )

__hash__()

Source code in xdsl/ir/core.py
2227
2228
def __hash__(self):
    return hash(self._op._successors)  # pyright: ignore[reportPrivateUsage]

RegionBlocks dataclass

Bases: Sequence[Block], Reversible[Block]

Multi-pass iterable of the blocks in a region.

Source code in xdsl/ir/core.py
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
@dataclass
class RegionBlocks(Sequence[Block], Reversible[Block]):
    """
    Multi-pass iterable of the blocks in a region.
    """

    _region: Region

    def __iter__(self):
        return _RegionBlocksIterator(self._region.first_block)

    @overload
    def __getitem__(self, idx: int) -> Block: ...

    @overload
    def __getitem__(self, idx: slice) -> Sequence[Block]: ...

    def __getitem__(self, idx: int | slice) -> Block | Sequence[Block]:
        if isinstance(idx, int):
            if 0 <= idx:
                for i, b in enumerate(self):
                    if i == idx:
                        return b
                raise IndexError
            else:
                for i, b in enumerate(reversed(self)):
                    if -1 == i + idx:
                        return b
                raise IndexError
        else:
            # This is possible but would require a bit of work to handle complex slices
            raise NotImplementedError("Indexing of RegionBlocks not yet implemented")

    def __len__(self):
        i = 0
        for _ in self:
            i += 1
        return i

    def __bool__(self) -> bool:
        """Returns `True` if there are blocks in this region."""
        first_block = self._region.first_block
        return first_block is not None

    def __reversed__(self):
        return _RegionBlocksReverseIterator(self._region.last_block)

    @property
    def first(self) -> Block | None:
        """
        First block in the region, None if region is empty.
        """
        return self._region.first_block

    @property
    def last(self) -> Block | None:
        """
        Last block in the region, None if region is empty.
        """
        return self._region.last_block

first: Block | None property

First block in the region, None if region is empty.

last: Block | None property

Last block in the region, None if region is empty.

__init__(_region: Region) -> None

__iter__()

Source code in xdsl/ir/core.py
2259
2260
def __iter__(self):
    return _RegionBlocksIterator(self._region.first_block)

__getitem__(idx: int | slice) -> Block | Sequence[Block]

__getitem__(idx: int) -> Block
__getitem__(idx: slice) -> Sequence[Block]
Source code in xdsl/ir/core.py
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
def __getitem__(self, idx: int | slice) -> Block | Sequence[Block]:
    if isinstance(idx, int):
        if 0 <= idx:
            for i, b in enumerate(self):
                if i == idx:
                    return b
            raise IndexError
        else:
            for i, b in enumerate(reversed(self)):
                if -1 == i + idx:
                    return b
            raise IndexError
    else:
        # This is possible but would require a bit of work to handle complex slices
        raise NotImplementedError("Indexing of RegionBlocks not yet implemented")

__len__()

Source code in xdsl/ir/core.py
2284
2285
2286
2287
2288
def __len__(self):
    i = 0
    for _ in self:
        i += 1
    return i

__bool__() -> bool

Returns True if there are blocks in this region.

Source code in xdsl/ir/core.py
2290
2291
2292
2293
def __bool__(self) -> bool:
    """Returns `True` if there are blocks in this region."""
    first_block = self._region.first_block
    return first_block is not None

__reversed__()

Source code in xdsl/ir/core.py
2295
2296
def __reversed__(self):
    return _RegionBlocksReverseIterator(self._region.last_block)

Region dataclass

Bases: _IRNode

A region contains a CFG of blocks. Regions are contained in operations.

Source code in xdsl/ir/core.py
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
@dataclass(init=False, eq=False, unsafe_hash=False)
class Region(_IRNode):
    """A region contains a CFG of blocks. Regions are contained in operations."""

    class DEFAULT:
        """
        A marker to be used as a default parameter to functions when a default
        single-block region should be constructed.
        """

    _first_block: Block | None = field(default=None, repr=False)
    """The first block in the region. This is the entry block if it is present."""

    _last_block: Block | None = field(default=None, repr=False)
    """The last block in the region."""

    parent: Operation | None = field(default=None, repr=False)
    """Operation containing the region."""

    def __init__(self, blocks: Block | Iterable[Block] = ()):
        super().__init__()
        self.add_block(blocks)

    @property
    def parent_node(self) -> IRNode | None:
        return self.parent

    def parent_block(self) -> Block | None:
        return self.parent.parent if self.parent else None

    def parent_op(self) -> Operation | None:
        return self.parent

    def parent_region(self) -> Region | None:
        return (
            self.parent.parent.parent
            if self.parent is not None and self.parent.parent is not None
            else None
        )

    def find_ancestor_block_in_region(self, block: Block) -> Block | None:
        """
        Returns 'block' if 'block' lies in this region, or otherwise finds
        the ancestor of 'block' that lies in this region.

        Returns None if no ancestor block that lies in this region is found.
        """
        curr_block = block
        while curr_block.parent_region() != self:
            curr_block = curr_block.parent_block()
            if curr_block is None:
                return None

        return curr_block

    @property
    def blocks(self) -> RegionBlocks:
        """
        A multi-pass iterable of blocks.
        """
        return RegionBlocks(self)

    @property
    def first_block(self) -> Block | None:
        """First block in this region. This is the entry block if present."""
        return self._first_block

    @property
    def last_block(self) -> Block | None:
        """Last block in this region."""
        return self._last_block

    def __repr__(self) -> str:
        short_reprs = ", ".join(_short_repr(block) for block in self.blocks)
        return f"Region(blocks=[{short_reprs}])"

    @property
    def ops(self) -> BlockOps:
        """
        Get the operations of a single-block region.
        Returns an exception if the region is not single-block.
        """
        if len(self.blocks) != 1:
            raise ValueError(
                "'ops' property of Region class is only available "
                "for single-block regions."
            )
        return self.block.ops

    @property
    def op(self) -> Operation:
        """
        Get the operation of a single-operation single-block region.
        Returns an exception if the region is not single-operation single-block.
        """
        if len(self.blocks) == 1:
            block = self.block
            first_op = block.first_op
            last_op = block.last_op
            if first_op is last_op and first_op is not None:
                return first_op
        raise ValueError(
            "'op' property of Region class is only available "
            "for single-operation single-block regions."
        )

    @property
    def block(self) -> Block:
        """
        Get the block of a single-block region.
        Returns an exception if the region is not single-block.
        """
        if self._first_block is None or self._first_block is not self._last_block:
            raise ValueError(
                "'block' property of Region class is only available "
                "for single-block regions."
            )
        return self._first_block

    def _attach_block(self, block: Block) -> None:
        """Attach a block to the region, and check that it has no parents."""
        if block.parent:
            raise ValueError(
                "Can't add to a region a block already attached to a region."
            )
        if block.is_ancestor(self):
            raise ValueError("Can't add a block to a region contained in the block.")
        block.parent = self

    def add_block(self, block: Block | Iterable[Block]) -> None:
        """
        Insert one or multiple blocks at the end of the region.
        The blocks should not be attached to another region.
        """
        blocks_iter: Iterator[Block]
        if isinstance(block, Block):
            blocks_iter = iter((block,))
        else:
            blocks_iter = iter(block)
        prev_block = self.last_block

        if prev_block is None:
            try:
                # First block
                prev_block = next(blocks_iter)
                self._attach_block(prev_block)
                self._first_block = prev_block
            except StopIteration:
                # blocks_iter is empty, nothing to do
                return

        try:
            while True:
                next_block = next(blocks_iter)
                self._attach_block(next_block)
                next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                    prev_block
                )
                prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                    next_block
                )
                prev_block = next_block

        except StopIteration:
            # Repair last block
            self._last_block = prev_block
            return

    def insert_block_before(
        self, block: Block | Iterable[Block], target: Block
    ) -> None:
        """
        Insert one or multiple blocks before a given block in the region.
        The blocks should not be attached to another region.
        """
        if target.parent is not self:
            raise ValueError(
                "Cannot insert blocks before a block into a region that is not the target's parent"
            )
        blocks_iter: Iterator[Block]
        if isinstance(block, Block):
            blocks_iter = iter((block,))
        else:
            blocks_iter = iter(block)
        prev_block = target.prev_block

        if prev_block is None:
            try:
                # First block
                new_first = next(blocks_iter)
                self._attach_block(new_first)
                self._first_block = new_first
                new_first._next_block = target  # pyright: ignore[reportPrivateUsage]
                prev_block = new_first
            except StopIteration:
                # blocks_iter is empty, nothing to do
                return

        # The invariant for the loop is that prev_block is always before target when
        # calling `next`.

        try:
            while True:
                next_block = next(blocks_iter)
                self._attach_block(next_block)
                next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                    prev_block
                )
                prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                    next_block
                )
                prev_block = next_block

        except StopIteration:
            # Repair broken link
            prev_block._next_block = target  # pyright: ignore[reportPrivateUsage]
            target._prev_block = prev_block  # pyright: ignore[reportPrivateUsage]
            return

    def insert_block_after(self, block: Block | Iterable[Block], target: Block) -> None:
        """
        Insert one or multiple blocks after a given block in the region.
        The blocks should not be attached to another region.
        """
        next_block = target.next_block
        if next_block is None:
            self.add_block(block)
        else:
            self.insert_block_before(block, next_block)

    def insert_block(self, blocks: Block | Iterable[Block], index: int) -> None:
        """
        Insert one or multiple blocks at a given index in the region.
        The blocks should not be attached to another region.
        """
        i = -1
        for i, b in enumerate(self.blocks):
            if i == index:
                self.insert_block_before(blocks, b)
                return
        if i + 1 == index:
            # Append block
            self.add_block(blocks)

    def get_block_index(self, block: Block) -> int:
        """Get the block position in a region."""
        if block.parent is not self:
            raise ValueError("Block is not a child of the region.")
        return next(
            idx for idx, region_block in enumerate(self.blocks) if region_block is block
        )

    def detach_block(self, block: int | Block) -> Block:
        """
        Detach a block from the region.
        Returns the detached block.
        """
        if isinstance(block, int):
            block = self.blocks[block]
        else:
            if block.parent is not self:
                raise ValueError("Block is not a child of the region.")

        block.parent = None
        if (prev_block := block.prev_block) is None:
            self._first_block = block.next_block
        else:
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                block.next_block
            )
        if (next_block := block.next_block) is None:
            self._last_block = block.prev_block
        else:
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                block.prev_block
            )

        # Detach linked list from block
        block._prev_block = None  # pyright: ignore[reportPrivateUsage]
        block._next_block = None  # pyright: ignore[reportPrivateUsage]

        return block

    def erase_block(self, block: int | Block, safe_erase: bool = True) -> None:
        """
        Erase a block from the region.
        If safe_erase is True, check that the block has no uses.
        """
        block = self.detach_block(block)
        block.erase(safe_erase=safe_erase)

    def clone(self) -> Region:
        """
        Clone the entire region into a new one.
        """
        new_region = Region()
        self.clone_into(new_region)
        return new_region

    def clone_into(
        self,
        dest: Region,
        insert_index: int | None = None,
        value_mapper: dict[SSAValue, SSAValue] | None = None,
        block_mapper: dict[Block, Block] | None = None,
        *,
        clone_name_hints: bool = True,
        clone_operands: bool = True,
    ):
        """
        Clone all block of this region into `dest` to position `insert_index`
        """
        assert dest != self
        if insert_index is None:
            insert_index = len(dest.blocks)
        if value_mapper is None:
            value_mapper = {}
        if block_mapper is None:
            block_mapper = {}

        new_blocks: list[Block] = []

        # Clone all blocks without their contents, and register the block mapping
        # This ensures that operations can refer to blocks that are not yet cloned
        for block in self.blocks:
            new_block = Block()
            new_blocks.append(new_block)
            block_mapper[block] = new_block

        dest.insert_block(new_blocks, insert_index)

        # Populate the blocks with the cloned operations
        for block, new_block in zip(self.blocks, new_blocks):
            for idx, block_arg in enumerate(block.args):
                new_block.insert_arg(block_arg.type, idx)
                new_arg = new_block.args[idx]
                value_mapper[block_arg] = new_arg
                if clone_name_hints:
                    new_arg.name_hint = block_arg.name_hint
            for op in block.ops:
                new_block.add_op(
                    op.clone(
                        value_mapper,
                        block_mapper,
                        clone_name_hints=clone_name_hints,
                        clone_operands=False,
                    )
                )
        # Handle cases where results may be created after their first use when walking
        # in lexicographic order.
        if clone_operands:
            for old, new in zip(self.walk(), dest.walk()):
                new.operands = tuple(
                    value_mapper.get(operand, operand) for operand in old.operands
                )

    def walk(
        self, *, reverse: bool = False, region_first: bool = False
    ) -> Iterator[Operation]:
        """
        Call a function on all operations contained in the region.
        If region_first is set, then the operation regions are iterated before the
        operation. If reverse is set, then the region, block, and operation lists are
        iterated in reverse order.
        """
        for block in reversed(self.blocks) if reverse else self.blocks:
            yield from block.walk(reverse=reverse, region_first=region_first)

    def verify(self) -> None:
        for block in self.blocks:
            block.verify()
            if block.parent != self:
                raise ValueError(
                    "Parent pointer of block does not refer to containing region"
                )

    def drop_all_references(self) -> None:
        """
        Drop all references to other operations.
        This function is called prior to deleting a region.
        """
        self.parent = None
        for block in self.blocks:
            block.drop_all_references()

    def erase(self) -> None:
        """
        Erase the region, and remove all its references to other operations.
        """
        assert self.parent, (
            "Regions with parents should first be " + "detached before erasure."
        )
        self.drop_all_references()

    def move_blocks(self, region: Region) -> None:
        """
        Move the blocks of this region to another region. Leave no blocks in this region.
        """
        if region is self:
            raise ValueError("Cannot move region into itself.")
        self_first_block = self._first_block
        if self_first_block is None:
            return
        self_last_block = self._last_block
        assert self_last_block is not None
        other_last_block = region.last_block
        if other_last_block is None:
            region._first_block = self._first_block
        else:
            self_first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                other_last_block
            )
            other_last_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                self_first_block
            )
        region._last_block = self_last_block

        for block in self.blocks:
            block.parent = region

        self._first_block = None
        self._last_block = None

    def move_blocks_before(self, target: Block) -> None:
        """
        Move the blocks of this region to another region, before the target block.
        Leave no blocks in this region.
        """
        region = target.parent
        if region is self:
            raise ValueError("Cannot move region into itself.")
        if region is None:
            raise ValueError("Cannot inline region before a block with no parent")

        first_block = self._first_block
        if not first_block:
            return
        last_block = self._last_block
        assert last_block is not None

        if target.prev_block is None:
            region._first_block = first_block
        else:
            target.prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                first_block
            )
            first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                target.prev_block
            )

        for block in self.blocks:
            block.parent = region

        last_block._next_block = target  # pyright: ignore[reportPrivateUsage]
        target._prev_block = last_block  # pyright: ignore[reportPrivateUsage]

        self._first_block = None
        self._last_block = None

    def is_structurally_equivalent(
        self,
        other: IRNode,
        context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
    ) -> bool:
        """
        Check if two regions are structurally equivalent.
        The context is a mapping of IR nodes to IR nodes that are already known
        to be equivalent. This enables checking whether the use dependencies and
        successors are equivalent.
        """
        if context is None:
            context = {}
        if not isinstance(other, Region):
            return False
        if len(self.blocks) != len(other.blocks):
            return False
        # register all blocks in the context so we can check whether ops have
        # the corrects successors
        for block, other_block in zip(self.blocks, other.blocks):
            context[block] = other_block
        if not all(
            block.is_structurally_equivalent(other_block, context)
            for block, other_block in zip(self.blocks, other.blocks)
        ):
            return False
        return True

parent: Operation | None = field(default=None, repr=False) class-attribute instance-attribute

Operation containing the region.

parent_node: IRNode | None property

blocks: RegionBlocks property

A multi-pass iterable of blocks.

first_block: Block | None property

First block in this region. This is the entry block if present.

last_block: Block | None property

Last block in this region.

ops: BlockOps property

Get the operations of a single-block region. Returns an exception if the region is not single-block.

op: Operation property

Get the operation of a single-operation single-block region. Returns an exception if the region is not single-operation single-block.

block: Block property

Get the block of a single-block region. Returns an exception if the region is not single-block.

DEFAULT

A marker to be used as a default parameter to functions when a default single-block region should be constructed.

Source code in xdsl/ir/core.py
2317
2318
2319
2320
2321
class DEFAULT:
    """
    A marker to be used as a default parameter to functions when a default
    single-block region should be constructed.
    """

__init__(blocks: Block | Iterable[Block] = ())

Source code in xdsl/ir/core.py
2332
2333
2334
def __init__(self, blocks: Block | Iterable[Block] = ()):
    super().__init__()
    self.add_block(blocks)

parent_block() -> Block | None

Source code in xdsl/ir/core.py
2340
2341
def parent_block(self) -> Block | None:
    return self.parent.parent if self.parent else None

parent_op() -> Operation | None

Source code in xdsl/ir/core.py
2343
2344
def parent_op(self) -> Operation | None:
    return self.parent

parent_region() -> Region | None

Source code in xdsl/ir/core.py
2346
2347
2348
2349
2350
2351
def parent_region(self) -> Region | None:
    return (
        self.parent.parent.parent
        if self.parent is not None and self.parent.parent is not None
        else None
    )

find_ancestor_block_in_region(block: Block) -> Block | None

Returns 'block' if 'block' lies in this region, or otherwise finds the ancestor of 'block' that lies in this region.

Returns None if no ancestor block that lies in this region is found.

Source code in xdsl/ir/core.py
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
def find_ancestor_block_in_region(self, block: Block) -> Block | None:
    """
    Returns 'block' if 'block' lies in this region, or otherwise finds
    the ancestor of 'block' that lies in this region.

    Returns None if no ancestor block that lies in this region is found.
    """
    curr_block = block
    while curr_block.parent_region() != self:
        curr_block = curr_block.parent_block()
        if curr_block is None:
            return None

    return curr_block

__repr__() -> str

Source code in xdsl/ir/core.py
2385
2386
2387
def __repr__(self) -> str:
    short_reprs = ", ".join(_short_repr(block) for block in self.blocks)
    return f"Region(blocks=[{short_reprs}])"

add_block(block: Block | Iterable[Block]) -> None

Insert one or multiple blocks at the end of the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
def add_block(self, block: Block | Iterable[Block]) -> None:
    """
    Insert one or multiple blocks at the end of the region.
    The blocks should not be attached to another region.
    """
    blocks_iter: Iterator[Block]
    if isinstance(block, Block):
        blocks_iter = iter((block,))
    else:
        blocks_iter = iter(block)
    prev_block = self.last_block

    if prev_block is None:
        try:
            # First block
            prev_block = next(blocks_iter)
            self._attach_block(prev_block)
            self._first_block = prev_block
        except StopIteration:
            # blocks_iter is empty, nothing to do
            return

    try:
        while True:
            next_block = next(blocks_iter)
            self._attach_block(next_block)
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                prev_block
            )
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                next_block
            )
            prev_block = next_block

    except StopIteration:
        # Repair last block
        self._last_block = prev_block
        return

insert_block_before(block: Block | Iterable[Block], target: Block) -> None

Insert one or multiple blocks before a given block in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
def insert_block_before(
    self, block: Block | Iterable[Block], target: Block
) -> None:
    """
    Insert one or multiple blocks before a given block in the region.
    The blocks should not be attached to another region.
    """
    if target.parent is not self:
        raise ValueError(
            "Cannot insert blocks before a block into a region that is not the target's parent"
        )
    blocks_iter: Iterator[Block]
    if isinstance(block, Block):
        blocks_iter = iter((block,))
    else:
        blocks_iter = iter(block)
    prev_block = target.prev_block

    if prev_block is None:
        try:
            # First block
            new_first = next(blocks_iter)
            self._attach_block(new_first)
            self._first_block = new_first
            new_first._next_block = target  # pyright: ignore[reportPrivateUsage]
            prev_block = new_first
        except StopIteration:
            # blocks_iter is empty, nothing to do
            return

    # The invariant for the loop is that prev_block is always before target when
    # calling `next`.

    try:
        while True:
            next_block = next(blocks_iter)
            self._attach_block(next_block)
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                prev_block
            )
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                next_block
            )
            prev_block = next_block

    except StopIteration:
        # Repair broken link
        prev_block._next_block = target  # pyright: ignore[reportPrivateUsage]
        target._prev_block = prev_block  # pyright: ignore[reportPrivateUsage]
        return

insert_block_after(block: Block | Iterable[Block], target: Block) -> None

Insert one or multiple blocks after a given block in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
def insert_block_after(self, block: Block | Iterable[Block], target: Block) -> None:
    """
    Insert one or multiple blocks after a given block in the region.
    The blocks should not be attached to another region.
    """
    next_block = target.next_block
    if next_block is None:
        self.add_block(block)
    else:
        self.insert_block_before(block, next_block)

insert_block(blocks: Block | Iterable[Block], index: int) -> None

Insert one or multiple blocks at a given index in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
def insert_block(self, blocks: Block | Iterable[Block], index: int) -> None:
    """
    Insert one or multiple blocks at a given index in the region.
    The blocks should not be attached to another region.
    """
    i = -1
    for i, b in enumerate(self.blocks):
        if i == index:
            self.insert_block_before(blocks, b)
            return
    if i + 1 == index:
        # Append block
        self.add_block(blocks)

get_block_index(block: Block) -> int

Get the block position in a region.

Source code in xdsl/ir/core.py
2557
2558
2559
2560
2561
2562
2563
def get_block_index(self, block: Block) -> int:
    """Get the block position in a region."""
    if block.parent is not self:
        raise ValueError("Block is not a child of the region.")
    return next(
        idx for idx, region_block in enumerate(self.blocks) if region_block is block
    )

detach_block(block: int | Block) -> Block

Detach a block from the region. Returns the detached block.

Source code in xdsl/ir/core.py
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
def detach_block(self, block: int | Block) -> Block:
    """
    Detach a block from the region.
    Returns the detached block.
    """
    if isinstance(block, int):
        block = self.blocks[block]
    else:
        if block.parent is not self:
            raise ValueError("Block is not a child of the region.")

    block.parent = None
    if (prev_block := block.prev_block) is None:
        self._first_block = block.next_block
    else:
        prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            block.next_block
        )
    if (next_block := block.next_block) is None:
        self._last_block = block.prev_block
    else:
        next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            block.prev_block
        )

    # Detach linked list from block
    block._prev_block = None  # pyright: ignore[reportPrivateUsage]
    block._next_block = None  # pyright: ignore[reportPrivateUsage]

    return block

erase_block(block: int | Block, safe_erase: bool = True) -> None

Erase a block from the region. If safe_erase is True, check that the block has no uses.

Source code in xdsl/ir/core.py
2596
2597
2598
2599
2600
2601
2602
def erase_block(self, block: int | Block, safe_erase: bool = True) -> None:
    """
    Erase a block from the region.
    If safe_erase is True, check that the block has no uses.
    """
    block = self.detach_block(block)
    block.erase(safe_erase=safe_erase)

clone() -> Region

Clone the entire region into a new one.

Source code in xdsl/ir/core.py
2604
2605
2606
2607
2608
2609
2610
def clone(self) -> Region:
    """
    Clone the entire region into a new one.
    """
    new_region = Region()
    self.clone_into(new_region)
    return new_region

clone_into(dest: Region, insert_index: int | None = None, value_mapper: dict[SSAValue, SSAValue] | None = None, block_mapper: dict[Block, Block] | None = None, *, clone_name_hints: bool = True, clone_operands: bool = True)

Clone all block of this region into dest to position insert_index

Source code in xdsl/ir/core.py
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
def clone_into(
    self,
    dest: Region,
    insert_index: int | None = None,
    value_mapper: dict[SSAValue, SSAValue] | None = None,
    block_mapper: dict[Block, Block] | None = None,
    *,
    clone_name_hints: bool = True,
    clone_operands: bool = True,
):
    """
    Clone all block of this region into `dest` to position `insert_index`
    """
    assert dest != self
    if insert_index is None:
        insert_index = len(dest.blocks)
    if value_mapper is None:
        value_mapper = {}
    if block_mapper is None:
        block_mapper = {}

    new_blocks: list[Block] = []

    # Clone all blocks without their contents, and register the block mapping
    # This ensures that operations can refer to blocks that are not yet cloned
    for block in self.blocks:
        new_block = Block()
        new_blocks.append(new_block)
        block_mapper[block] = new_block

    dest.insert_block(new_blocks, insert_index)

    # Populate the blocks with the cloned operations
    for block, new_block in zip(self.blocks, new_blocks):
        for idx, block_arg in enumerate(block.args):
            new_block.insert_arg(block_arg.type, idx)
            new_arg = new_block.args[idx]
            value_mapper[block_arg] = new_arg
            if clone_name_hints:
                new_arg.name_hint = block_arg.name_hint
        for op in block.ops:
            new_block.add_op(
                op.clone(
                    value_mapper,
                    block_mapper,
                    clone_name_hints=clone_name_hints,
                    clone_operands=False,
                )
            )
    # Handle cases where results may be created after their first use when walking
    # in lexicographic order.
    if clone_operands:
        for old, new in zip(self.walk(), dest.walk()):
            new.operands = tuple(
                value_mapper.get(operand, operand) for operand in old.operands
            )

walk(*, reverse: bool = False, region_first: bool = False) -> Iterator[Operation]

Call a function on all operations contained in the region. If region_first is set, then the operation regions are iterated before the operation. If reverse is set, then the region, block, and operation lists are iterated in reverse order.

Source code in xdsl/ir/core.py
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
def walk(
    self, *, reverse: bool = False, region_first: bool = False
) -> Iterator[Operation]:
    """
    Call a function on all operations contained in the region.
    If region_first is set, then the operation regions are iterated before the
    operation. If reverse is set, then the region, block, and operation lists are
    iterated in reverse order.
    """
    for block in reversed(self.blocks) if reverse else self.blocks:
        yield from block.walk(reverse=reverse, region_first=region_first)

verify() -> None

Source code in xdsl/ir/core.py
2681
2682
2683
2684
2685
2686
2687
def verify(self) -> None:
    for block in self.blocks:
        block.verify()
        if block.parent != self:
            raise ValueError(
                "Parent pointer of block does not refer to containing region"
            )

drop_all_references() -> None

Drop all references to other operations. This function is called prior to deleting a region.

Source code in xdsl/ir/core.py
2689
2690
2691
2692
2693
2694
2695
2696
def drop_all_references(self) -> None:
    """
    Drop all references to other operations.
    This function is called prior to deleting a region.
    """
    self.parent = None
    for block in self.blocks:
        block.drop_all_references()

erase() -> None

Erase the region, and remove all its references to other operations.

Source code in xdsl/ir/core.py
2698
2699
2700
2701
2702
2703
2704
2705
def erase(self) -> None:
    """
    Erase the region, and remove all its references to other operations.
    """
    assert self.parent, (
        "Regions with parents should first be " + "detached before erasure."
    )
    self.drop_all_references()

move_blocks(region: Region) -> None

Move the blocks of this region to another region. Leave no blocks in this region.

Source code in xdsl/ir/core.py
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
def move_blocks(self, region: Region) -> None:
    """
    Move the blocks of this region to another region. Leave no blocks in this region.
    """
    if region is self:
        raise ValueError("Cannot move region into itself.")
    self_first_block = self._first_block
    if self_first_block is None:
        return
    self_last_block = self._last_block
    assert self_last_block is not None
    other_last_block = region.last_block
    if other_last_block is None:
        region._first_block = self._first_block
    else:
        self_first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            other_last_block
        )
        other_last_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            self_first_block
        )
    region._last_block = self_last_block

    for block in self.blocks:
        block.parent = region

    self._first_block = None
    self._last_block = None

move_blocks_before(target: Block) -> None

Move the blocks of this region to another region, before the target block. Leave no blocks in this region.

Source code in xdsl/ir/core.py
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
def move_blocks_before(self, target: Block) -> None:
    """
    Move the blocks of this region to another region, before the target block.
    Leave no blocks in this region.
    """
    region = target.parent
    if region is self:
        raise ValueError("Cannot move region into itself.")
    if region is None:
        raise ValueError("Cannot inline region before a block with no parent")

    first_block = self._first_block
    if not first_block:
        return
    last_block = self._last_block
    assert last_block is not None

    if target.prev_block is None:
        region._first_block = first_block
    else:
        target.prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            first_block
        )
        first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            target.prev_block
        )

    for block in self.blocks:
        block.parent = region

    last_block._next_block = target  # pyright: ignore[reportPrivateUsage]
    target._prev_block = last_block  # pyright: ignore[reportPrivateUsage]

    self._first_block = None
    self._last_block = None

is_structurally_equivalent(other: IRNode, context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None) -> bool

Check if two regions are structurally equivalent. The context is a mapping of IR nodes to IR nodes that are already known to be equivalent. This enables checking whether the use dependencies and successors are equivalent.

Source code in xdsl/ir/core.py
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
def is_structurally_equivalent(
    self,
    other: IRNode,
    context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
) -> bool:
    """
    Check if two regions are structurally equivalent.
    The context is a mapping of IR nodes to IR nodes that are already known
    to be equivalent. This enables checking whether the use dependencies and
    successors are equivalent.
    """
    if context is None:
        context = {}
    if not isinstance(other, Region):
        return False
    if len(self.blocks) != len(other.blocks):
        return False
    # register all blocks in the context so we can check whether ops have
    # the corrects successors
    for block, other_block in zip(self.blocks, other.blocks):
        context[block] = other_block
    if not all(
        block.is_structurally_equivalent(other_block, context)
        for block, other_block in zip(self.blocks, other.blocks)
    ):
        return False
    return True