[Mlir-commits] [mlir] [WIP][mlir][EmitC] Model lvalues as a type in EmitC (PR #91475)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Tue Jun 11 07:07:44 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-emitc
Author: Simon Camphausen (simon-camp)
<details>
<summary>Changes</summary>
This is an early unpolished version of what has been previously discussed on [discourse](https://discourse.llvm.org/t/rfc-separate-variables-from-ssa-values-in-emitc/75224/9). See this as a starting point for further discussions.
---
Patch is 94.19 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/91475.diff
25 Files Affected:
- (modified) mlir/include/mlir/Dialect/EmitC/IR/EmitC.td (+38-9)
- (modified) mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td (+18)
- (modified) mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp (+1-6)
- (modified) mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp (+27-4)
- (modified) mlir/lib/Dialect/EmitC/IR/EmitC.cpp (+83-24)
- (modified) mlir/lib/Dialect/EmitC/Transforms/FormExpressions.cpp (+2-1)
- (modified) mlir/lib/Target/Cpp/TranslateToCpp.cpp (+82-43)
- (modified) mlir/test/Conversion/MemRefToEmitC/memref-to-emitc.mlir (+12-12)
- (modified) mlir/test/Conversion/SCFToEmitC/for.mlir (+27-19)
- (modified) mlir/test/Conversion/SCFToEmitC/if.mlir (+12-10)
- (modified) mlir/test/Dialect/EmitC/invalid_ops.mlir (+31-30)
- (modified) mlir/test/Dialect/EmitC/invalid_types.mlir (+53-13)
- (modified) mlir/test/Dialect/EmitC/ops.mlir (+11-10)
- (modified) mlir/test/Dialect/EmitC/transforms.mlir (+11-37)
- (modified) mlir/test/Dialect/EmitC/types.mlir (+17-2)
- (modified) mlir/test/Target/Cpp/common-cpp.mlir (+13-5)
- (modified) mlir/test/Target/Cpp/expressions.mlir (+52-32)
- (modified) mlir/test/Target/Cpp/for.mlir (+60-22)
- (modified) mlir/test/Target/Cpp/global.mlir (+79-17)
- (modified) mlir/test/Target/Cpp/if.mlir (+6-6)
- (modified) mlir/test/Target/Cpp/invalid.mlir (+1-1)
- (modified) mlir/test/Target/Cpp/invalid_declare_variables_at_top.mlir (+13-2)
- (added) mlir/test/Target/Cpp/lvalue.mlir (+37)
- (modified) mlir/test/Target/Cpp/subscript.mlir (+76-30)
- (modified) mlir/test/Target/Cpp/variable.mlir (+9-7)
``````````diff
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 5da8593f59563..f5d82bc41642e 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -97,9 +97,9 @@ def EmitC_ApplyOp : EmitC_Op<"apply", [CExpression]> {
}];
let arguments = (ins
Arg<StrAttr, "the operator to apply">:$applicableOperator,
- EmitCType:$operand
+ AnyTypeOf<[EmitCType, EmitC_LValueType]>:$operand
);
- let results = (outs EmitCType:$result);
+ let results = (outs AnyTypeOf<[EmitCType, EmitC_LValueType]>:$result);
let assemblyFormat = [{
$applicableOperator `(` $operand `)` attr-dict `:` functional-type($operand, results)
}];
@@ -835,6 +835,21 @@ def EmitC_LogicalOrOp : EmitC_BinaryOp<"logical_or", [CExpression]> {
let assemblyFormat = "operands attr-dict `:` type(operands)";
}
+def EmitC_LValueLoadOp : EmitC_Op<"lvalue_load", [
+ TypesMatchWith<"result type matches value type of 'operand'",
+ "operand", "result",
+ "::llvm::cast<LValueType>($_self).getValue()">
+]> {
+ let summary = "load an lvalue by assigning it to a local variable";
+ let description = [{}];
+
+ let arguments = (ins
+ Res<EmitC_LValueType, "", [MemRead<DefaultResource, 0, FullEffect>]>:$operand);
+ let results = (outs AnyType:$result);
+
+ let assemblyFormat = "$operand attr-dict `:` type($operand)";
+}
+
def EmitC_MulOp : EmitC_BinaryOp<"mul", [CExpression]> {
let summary = "Multiplication operation";
let description = [{
@@ -1009,7 +1024,8 @@ def EmitC_VariableOp : EmitC_Op<"variable", []> {
}];
let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
- let results = (outs EmitCType);
+ let results = (outs Res<AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>, "",
+ [MemAlloc<DefaultResource, 0, FullEffect>]>:$memref);
let hasVerifier = 1;
}
@@ -1079,7 +1095,7 @@ def EmitC_GetGlobalOp : EmitC_Op<"get_global",
}];
let arguments = (ins FlatSymbolRefAttr:$name);
- let results = (outs EmitCType:$result);
+ let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>:$result);
let assemblyFormat = "$name `:` type($result) attr-dict";
}
@@ -1137,7 +1153,9 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> {
```
}];
- let arguments = (ins EmitCType:$var, EmitCType:$value);
+ let arguments = (ins
+ Res<EmitC_LValueType, "", [MemWrite<DefaultResource, 1, FullEffect>]>:$var,
+ Res<EmitCType, "", [MemRead<DefaultResource, 0, FullEffect>]>:$value);
let results = (outs);
let hasVerifier = 1;
@@ -1243,15 +1261,26 @@ def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
EmitC_PointerType]>,
"the value to subscript">:$value,
Variadic<EmitCType>:$indices);
- let results = (outs EmitCType:$result);
+ let results = (outs EmitC_LValueType:$result);
let builders = [
OpBuilder<(ins "TypedValue<ArrayType>":$array, "ValueRange":$indices), [{
- build($_builder, $_state, array.getType().getElementType(), array, indices);
+ build(
+ $_builder,
+ $_state,
+ emitc::LValueType::get(array.getType().getElementType()),
+ array,
+ indices
+ );
}]>,
OpBuilder<(ins "TypedValue<PointerType>":$pointer, "Value":$index), [{
- build($_builder, $_state, pointer.getType().getPointee(), pointer,
- ValueRange{index});
+ build(
+ $_builder,
+ $_state,
+ emitc::LValueType::get(pointer.getType().getPointee()),
+ pointer,
+ ValueRange{index}
+ );
}]>
];
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
index 444395b915e25..fc795962a3e5b 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
@@ -83,6 +83,23 @@ def EmitC_ArrayType : EmitC_Type<"Array", "array", [ShapedTypeInterface]> {
let hasCustomAssemblyFormat = 1;
}
+def EmitC_LValueType : EmitC_Type<"LValue", "lvalue"> {
+ let summary = "EmitC lvalue type";
+
+ let description = [{
+ Values of this type can be assigned to and their address can be taken.
+ }];
+
+ let parameters = (ins "Type":$value);
+ let builders = [
+ TypeBuilderWithInferredContext<(ins "Type":$value), [{
+ return $_get(value.getContext(), value);
+ }]>
+ ];
+ let assemblyFormat = "`<` qualified($value) `>`";
+ let genVerifyDecl = 1;
+}
+
def EmitC_OpaqueType : EmitC_Type<"Opaque", "opaque"> {
let summary = "EmitC opaque type";
@@ -128,6 +145,7 @@ def EmitC_PointerType : EmitC_Type<"Pointer", "ptr"> {
}]>
];
let assemblyFormat = "`<` qualified($pointee) `>`";
+ let genVerifyDecl = 1;
}
#endif // MLIR_DIALECT_EMITC_IR_EMITCTYPES
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
index e0c421741b305..2e8fbbad14d40 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
@@ -137,12 +137,7 @@ struct ConvertLoad final : public OpConversionPattern<memref::LoadOp> {
auto subscript = rewriter.create<emitc::SubscriptOp>(
op.getLoc(), arrayValue, operands.getIndices());
- auto noInit = emitc::OpaqueAttr::get(getContext(), "");
- auto var =
- rewriter.create<emitc::VariableOp>(op.getLoc(), resultTy, noInit);
-
- rewriter.create<emitc::AssignOp>(op.getLoc(), var, subscript);
- rewriter.replaceOp(op, var);
+ rewriter.replaceOpWithNewOp<emitc::LValueLoadOp>(op, resultTy, subscript);
return success();
}
};
diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 0a89242225255..59a090a5fc65f 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -63,9 +63,10 @@ static SmallVector<Value> createVariablesForResults(T op,
for (OpResult result : op.getResults()) {
Type resultType = result.getType();
+ Type varType = emitc::LValueType::get(resultType);
emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, "");
emitc::VariableOp var =
- rewriter.create<emitc::VariableOp>(loc, resultType, noInit);
+ rewriter.create<emitc::VariableOp>(loc, varType, noInit);
resultVariables.push_back(var);
}
@@ -80,6 +81,14 @@ static void assignValues(ValueRange values, SmallVector<Value> &variables,
rewriter.create<emitc::AssignOp>(loc, var, value);
}
+SmallVector<Value> loadValues(const SmallVector<Value> &variables,
+ PatternRewriter &rewriter, Location loc) {
+ return llvm::map_to_vector<>(variables, [&](Value var) {
+ Type type = cast<emitc::LValueType>(var.getType()).getValue();
+ return rewriter.create<emitc::LValueLoadOp>(loc, type, var).getResult();
+ });
+}
+
static void lowerYield(SmallVector<Value> &resultVariables,
PatternRewriter &rewriter, scf::YieldOp yield) {
Location loc = yield.getLoc();
@@ -113,15 +122,26 @@ LogicalResult ForLowering::matchAndRewrite(ForOp forOp,
// Erase the auto-generated terminator for the lowered for op.
rewriter.eraseOp(loweredBody->getTerminator());
+ IRRewriter::InsertPoint ip = rewriter.saveInsertionPoint();
+ rewriter.setInsertionPointToEnd(loweredBody);
+
+ SmallVector<Value> iterArgsValues =
+ loadValues(resultVariables, rewriter, loc);
+
+ rewriter.restoreInsertionPoint(ip);
+
SmallVector<Value> replacingValues;
replacingValues.push_back(loweredFor.getInductionVar());
- replacingValues.append(resultVariables.begin(), resultVariables.end());
+ replacingValues.append(iterArgsValues.begin(), iterArgsValues.end());
rewriter.mergeBlocks(forOp.getBody(), loweredBody, replacingValues);
lowerYield(resultVariables, rewriter,
cast<scf::YieldOp>(loweredBody->getTerminator()));
- rewriter.replaceOp(forOp, resultVariables);
+ // Copy iterArgs into results after the for loop.
+ SmallVector<Value> resultValues = loadValues(resultVariables, rewriter, loc);
+
+ rewriter.replaceOp(forOp, resultValues);
return success();
}
@@ -173,7 +193,10 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
lowerRegion(elseRegion, loweredElseRegion);
}
- rewriter.replaceOp(ifOp, resultVariables);
+ rewriter.setInsertionPointAfter(ifOp);
+ SmallVector<Value> results = loadValues(resultVariables, rewriter, loc);
+
+ rewriter.replaceOp(ifOp, results);
return success();
}
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 20f47574b25ad..43cf410e8889c 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -61,6 +61,9 @@ void mlir::emitc::buildTerminatedBody(OpBuilder &builder, Location loc) {
bool mlir::emitc::isSupportedEmitCType(Type type) {
if (llvm::isa<emitc::OpaqueType>(type))
return true;
+ if (auto lType = llvm::dyn_cast<emitc::LValueType>(type))
+ // lvalue types are only allowed in a few places.
+ return false;
if (auto ptrType = llvm::dyn_cast<emitc::PointerType>(type))
return isSupportedEmitCType(ptrType.getPointee());
if (auto arrayType = llvm::dyn_cast<emitc::ArrayType>(type)) {
@@ -140,6 +143,8 @@ static LogicalResult verifyInitializationAttribute(Operation *op,
<< "string attributes are not supported, use #emitc.opaque instead";
Type resultType = op->getResult(0).getType();
+ if (auto lType = dyn_cast<LValueType>(resultType))
+ resultType = lType.getValue();
Type attrType = cast<TypedAttr>(value).getType();
if (resultType != attrType)
@@ -188,9 +193,19 @@ LogicalResult ApplyOp::verify() {
if (applicableOperatorStr != "&" && applicableOperatorStr != "*")
return emitOpError("applicable operator is illegal");
- Operation *op = getOperand().getDefiningOp();
- if (op && dyn_cast<ConstantOp>(op))
- return emitOpError("cannot apply to constant");
+ Type operandType = getOperand().getType();
+ Type resultType = getResult().getType();
+ if (applicableOperatorStr == "&") {
+ if (!llvm::isa<emitc::LValueType>(operandType))
+ return emitOpError("operand type must be an lvalue when applying `&`");
+ if (!llvm::isa<emitc::PointerType>(resultType))
+ return emitOpError("result type must be a pointer when applying `&`");
+ } else {
+ if (!llvm::isa<emitc::PointerType>(operandType))
+ return emitOpError("operand type must be a pointer when applying `*`");
+ if (!llvm::isa<emitc::LValueType>(resultType))
+ return emitOpError("result type must be an lvalue when applying `*`");
+ }
return success();
}
@@ -202,20 +217,18 @@ LogicalResult ApplyOp::verify() {
/// The assign op requires that the assigned value's type matches the
/// assigned-to variable type.
LogicalResult emitc::AssignOp::verify() {
- Value variable = getVar();
- Operation *variableDef = variable.getDefiningOp();
- if (!variableDef ||
- !llvm::isa<emitc::VariableOp, emitc::SubscriptOp>(variableDef))
- return emitOpError() << "requires first operand (" << variable
- << ") to be a Variable or subscript";
-
- Value value = getValue();
- if (variable.getType() != value.getType())
- return emitOpError() << "requires value's type (" << value.getType()
- << ") to match variable's type (" << variable.getType()
- << ")";
- if (isa<ArrayType>(variable.getType()))
- return emitOpError() << "cannot assign to array type";
+ TypedValue<emitc::LValueType> variable = getVar();
+
+ if (!variable.getDefiningOp())
+ return emitOpError() << "cannot assign to block argument";
+
+ Type valueType = getValue().getType();
+ Type variableType = variable.getType().getValue();
+ if (variableType != valueType)
+ return emitOpError() << "requires value's type (" << valueType
+ << ") to match variable's type (" << variableType
+ << ")\n variable: " << variable
+ << "\n value: " << getValue() << "\n";
return success();
}
@@ -842,9 +855,10 @@ LogicalResult emitc::SubscriptOp::verify() {
}
// Check element type.
Type elementType = arrayType.getElementType();
- if (elementType != getType()) {
+ Type resultType = getType().getValue();
+ if (elementType != resultType) {
return emitOpError() << "on array operand requires element type ("
- << elementType << ") and result type (" << getType()
+ << elementType << ") and result type (" << resultType
<< ") to match";
}
return success();
@@ -868,9 +882,10 @@ LogicalResult emitc::SubscriptOp::verify() {
}
// Check pointee type.
Type pointeeType = pointerType.getPointee();
- if (pointeeType != getType()) {
+ Type resultType = getType().getValue();
+ if (pointeeType != resultType) {
return emitOpError() << "on pointer operand requires pointee type ("
- << pointeeType << ") and result type (" << getType()
+ << pointeeType << ") and result type (" << resultType
<< ") to match";
}
return success();
@@ -964,6 +979,25 @@ emitc::ArrayType::cloneWith(std::optional<ArrayRef<int64_t>> shape,
return emitc::ArrayType::get(*shape, elementType);
}
+//===----------------------------------------------------------------------===//
+// LValueType
+//===----------------------------------------------------------------------===//
+
+LogicalResult mlir::emitc::LValueType::verify(
+ llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
+ mlir::Type value) {
+ // Check that the wrapped type is valid. This especially forbids nested lvalue
+ // types.
+ if (!isSupportedEmitCType(value))
+ return emitError()
+ << "!emitc.lvalue must wrap supported emitc type, but got " << value;
+
+ if (llvm::isa<emitc::ArrayType>(value))
+ return emitError() << "!emitc.lvalue cannot wrap !emitc.array type";
+
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// OpaqueType
//===----------------------------------------------------------------------===//
@@ -981,6 +1015,18 @@ LogicalResult mlir::emitc::OpaqueType::verify(
return success();
}
+//===----------------------------------------------------------------------===//
+// PointerType
+//===----------------------------------------------------------------------===//
+
+LogicalResult mlir::emitc::PointerType::verify(
+ llvm::function_ref<mlir::InFlightDiagnostic()> emitError, Type value) {
+ if (llvm::isa<emitc::LValueType>(value))
+ return emitError() << "pointers to lvalues are not allowed";
+
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// GlobalOp
//===----------------------------------------------------------------------===//
@@ -1078,9 +1124,22 @@ GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
<< getName() << "' does not reference a valid emitc.global";
Type resultType = getResult().getType();
- if (global.getType() != resultType)
- return emitOpError("result type ")
- << resultType << " does not match type " << global.getType()
+ Type globalType = global.getType();
+
+ // global has array type
+ if (llvm::isa<ArrayType>(globalType)) {
+ if (globalType != resultType)
+ return emitOpError("on array type expects result type ")
+ << resultType << " to match type " << globalType
+ << " of the global @" << getName();
+ return success();
+ }
+
+ // global has non-array type
+ auto lvalueType = dyn_cast<LValueType>(resultType);
+ if (!lvalueType || lvalueType.getValue() != globalType)
+ return emitOpError("on non-array type expects result inner type ")
+ << lvalueType.getValue() << " to match type " << globalType
<< " of the global @" << getName();
return success();
}
diff --git a/mlir/lib/Dialect/EmitC/Transforms/FormExpressions.cpp b/mlir/lib/Dialect/EmitC/Transforms/FormExpressions.cpp
index 82bd031430d36..758b8527c2fa5 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/FormExpressions.cpp
+++ b/mlir/lib/Dialect/EmitC/Transforms/FormExpressions.cpp
@@ -38,7 +38,8 @@ struct FormExpressionsPass
auto matchFun = [&](Operation *op) {
if (op->hasTrait<OpTrait::emitc::CExpression>() &&
!op->getParentOfType<emitc::ExpressionOp>() &&
- op->getNumResults() == 1)
+ op->getNumResults() == 1 &&
+ isSupportedEmitCType(op->getResult(0).getType()))
createExpression(op, builder);
};
rootOp->walk(matchFun);
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 202df89025f26..79e433cbe4612 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -174,6 +174,9 @@ struct CppEmitter {
/// Emit an expression as a C expression.
LogicalResult emitExpression(ExpressionOp expressionOp);
+ /// Insert the expression representing the operation into the value cache.
+ LogicalResult cacheDeferredOpResult(Operation *op);
+
/// Return the existing or a new name for a Value.
StringRef getOrCreateName(Value val);
@@ -273,6 +276,18 @@ struct CppEmitter {
};
} // namespace
+/// Determine whether expression \p op should be emitted in a deferred way.
+static bool hasDeferredEmission(Operation *op) {
+ if (isa_and_nonnull<emitc::GetGlobalOp, emitc::LiteralOp, emitc::SubscriptOp>(
+ op))
+ return true;
+
+ if (auto applyOp = dyn_cast_or_null<emitc::ApplyOp>(op))
+ return applyOp.getApplicableOperator() == "*";
+
+ return false;
+}
+
/// Determine whether expression \p expressionOp should be emitted inline, i.e.
/// as part of its user. This function recommends inlining of any expressions
/// that can be inlined unless it is used by another expression, under the
@@ -295,10 +310,10 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
Operation *user = *result.getUsers().begin();
- // Do not inline expressions used by subscript operations, since the
- // way the subscript operation translation is implemented requires that
- // variables be materialized.
- if (isa<emitc::SubscriptOp>(user))
+ // Do not inline expressions used by operations with deferred emission, since
+ // the way their translation is implemented requires that variables be
+ // materialized.
+ if (hasDeferredEmission(user))
return false;
// Do not inline expressions used by ops with the CExpression trait. If this
@@ -371,17 +386,11 @@ static LogicalResult printOperation(CppEmitter &emitter,
}
static LogicalResult printOperation(CppEmitter &emitter,
- emitc::GetGlobalOp op) {
- // Add name to cache so that `hasValueInScope` works.
- emitter.getOrCreateName(op.getResult());
- return success();
-}
+ emitc::LValueLoadOp lValueLoadOp) {
+ if (failed(emitter.emitAssignPrefix(*lValueLoadOp)))
+ return failure();
-static LogicalResult printOperation(CppEmitter &emitter,
- emitc::SubscriptOp subscriptOp) {
- // Add name to cache so that `hasValueInScope` works.
- emitter.getOrCreateName(subscriptOp.getResult());
- return success();
+ return emitter.emitOperand(lValueLoadOp.getOperand());
}
static LogicalResult printBinaryOperation(CppEmitter &emitter,
@@ -621,9 +630,7 @@ static LogicalResult printOperation(CppEmitter &emitter,
if (t.getType().isIndex()) {
int64_t idx = t.getInt();
Value operand = op.getOperand(idx);
- auto literalDef =
- dyn_cast_if_present<LiteralOp>(operand.getDefiningOp());
- if (!literalDef && !emitter.hasValueInScope(operand))
+ if (!emitter.hasValueInScope(operand))
return op.emitOpError("operand ")
<< idx << "'s value not defined in scope";
os << emitter.getOrCreateName(operand);
@@ -660,6 +667,10 @@ static LogicalResult printOperation(CppEmitter &emitter,
emitc::ApplyOp applyOp) {
raw_ostream &os = emitter.ostre...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/91475
More information about the Mlir-commits
mailing list