Replacing a result value with multiple values (Error)

Hi.

I’m trying to write a lowering pass that converts an op returning a single value to multiple ops. The result of the original op will need to be replaced by all the new values.

Here is an example:

%1 = foo() : MultipleIntegersType

%2 = bar(%1)

Should get lowered to:

%1 = constant 42 : Integer
%2 = constant 56 : Integer

%3 = addi(%1, %2) : Integer

Notice that the operation “foo” with custom type “MultipleIntegersType” got lowered to multiple ops.

I wrote a custom type conversion:

typeConverter.addConversion(
      [](MultipleIntegersType t, SmallVectorImpl<Type>& result) -> llvm::Optional<LogicalResult> {
        // omitted: place correct number of integers into result here
        return success();
      });

Also in pseudo-code, here is how I wrote the lowering pattern for foo:

auto intOne = rewriter.create<IntegerOp>(...);
auto intTwo = rewriter.create<IntegerOp>(...);

rewriter.replaceOp(originalOp, ValueRange{intOne, intTwo});

However, this doesn’t seem to work: I always get some variation of “wrong number of results” on the rewriter.replaceOp line. This seems counter-intuitive to me - why hasn’t the type converter been applied, which would result in the result type being multiple integers?

Is there any way to achieve what I want? I realise I could just pack the values into a struct and lower the MultipleIntegersType into a single struct, but I’d like to avoid that if possible.

There is currently no support for one-to-many value replacement in the dialect conversion infrastructure.

Only partial support is available in block argument conversion, where it is supported by the type converter providing a way to pack multiple values back into one:

^bb0(%arg0: !compound-type):
  "use"(%arg0)

gets converted into

^bb0(%arg0: !primitive-type, %arg1: !primitive-type):
  %0 = "op-generated-by-type-converter"(%arg0, %arg1) 
      : (!primitive-type, !primitive-type) -> !compund-type

This mechanism could be potentially extended to op results, but that would likely require breaking API changes and will take time.

In the meantime, you can introduce packing and extraction ops for your types in the conversion process, and then canonicalize them away in a separate step by replacing

%0 = "pack"(%1, %2)
%3 = "get"(%0) {position=0}
%4 = "get"(%0) {position=1}
"use"(%3)
"use"(%4)

with

"use"(%1)
"use"(%2)

Thank you, this is a great solution!

Hi, I’m now working on a problem which is very similar to @hannessolo 's problem:

And @ftynse 's solution is:

My question is:

(1) Is there easier way to do one-to-many replacement now in 2023? Since this problem was answered in 2021.

(2) It seems that vector::InsertElementOp and vector::ExtractElementOp are designed for this situation. But how can I define an initial vector value before I insert elements into it? I noticed that for LLVM::InsertElementOp and LLVM::ExtractElementOp, there is a op called llvm.undef, which intializes a vector value or a struct value. What is the “undef” op for vector::InsertElementOp and vector::ExtractElementOp?

Thanks!

This has since been implemented: llvm-project/OneToNTypeConversion.h at main · llvm/llvm-project · GitHub

In absence of undef you can always start with a zero constant vector and insert into it.

Thank you very much! @mehdi_amini