[Mlir-commits] [mlir] [mlir][emitc] Lower multiple results as a struct (PR #200659)
Gil Rapaport
llvmlistbot at llvm.org
Fri Jun 5 07:57:31 PDT 2026
https://github.com/aniragil updated https://github.com/llvm/llvm-project/pull/200659
>From 33b56ec232723d095e86e7c26947173c7a16f363 Mon Sep 17 00:00:00 2001
From: Gil Rapaport <gil.rapaport at mobileye.com>
Date: Mon, 18 May 2026 12:14:08 +0300
Subject: [PATCH] [mlir][emitc] Lower multiple results as a struct
Previously, func to emitc lowering rejected func.{func,call,return}
with more than one result/operand. This patch lifts that restriction by
packing multiple return values into an automatically-generated struct,
e.g. for a function returning (i32, i32):
emitc.class struct @return_i32_i32 {
emitc.field @field0 : i32
emitc.field @field1 : i32
}
On return, the operands are packed into a local struct variable which is
then loaded and returned. On call sites, the struct is stored in a local
variable, and each field is extracted to recreate the individual SSA
values of the original results. As with single-result functions,
`emitc.array` return types are not supported.
If a class with that name already exists, it is verified to have exactly
the expected fields with the correct types and no methods. Two functions
with the same return type tuple share a single class definition.
Assisted-by: Copilot
---
.../Conversion/FuncToEmitC/FuncToEmitC.cpp | 224 ++++++++++++++++--
.../FuncToEmitC/func-to-emitc-failed.mlir | 86 +++++++
.../Conversion/FuncToEmitC/func-to-emitc.mlir | 94 ++++++++
mlir/test/Target/Cpp/func.mlir | 63 +++++
4 files changed, 441 insertions(+), 26 deletions(-)
diff --git a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
index 4801f07d82c9f..95b165a61aab9 100644
--- a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
+++ b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
@@ -16,12 +16,121 @@
#include "mlir/Conversion/ConvertToEmitC/ToEmitCInterface.h"
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/SymbolTable.h"
#include "mlir/Transforms/DialectConversion.h"
+#include "llvm/ADT/StringExtras.h"
using namespace mlir;
namespace {
+//===----------------------------------------------------------------------===//
+// Multi-return struct helpers
+//===----------------------------------------------------------------------===//
+
+// Looks up or creates an `emitc.class` named after `types` in the nearest
+// enclosing symbol table of `op`, suitable for packing those types as plain
+// struct fields (field0, field1, ...). If the class already exists it is
+// verified to have exactly the right fields and no methods. Returns the
+// corresponding !emitc.opaque<"struct ..."> type on success.
+static FailureOr<emitc::OpaqueType>
+getOrCreateMultiReturnType(ConversionPatternRewriter &rewriter, Location loc,
+ Operation *op, TypeRange types) {
+ // Build the struct name from the types, e.g. "return_i32_i32". Each type is
+ // printed and non-alphanumeric characters are replaced with '_'.
+ std::string structName = "return";
+ for (Type type : types) {
+ std::string typeName;
+ llvm::raw_string_ostream os(typeName);
+ type.print(os);
+ std::replace_if(
+ typeName.begin(), typeName.end(),
+ [](char c) { return !llvm::isAlnum(c); }, '_');
+ structName += "_" + typeName;
+ }
+
+ // Find the enclosing symbol table and the direct child op within it that
+ // contains `op`; the class will be inserted immediately before that child.
+ Operation *symbolTableOp = SymbolTable::getNearestSymbolTable(op);
+ Operation *insertBefore = op;
+ while (insertBefore->getParentOp() != symbolTableOp)
+ insertBefore = insertBefore->getParentOp();
+
+ if (Operation *sym = SymbolTable::lookupSymbolIn(symbolTableOp, structName)) {
+ auto classOp = dyn_cast<emitc::ClassOp>(sym);
+ if (!classOp)
+ return emitError(loc) << "symbol '" << structName
+ << "' exists but is not an emitc.class";
+
+ if (classOp.getClassType() != emitc::ClassType::struct_)
+ return emitError(loc)
+ << "existing class '" << structName << "' is not a struct";
+
+ SmallVector<emitc::FieldOp> fields;
+ for (Operation &bodyOp : classOp.getBody().front()) {
+ if (isa<emitc::FuncOp>(bodyOp))
+ return emitError(loc) << "existing class '" << structName
+ << "' has methods; expected a plain struct";
+ if (auto fieldOp = dyn_cast<emitc::FieldOp>(bodyOp))
+ fields.push_back(fieldOp);
+ }
+ if (fields.size() != types.size())
+ return emitError(loc) << "existing class '" << structName
+ << "' has wrong number of fields";
+ for (auto [i, fieldOp] : llvm::enumerate(fields)) {
+ if (fieldOp.getSymName() != "field" + std::to_string(i))
+ return emitError(loc) << "existing class '" << structName
+ << "': unexpected field name at index " << i;
+ if (fieldOp.getTypeAttr().getValue() != types[i])
+ return emitError(loc) << "existing class '" << structName
+ << "': wrong type for field " << i;
+ }
+ } else {
+ // Create the ClassOp before `insertBefore`, then restore the insertion
+ // point.
+ auto savedIP = rewriter.saveInsertionPoint();
+ rewriter.setInsertionPoint(insertBefore);
+
+ emitc::ClassOp classOp = emitc::ClassOp::create(rewriter, loc, structName,
+ /*final_specifier=*/false,
+ emitc::ClassType::struct_);
+ rewriter.createBlock(&classOp.getBody());
+ rewriter.setInsertionPointToStart(&classOp.getBody().front());
+
+ for (auto [i, type] : llvm::enumerate(types)) {
+ auto fieldName = rewriter.getStringAttr("field" + std::to_string(i));
+ emitc::FieldOp::create(rewriter, loc, fieldName, TypeAttr::get(type),
+ nullptr);
+ }
+
+ rewriter.restoreInsertionPoint(savedIP);
+ }
+ return emitc::OpaqueType::get(rewriter.getContext(), "struct " + structName);
+}
+
+// Packs multiple SSA values into an emitc.class struct variable and loads the
+// result as a single SSA value of the opaque struct type.
+static Value packValuesIntoStruct(ConversionPatternRewriter &rewriter,
+ Location loc, ValueRange values,
+ emitc::OpaqueType structType) {
+ MLIRContext *ctx = rewriter.getContext();
+ auto noInit = emitc::OpaqueAttr::get(ctx, "");
+ Value structLv =
+ emitc::VariableOp::create(rewriter, loc,
+ emitc::LValueType::get(structType), noInit)
+ .getResult();
+ for (auto [i, val] : llvm::enumerate(values)) {
+ Value fieldLv =
+ emitc::MemberOp::create(
+ rewriter, loc, emitc::LValueType::get(val.getType()),
+ rewriter.getStringAttr("field" + std::to_string(i)), structLv)
+ .getResult();
+ emitc::AssignOp::create(rewriter, loc, fieldLv, val);
+ }
+ return emitc::LoadOp::create(rewriter, loc, structType, structLv).getResult();
+}
+
/// Implement the interface to convert Func to EmitC.
struct FuncToEmitCDialectInterface : public ConvertToEmitCPatternInterface {
FuncToEmitCDialectInterface(Dialect *dialect)
@@ -55,26 +164,66 @@ class CallOpConversion final : public OpConversionPattern<func::CallOp> {
LogicalResult
matchAndRewrite(func::CallOp callOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
- // Multiple results func cannot be converted to `emitc.func`.
- if (callOp.getNumResults() > 1)
- return rewriter.notifyMatchFailure(
- callOp, "only functions with zero or one result can be converted");
-
- if (callOp.getNumResults() == 1) {
- Type resultType =
- getTypeConverter()->convertType(callOp.getResult(0).getType());
+ SmallVector<Type> convertedResultTypes;
+ for (Type t : callOp.getResultTypes()) {
+ Type resultType = getTypeConverter()->convertType(t);
if (!resultType)
return rewriter.notifyMatchFailure(callOp,
"result type conversion failed");
if (isa<emitc::ArrayType>(resultType))
return rewriter.notifyMatchFailure(
callOp, "function calls returning arrays are not supported");
+ convertedResultTypes.push_back(resultType);
}
- rewriter.replaceOpWithNewOp<emitc::CallOp>(callOp, callOp.getResultTypes(),
- adaptor.getOperands(),
- callOp->getAttrs());
+ if (callOp.getNumResults() <= 1) {
+ rewriter.replaceOpWithNewOp<emitc::CallOp>(
+ callOp, callOp.getResultTypes(), adaptor.getOperands(),
+ callOp->getAttrs());
+ return success();
+ }
+
+ // Multi-result call: determine the struct type.
+ Location loc = callOp.getLoc();
+
+ auto structType =
+ getOrCreateMultiReturnType(rewriter, loc, callOp, convertedResultTypes);
+ if (failed(structType))
+ return rewriter.notifyMatchFailure(callOp,
+ "incompatible multi-return struct");
+
+ // Emit a call returning the packed struct.
+ Value structVal =
+ emitc::CallOp::create(rewriter, loc, callOp.getCalleeAttr(),
+ TypeRange{*structType}, adaptor.getOperands())
+ .getResult(0);
+
+ // Unpack struct fields to replace the original multiple results.
+ MLIRContext *ctx = rewriter.getContext();
+ auto noInit = emitc::OpaqueAttr::get(ctx, "");
+ Value structLv =
+ emitc::VariableOp::create(rewriter, loc,
+ emitc::LValueType::get(*structType), noInit)
+ .getResult();
+ emitc::AssignOp::create(rewriter, loc, structLv, structVal);
+ SmallVector<Value> results;
+ for (auto [i, result] : llvm::enumerate(callOp.getResults())) {
+ if (result.use_empty()) {
+ results.push_back(Value()); // No replacement needed.
+ continue;
+ }
+ Type fieldType = convertedResultTypes[i];
+ StringAttr fieldName =
+ rewriter.getStringAttr("field" + std::to_string(i));
+ Value fieldLv = emitc::MemberOp::create(rewriter, loc,
+ emitc::LValueType::get(fieldType),
+ fieldName, structLv)
+ .getResult();
+ results.push_back(
+ emitc::LoadOp::create(rewriter, loc, fieldType, fieldLv).getResult());
+ }
+ rewriter.replaceOp(callOp, results);
return success();
}
};
@@ -88,10 +237,6 @@ class FuncOpConversion final : public OpConversionPattern<func::FuncOp> {
ConversionPatternRewriter &rewriter) const override {
FunctionType fnType = funcOp.getFunctionType();
- if (fnType.getNumResults() > 1)
- return rewriter.notifyMatchFailure(
- funcOp, "only functions with zero or one result can be converted");
-
TypeConverter::SignatureConversion signatureConverter(
fnType.getNumInputs());
for (const auto &argType : enumerate(fnType.getInputs())) {
@@ -102,15 +247,28 @@ class FuncOpConversion final : public OpConversionPattern<func::FuncOp> {
signatureConverter.addInputs(argType.index(), convertedType);
}
- Type resultType;
- if (fnType.getNumResults() == 1) {
- resultType = getTypeConverter()->convertType(fnType.getResult(0));
+ SmallVector<Type> convertedResultTypes;
+ for (Type t : fnType.getResults()) {
+ Type resultType = getTypeConverter()->convertType(t);
if (!resultType)
return rewriter.notifyMatchFailure(funcOp,
"result type conversion failed");
if (isa<emitc::ArrayType>(resultType))
return rewriter.notifyMatchFailure(
funcOp, "functions returning arrays are not supported");
+ convertedResultTypes.push_back(resultType);
+ }
+
+ Type resultType;
+ if (fnType.getNumResults() == 1) {
+ resultType = convertedResultTypes[0];
+ } else if (fnType.getNumResults() > 1) {
+ auto structTypeOrErr = getOrCreateMultiReturnType(
+ rewriter, funcOp.getLoc(), funcOp, convertedResultTypes);
+ if (failed(structTypeOrErr))
+ return rewriter.notifyMatchFailure(funcOp,
+ "incompatible multi-return struct");
+ resultType = *structTypeOrErr;
}
// Create the converted `emitc.func` op.
@@ -160,17 +318,31 @@ class ReturnOpConversion final : public OpConversionPattern<func::ReturnOp> {
LogicalResult
matchAndRewrite(func::ReturnOp returnOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
- if (returnOp.getNumOperands() > 1)
- return rewriter.notifyMatchFailure(
- returnOp, "only zero or one operand is supported");
- if (returnOp.getNumOperands() == 1 &&
- isa<emitc::ArrayType>(adaptor.getOperands()[0].getType()))
+ if (llvm::any_of(adaptor.getOperands(), [](Value operand) {
+ return isa<emitc::ArrayType>(operand.getType());
+ }))
return rewriter.notifyMatchFailure(returnOp,
"returning arrays is not supported");
- rewriter.replaceOpWithNewOp<emitc::ReturnOp>(
- returnOp,
- returnOp.getNumOperands() ? adaptor.getOperands()[0] : nullptr);
+ if (returnOp.getNumOperands() <= 1) {
+ rewriter.replaceOpWithNewOp<emitc::ReturnOp>(
+ returnOp,
+ returnOp.getNumOperands() ? adaptor.getOperands()[0] : nullptr);
+ return success();
+ }
+
+ // Multi-operand return: pack values into a struct.
+ Location loc = returnOp.getLoc();
+
+ auto structType = getOrCreateMultiReturnType(rewriter, loc, returnOp,
+ adaptor.getOperands());
+ if (failed(structType))
+ return rewriter.notifyMatchFailure(returnOp,
+ "incompatible multi-return struct");
+
+ Value structVal =
+ packValuesIntoStruct(rewriter, loc, adaptor.getOperands(), *structType);
+ rewriter.replaceOpWithNewOp<emitc::ReturnOp>(returnOp, structVal);
return success();
}
};
diff --git a/mlir/test/Conversion/FuncToEmitC/func-to-emitc-failed.mlir b/mlir/test/Conversion/FuncToEmitC/func-to-emitc-failed.mlir
index d85069371e691..fef773e9857e4 100644
--- a/mlir/test/Conversion/FuncToEmitC/func-to-emitc-failed.mlir
+++ b/mlir/test/Conversion/FuncToEmitC/func-to-emitc-failed.mlir
@@ -97,3 +97,89 @@ func.func private @caller(%arg0: memref<1xi64>, %arg1: i64) -> memref<1xi64> {
%0 = call @callee(%arg1) : (i64) -> i64
return %arg0 : memref<1xi64>
}
+
+// -----
+
+// A symbol with the auto-generated struct name already exists but is not an
+// emitc.class (here it is an emitc.func).
+emitc.func @return_i32_i32() { emitc.return }
+// expected-error at +2 {{symbol 'return_i32_i32' exists but is not an emitc.class}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @symbol_not_a_class(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// The existing emitc.class is not a struct (class_type != struct).
+emitc.class @return_i32_i32 {
+ emitc.field @field0 : i32
+ emitc.field @field1 : i32
+}
+
+// expected-error at +2 {{existing class 'return_i32_i32' is not a struct}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @class_not_a_struct(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// The existing emitc.class has a method, so it cannot be used as a plain
+// struct.
+emitc.class struct @return_i32_i32 {
+ emitc.func @method() { emitc.return }
+}
+
+// expected-error at +2 {{existing class 'return_i32_i32' has methods; expected a plain struct}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @class_has_methods(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// The existing emitc.class has fewer fields than the return types require.
+emitc.class struct @return_i32_i32 {
+ emitc.field @field0 : i32
+}
+
+// expected-error at +2 {{existing class 'return_i32_i32' has wrong number of fields}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @class_wrong_field_count(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// The existing emitc.class has fields with unexpected names.
+emitc.class struct @return_i32_i32 {
+ emitc.field @a : i32
+ emitc.field @b : i32
+}
+
+// expected-error at +2 {{existing class 'return_i32_i32': unexpected field name at index 0}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @class_wrong_field_names(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// The existing emitc.class has fields with the wrong types.
+emitc.class struct @return_i32_i32 {
+ emitc.field @field0 : i64
+ emitc.field @field1 : i32
+}
+
+// expected-error at +2 {{existing class 'return_i32_i32': wrong type for field 0}}
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func @class_wrong_field_types(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+
+// -----
+
+// Multi-result function where one result is an array type.
+// expected-error at +1 {{failed to legalize operation 'func.func'}}
+func.func private @multi_result_with_array() -> (i32, !emitc.array<10xi32>)
diff --git a/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir b/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
index 6824a64dda3ef..ccda818585517 100644
--- a/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
+++ b/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
@@ -75,3 +75,97 @@ func.func @call() {
call @return_void() : () -> ()
return
}
+
+// -----
+
+// Multi-result function: check that an emitc.class struct is created and the
+// function returns the packed struct.
+// CHECK-LABEL: emitc.class struct @return_i32_i32 {
+// CHECK: emitc.field @field0 : i32
+// CHECK: emitc.field @field1 : i32
+// CHECK: }
+// CHECK-LABEL: emitc.func @return_two(
+// CHECK-SAME: %[[ARG0:.*]]: i32,
+// CHECK-SAME: %[[ARG1:.*]]: i32) -> !emitc.opaque<"struct return_i32_i32"> {
+// CHECK: %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+// CHECK: %[[VAL_1:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field0"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK: assign %[[ARG0]] : i32 to %[[VAL_1]] : <i32>
+// CHECK: %[[VAL_2:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK: assign %[[ARG1]] : i32 to %[[VAL_2]] : <i32>
+// CHECK: %[[VAL_3:.*]] = load %[[VAL_0]] : <!emitc.opaque<"struct return_i32_i32">>
+// CHECK: return %[[VAL_3]] : !emitc.opaque<"struct return_i32_i32">
+// CHECK: }
+func.func @return_two(%arg0: i32, %arg1: i32) -> (i32, i32) {
+ return %arg0, %arg1 : i32, i32
+}
+
+// -----
+
+// Call to a multi-result function: check that the call returns the struct and
+// that only the field actually used is extracted.
+// CHECK-LABEL: emitc.class struct @return_i32_i32 {
+// CHECK: emitc.field @field0 : i32
+// CHECK: emitc.field @field1 : i32
+// CHECK: }
+// CHECK-LABEL: emitc.func @return_two(
+// CHECK-SAME: %[[ARG0:.*]]: i32,
+// CHECK-SAME: %[[ARG1:.*]]: i32) -> !emitc.opaque<"struct return_i32_i32"> {
+// CHECK-NEXT: %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: %[[VAL_1:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field0"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG0]] : i32 to %[[VAL_1]] : <i32>
+// CHECK-NEXT: %[[VAL_2:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG1]] : i32 to %[[VAL_2]] : <i32>
+// CHECK-NEXT: %[[VAL_3:.*]] = load %[[VAL_0]] : <!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: return %[[VAL_3]] : !emitc.opaque<"struct return_i32_i32">
+// CHECK-NEXT: }
+// CHECK-LABEL: emitc.func @caller(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32 {
+// CHECK-NEXT: %[[VAL_0:.*]] = call @return_two(%[[ARG0]], %[[ARG0]]) : (i32, i32) -> !emitc.opaque<"struct return_i32_i32">
+// CHECK-NEXT: %[[VAL_1:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: assign %[[VAL_0]] : !emitc.opaque<"struct return_i32_i32"> to %[[VAL_1]] : <!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: %[[VAL_2:.*]] = "emitc.member"(%[[VAL_1]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: %[[VAL_3:.*]] = load %[[VAL_2]] : <i32>
+// CHECK-NEXT: return %[[VAL_3]] : i32
+// CHECK-NEXT: }
+func.func @return_two(%arg0: i32, %arg1: i32) -> (i32, i32) {
+ return %arg0, %arg1 : i32, i32
+}
+func.func @caller(%arg0: i32) -> i32 {
+ %0, %1 = call @return_two(%arg0, %arg0) : (i32, i32) -> (i32, i32)
+ return %1 : i32
+}
+
+// -----
+
+// Two functions returning the same type tuple share one emitc.class.
+// CHECK-LABEL: emitc.class struct @return_i32_i32 {
+// CHECK: emitc.field @field0 : i32
+// CHECK: emitc.field @field1 : i32
+// CHECK: }
+// CHECK-LABEL: emitc.func @first(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> !emitc.opaque<"struct return_i32_i32"> {
+// CHECK-NEXT: %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: %[[VAL_1:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field0"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG0]] : i32 to %[[VAL_1]] : <i32>
+// CHECK-NEXT: %[[VAL_2:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK-NEXT: %[[VAL_3:.*]] = load %[[VAL_0]] : <!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: return %[[VAL_3]] : !emitc.opaque<"struct return_i32_i32">
+// CHECK-NEXT: }
+// CHECK-LABEL: emitc.func @second(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> !emitc.opaque<"struct return_i32_i32"> {
+// CHECK-NEXT: %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: %[[VAL_1:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field0"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG0]] : i32 to %[[VAL_1]] : <i32>
+// CHECK-NEXT: %[[VAL_2:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+// CHECK-NEXT: assign %[[ARG0]] : i32 to %[[VAL_2]] : <i32>
+// CHECK-NEXT: %[[VAL_3:.*]] = load %[[VAL_0]] : <!emitc.opaque<"struct return_i32_i32">>
+// CHECK-NEXT: return %[[VAL_3]] : !emitc.opaque<"struct return_i32_i32">
+// CHECK-NEXT: }
+// CHECK-NOT: emitc.class
+func.func @first(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
+func.func @second(%arg0: i32) -> (i32, i32) {
+ return %arg0, %arg0 : i32, i32
+}
diff --git a/mlir/test/Target/Cpp/func.mlir b/mlir/test/Target/Cpp/func.mlir
index 9c9ea55bfc4e1..82f1ee9f6ec2b 100644
--- a/mlir/test/Target/Cpp/func.mlir
+++ b/mlir/test/Target/Cpp/func.mlir
@@ -43,3 +43,66 @@ emitc.func private @extern_func(i32) attributes {specifiers = ["extern"]}
emitc.func private @array_arg(!emitc.array<3xi32>) attributes {specifiers = ["extern"]}
// CPP-DEFAULT: extern void array_arg(int32_t[3]);
+
+emitc.class struct @return_i32_i32 {
+ emitc.field @field0 : i32
+ emitc.field @field1 : i32
+}
+
+emitc.func @return_two(%arg0: i32, %arg1: i32) -> !emitc.opaque<"struct return_i32_i32"> {
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+ %1 = "emitc.member"(%0) <{member = "field0"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+ assign %arg0 : i32 to %1 : <i32>
+ %2 = "emitc.member"(%0) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+ assign %arg1 : i32 to %2 : <i32>
+ %3 = load %0 : <!emitc.opaque<"struct return_i32_i32">>
+ return %3 : !emitc.opaque<"struct return_i32_i32">
+}
+
+emitc.func @call_two(%arg0: i32) -> i32 {
+ %0 = call @return_two(%arg0, %arg0) : (i32, i32) -> !emitc.opaque<"struct return_i32_i32">
+ %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
+ assign %0 : !emitc.opaque<"struct return_i32_i32"> to %1 : <!emitc.opaque<"struct return_i32_i32">>
+ %2 = "emitc.member"(%1) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
+ %3 = load %2 : <i32>
+ return %3 : i32
+}
+
+// CPP-DEFAULT: struct return_i32_i32 {
+// CPP-DEFAULT-NEXT: int32_t field0;
+// CPP-DEFAULT-NEXT: int32_t field1;
+// CPP-DEFAULT-NEXT: };
+// CPP-DEFAULT-NEXT: struct return_i32_i32 return_two(int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: struct return_i32_i32 [[V3:[^ ]*]];
+// CPP-DEFAULT-NEXT: [[V3]].field0 = [[V1]];
+// CPP-DEFAULT-NEXT: [[V3]].field1 = [[V2]];
+// CPP-DEFAULT-NEXT: struct return_i32_i32 [[V4:[^ ]*]] = [[V3]];
+// CPP-DEFAULT-NEXT: return [[V4]];
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: int32_t call_two(int32_t [[V1:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: struct return_i32_i32 [[V2:[^ ]*]] = return_two([[V1]], [[V1]]);
+// CPP-DEFAULT-NEXT: struct return_i32_i32 [[V3:[^ ]*]];
+// CPP-DEFAULT-NEXT: [[V3]] = [[V2]];
+// CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]] = [[V3]].field1;
+// CPP-DEFAULT-NEXT: return [[V4]];
+
+// CPP-DECLTOP: struct return_i32_i32 {
+// CPP-DECLTOP-NEXT: int32_t field0;
+// CPP-DECLTOP-NEXT: int32_t field1;
+// CPP-DECLTOP-NEXT: };
+// CPP-DECLTOP-NEXT: struct return_i32_i32 return_two(int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
+// CPP-DECLTOP-NEXT: struct return_i32_i32 [[V3:[^ ]*]];
+// CPP-DECLTOP-NEXT: struct return_i32_i32 [[V4:[^ ]*]];
+// CPP-DECLTOP: [[V3]].field0 = [[V1]];
+// CPP-DECLTOP-NEXT: [[V3]].field1 = [[V2]];
+// CPP-DECLTOP-NEXT: [[V4]] = [[V3]];
+// CPP-DECLTOP-NEXT: return [[V4]];
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: int32_t call_two(int32_t [[V1:[^ ]*]]) {
+// CPP-DECLTOP-NEXT: struct return_i32_i32 [[V2:[^ ]*]];
+// CPP-DECLTOP-NEXT: struct return_i32_i32 [[V3:[^ ]*]];
+// CPP-DECLTOP-NEXT: int32_t [[V4:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[V2]] = return_two([[V1]], [[V1]]);
+// CPP-DECLTOP: [[V3]] = [[V2]];
+// CPP-DECLTOP-NEXT: [[V4]] = [[V3]].field1;
+// CPP-DECLTOP-NEXT: return [[V4]];
More information about the Mlir-commits
mailing list