Phase 1, week 3retrospective

Dialect conversion

DialectConversion rewrites ops toward a target legality, by predicate or op list, and full conversion differs from partial.


Legality is the declaration

// sketch: MyTensorType stands for a type your dialect defines.
ConversionTarget target(getContext());
target.addLegalDialect<arith::ArithDialect, func::FuncDialect>();
target.addDynamicallyLegalOp<func::FuncOp>([](func::FuncOp f) {
  return llvm::none_of(f.getArgumentTypes(),
                       [](Type t) { return isa<MyTensorType>(t); });
});

The target states which operations are acceptable after conversion; it says nothing about how to get there. Anything not legal must be matched by a pattern, and a pattern that matches an operation the target already considers legal does not fire. markUnknownOpDynamicallyLegal is the escape hatch, and using it broadly is how a conversion stops converting.

Patterns carry a type converter

OpConversionPattern receives a ConversionPatternRewriter and the pattern’s TypeConverter:

struct LowerMake : OpConversionPattern<MyMakeOp> {
  using OpConversionPattern::OpConversionPattern;
  LogicalResult matchAndRewrite(MyMakeOp op, OpAdaptor adaptor,
                                ConversionPatternRewriter &rewriter) const override {
    Type ty = getTypeConverter()->convertType(op.getType());
    if (ty) {
      rewriter.replaceOpWithNewOp<memref::AllocOp>(op, ty.cast<MemRefType>());
      return success();
    }
    return rewriter.notifyMatchFailure(op, "no conversion for result type");
  }
};

TypeConverter::addConversion registers the type rules, and source and target materialisations tell the framework how to bridge a type it cannot convert directly. The snippet is a sketch of the API, not a compilable pass.

Unrealized casts mark the seam

When an operand’s type changes and no materialisation exists, the driver inserts builtin.unrealized_conversion_cast so the IR stays valid. Survivors are an unfinished conversion. -reconcile-unrealized-casts removes redundant pairs, and mlir-translate rejects a module that still contains one. Confirm the pass name on your pinned release.

Full or partial conversion

applyFullConversion succeeds only if every operation in the converted subtree ends up legal, so an operation nobody wrote a pattern for is an error at the point it appears. applyPartialConversion leaves anything you never marked illegal in place, which is what makes it useful on mixed IR and what allows a missing lowering to pass as success.

A lowering to a self-contained target dialect wants full conversion. A partial lowering that only rewrites a few ops wants partial conversion, scoped to the dialect it touches. Confirm both names against the header on your release.

Things that break

A dynamic legality callback that is too permissive allows an operation to stay, the pass reports success, and the leftovers fail in a later pass or at translation. The other failure is structural: marking an op legal does not exempt its regions, so a lowering can half-convert a body while the outer op looks settled.

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

You mark the `func` dialect legal and expect the function bodies to be left alone. What does the driver do?

`mlir-opt` exits zero but the output still contains `builtin.unrealized_conversion_cast`. What does that mean?

Which conversion mode should a lowering to LLVM use?

0 / 3 correct

← back to the map