Phase 1, week 3retrospective

Traits and interfaces

Traits make structural and semantic assertions that generic code trusts blindly, so a wrong trait is a miscompile, not a style issue.


Traits are assertions in the type

A trait is part of an operation’s C++ type. Pure, SingleBlock, NOperands<2>::Impl, and SingleBlockImplicitTerminator<scf::YieldOp> are mixed into the op definition, so Op<..., [Pure]> asserts, where the op is declared, that it has no memory effects. You cannot attach a trait to an op another dialect owns, and its hooks are static: verifyTrait runs during verification, and the trait adds no dispatch of its own.

Traits are what a pass checks before reasoning structurally. hasTrait<OpTrait::IsolatedFromAbove>() licenses caching values across a region; SingleBlock licenses front(). Note that hasTrait works on an Operation *, so traits are queryable at runtime — they are simply not definable outside the op.

Interfaces are behaviour

An op interface declares methods an operation implements, so a pass can call them without knowing the concrete type:

if (auto linalgOp = dyn_cast<linalg::LinalgOp>(op)) {
  SmallVector<AffineMap> maps = linalgOp.getIndexingMapsArray();
  // ...
}

linalg::LinalgOp covers linalg.matmul and linalg.generic alike. DestinationStyleOpInterface, TilingInterface, and BufferizableOpInterface cover ops from unrelated dialects, and dispatch is virtual, so one pass handles every implementer.

Interfaces also take external models: an implementation attached to an operation from another dialect. Bufferization depends on this — BufferizableOpInterface implementations for tensor ops live outside the tensor dialect, in a dialect’s Transforms build unit.

Choosing one

Ask whether you need a guarantee or a call. Structure — one region, one block, a terminator, no effects — is a trait. Behaviour a generic pass invokes is an interface. A case that needs both uses the trait for the guarantee and the interface for the call.

Things that break

Marking an op Pure when it reads or allocates memory is a miscompile waiting for -cse: the optimiser reuses one load for another and nothing verifies the trait. On the interface side the failure is silent differently: register the external model in a dialect that never gets loaded and dyn_cast returns null, so the pattern never fires and no error appears. If a pass reports less work than you expected, check dialect registration before the pattern logic.

Sources

A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.

Check yourself

A generic pass must call `getIterationDomain` on `linalg.matmul` and on `scf.forall` alike. What carries that?

You add `Pure` to an op that reads a memref. What is the consequence?

Which of these can an external model do that a trait cannot?

0 / 3 correct

← back to the map