I’ve been running into an issue with my compiler lowering from its dialect to the LLVM dialect when lowering to llvm.invoke
. The problem appears to be that some point after conversion from my dialect’s invoke
to llvm.invoke
, something is modifying the successor operands for the normal
block to contain the return value of the llvm.invoke
operation itself:
%12 = llvm.invoke @"abs/1"(%8, %12) to ^bb6(%12 : !llvm.i64) unwind ^bb8 : (!llvm.i64, !llvm.i64) -> !llvm.i64
Notice how the return value of the invoke is also somehow an operand to the successofr block ^bb6
? The error I get for this is error: operand #1 does not dominate this use
, which makes perfect sense; but I’m not sure how it is getting mangled into this form. My assumption at this point is that some transformation in MLIR is performing an optimization that requires converting the return value of the invoke into a block argument, but it never checks whether that transformation is legal.
For my part, in an effort to troubleshoot this, I have all of my optimizations turned off; I’m not performing any canonicalizations that combine blocks or operations, basically just converting directly from my dialect to LLVM dialect, without going through the Standard dialect at all. The only passes I’m running at this point are the conversion pass itself, and a canonicalization pass before and after (which for my IR is a no-op, because I’ve disabled them, and as far as I can tell, the LLVM dialect has no canonicalizations either).
Here’s the relevant chunk of my dialect’s IR that then gets lowered to LLVM dialect (completely unoptimized, this is essentially translated directly from the frontend); it basically represents the equivalent of try { return abs(non_number()); } catch (exception) { return exception; }:
eir.func @"init:start/0"() -> !eir.term attributes {personality = @lumen_eh_personality} {
%0 = llvm.mlir.null : !llvm<"i8*">
%1 = eir.invoke @"non_number/0" to ^bb1 unwind ^bb12 : !eir.term
^bb1: // pred: ^bb0
eir.br ^bb2(%1 : !eir.term)
^bb2(%2: !eir.term): // pred: ^bb1
%3 = eir.invoke @"abs/1"(%2 : !eir.term) to ^bb9 unwind ^bb10 : !eir.term
^bb3: // pred: ^bb4
eir.unreachable
^bb4: // pred: ^bb16
eir.br ^bb3
^bb5: // pred: ^bb8
eir.return %14 : !eir.term
..snip..
^bb9: // pred: ^bb2
eir.return %3 : !eir.term
^bb10: // pred: ^bb2
...code path which ultimately leads to ^bb5..
To confirm that this wasn’t something I was during prior to converting to llvm.invoke
, here’s what I see in the debug output during conversion:
//===-------------------------------------------===//
Legalizing operation : 'llvm.invoke'(0x7fdb8df0d7d0) {
%8 = "llvm.invoke"()[^bb3, ^bb9] {callee = @"non_number/0", operand_segment_sizes = dense<0> : vector<3xi32>} : () -> !llvm.i64
} -> SUCCESS : operation marked legal by the target
...snip..
//===-------------------------------------------===//
Legalizing operation : 'llvm.invoke'(0x7fdb8df0ef50) {
%13 = "llvm.invoke"(%9)[^bb8, ^bb9] {callee = @"abs/1", operand_segment_sizes = dense<[1, 0, 0]> : vector<3xi32>} : (!eir.term) -> !llvm.i64
} -> SUCCESS : operation marked legal by the target
Conversion itself succeeds, but fails verification (for obvious reasons).
So it appears to me that the initial conversion preserves the representation in my own IR, which itself is modeled after the way LLVM itself requires invokes to be laid out - namely that the return value is referenced directly from the normal block, not passed as a block argument. If there is a way for me to represent the return value of the invoke as an implicit argument to the successor block, and have that address the issue, I’m happy to make that change, but when I initially started using MLIR that wasn’t possible, or at least I wasn’t able to figure out how to represent it that way.
I’m starting to bang my head against the wall a bit, so wanted to see if anyone has suggestions on how best to troubleshoot this, or if I’ve found a bug. Happy to help test things out or provide more details if that’s helpful. Just let me know!
Paul