Recent patches attempting to introduce the equivalents of indirectbr
and switch
LLVM IR instructions into the LLVM dialect highlighted a design choice in the LLVM dialect that we had postponed: how should we deal with duplicate successors in the LLVM dialect?
Unlike LLVM IR, MLIR does not have explicit PHI nodes. Instead, it uses basic block arguments. The latter allow a terminator to list the same successor more than once with different arguments.
^bb0:
cond_br %cond, ^bb1[%0: i64], ^bb1[%1: i64]
^bb1(%arg0: i64): // preds: ^bb0
This is not directly expressible in the LLVM IR using phi
instruction since it uses block labels of the predecessor to specify to which value in the block it refers. (phi i64 [%2, bb1], [%3, bb1]
is not valid. Same applies to other LLVM IR terminators with more than one successor.
Currently, we disallow conditional branches in the LLVM dialect to have identical successors. The reason is partly legacy (this was implemented before we supported custom terminator operations) and partly the overarching idea to keep the translation from the LLVM dialect to LLVM IR as trivial as possible. Furthermore, one can argue that the LLVM dialect should not support cases that are not supported by LLVM IR.
The problematic situation above is currently resolved in the lowering from the standard to the LLVM dialect by introducing an additional block that can be used in PHI to differentiate predecessors.
^bb0:
cond_br %cond ^bb1[%0: i64], ^bb2[%1: i64]
^bb2(%arg0: i64):
br ^bb1[%arg0: i64]
^bb1(%arg1: i64): // preds ^bb0, ^bb1
An alternative lowering could be to use select
and unconditionalize the branch.
^bb0:
%2 = select %cond, %0, %1 : i64
br ^bb1[%2: i64]
Note that both solutions should be seen as relying on the semantics of the operation being transformed, cond_br
in this case: they need to know that block arguments are forwarded without modifications to the blocks. This will get more important after the special treatment of successor operands is removed.
If we follow the same reasoning as above, we should also disallow repeated successors with identical arguments for indirectbr
, switch
, callbr
and potentially catchswitch
operations.
Alternatively, we may decide to allow repeated successors with different arguments and perform the block duplication/select injection at a later stage. This embedding will fit better into the MLIR’s flavor of SSA, and is more concise. However, depending on the place of repeated-successor-removal legalization, may get (A) some constructs in the LLVM dialect are not convertible to LLVM IR (legalization necessary before calling the conversion); or (B) the translation from the LLVM dialect to LLVM IR becomes less trivial and may inject blocks or operations that were not present in the input.
My order of preference is (1) disallow constructs that are not expressible in the LLVM IR; (2) use (A); (3) use (B). The rationale for the first priority is that the LLVM dialect models LLVM IR closely and if we accept this divergence from the LLVM IR, we are opening the way to other divergences, which creates a danger of having multiple subtle mismatches that users will have to constantly keep in mind.
Any preferences from other interested parties?
Ping @clattner, @mehdi_amini, @River707 specifically.