15 Trait
A trait is reusable behavior or an invariant attached to an operation definition. It lets a dialect state that several operation classes share a structural fact without copying the same verification and API machinery into every class.
Traits are declared in ODS/TableGen and become part of an operation’s static contract. Typical examples identify terminators, require all operands and results to have the same type, establish symbol identity, or constrain region shape.
15.1 Traits Express Facts The Framework Can Reuse
A branch terminator and a return terminator have different meanings, but both must end their parent block. The terminator trait lets generic block and region verification enforce that sequencing rule without a hard-coded list of dialect operation names. Likewise, a same-type trait can share a routine constraint across a family of arithmetic operations.
Traits make invariants visible in the operation definition. A reader can learn part of an operation’s contract before reading handwritten C++, and a generic utility can query a trait rather than know every concrete operation class.
15.2 Structural And Semantic Traits
Many traits are structural: zero regions, one region, single-block region, terminator, or same operand/result type. Others establish semantic conventions, such as symbol-like identity or isolation from values above a region. Neither category is a casual annotation. A single-block operation cannot safely be given a second block by a generic CFG pass; an isolated operation changes which SSA captures are legal and can therefore be cloned or processed independently.
A trait does not express every related rule. A terminator trait says that an operation ends a block; it does not itself specify which values may be returned or which successor arguments are valid. The parent operation and dialect verifier provide those additional constraints.
15.3 Traits And Interfaces Are Different Tools
A trait is generally a static, declarative property applied to every instance of an operation class. An interface is a callable API with operation-specific implementation. Traits can add methods and verifiers, but they do not replace an interface when generic code needs a dynamic question such as which resources an operation reads or writes.
Use a trait when the property is inherent in the class and shared declaratively. Use an interface when generic infrastructure must ask for behavior or data. Well-designed operations often use both: a region trait may establish shape while a loop or memory-effect interface explains semantic behavior.
15.4 Composition And Design
Traits compose, so an operation may simultaneously be a terminator, have no results, and impose a region constraint. That reduces boilerplate, but incompatible traits can also describe an impossible operation. Check generated documentation and verification tests whenever adding a trait: its effects may extend to parsing, builders, canonicalizers, and generic passes.
Do not use a trait as a label for behavior that has no enforced meaning. If a pass must rely on a property, give it a verifier-backed representation or an interface. A comment calling an operation loop-like is much less useful than a real loop interface and a defined body/terminator contract.