Author: Sirui Mu Date: 2025-05-20T18:11:19+08:00 New Revision: 838ddc28f222ae244626a827a433fd9235546d3d
URL: https://github.com/llvm/llvm-project/commit/838ddc28f222ae244626a827a433fd9235546d3d DIFF: https://github.com/llvm/llvm-project/commit/838ddc28f222ae244626a827a433fd9235546d3d.diff LOG: [CIR] Add support for indirect calls (#139748) This PR adds support for indirect calls to the `cir.call` operation. Added: Modified: clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h clang/include/clang/CIR/Dialect/IR/CIROps.td clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenCall.cpp clang/lib/CIR/CodeGen/CIRGenCall.h clang/lib/CIR/CodeGen/CIRGenExpr.cpp clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h clang/lib/CIR/CodeGen/CIRGenTypes.h clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp clang/test/CIR/CodeGen/call.cpp clang/test/CIR/IR/call.cir Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index a63bf4f8858d0..b680e4162a5ce 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -225,6 +225,14 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { callee.getFunctionType().getReturnType(), operands); } + cir::CallOp createIndirectCallOp(mlir::Location loc, + mlir::Value indirectTarget, + cir::FuncType funcType, + mlir::ValueRange operands) { + return create<cir::CallOp>(loc, indirectTarget, funcType.getReturnType(), + operands); + } + //===--------------------------------------------------------------------===// // Cast/Conversion Operators //===--------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index d3411973129a4..8267df92e3187 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1843,13 +1843,8 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []> DeclareOpInterfaceMethods<SymbolUserOpInterface>])> { let extraClassDeclaration = [{ /// Get the argument operands to the called function. - mlir::OperandRange getArgOperands() { - return getArgs(); - } - - mlir::MutableOperandRange getArgOperandsMutable() { - return getArgsMutable(); - } + mlir::OperandRange getArgOperands(); + mlir::MutableOperandRange getArgOperandsMutable(); /// Return the callee of this operation mlir::CallInterfaceCallable getCallableForCallee() { @@ -1871,8 +1866,17 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []> ::mlir::Attribute removeArgAttrsAttr() { return {}; } ::mlir::Attribute removeResAttrsAttr() { return {}; } + bool isIndirect() { return !getCallee(); } + mlir::Value getIndirectCall(); + void setArg(unsigned index, mlir::Value value) { - setOperand(index, value); + if (!isIndirect()) { + setOperand(index, value); + return; + } + + // For indirect call, the operand list is shifted by one. + setOperand(index + 1, value); } }]; @@ -1884,16 +1888,24 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []> // the upstreaming process moves on. The verifiers is also missing for now, // will add in the future. - dag commonArgs = (ins FlatSymbolRefAttr:$callee, - Variadic<CIR_AnyType>:$args); + dag commonArgs = (ins OptionalAttr<FlatSymbolRefAttr>:$callee, + Variadic<CIR_AnyType>:$args); } def CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> { let summary = "call a function"; let description = [{ - The `cir.call` operation represents a direct call to a function that is - within the same symbol scope as the call. The callee is encoded as a symbol - reference attribute named `callee`. + The `cir.call` operation represents a function call. It could represent + either a direct call or an indirect call. + + If the operation represents a direct call, the callee should be defined + within the same symbol scope as the call. The `callee` attribute contains a + symbol reference to the callee function. All operands of this operation are + arguments to the callee function. + + If the operation represents an indirect call, the `callee` attribute is + empty. The first operand of this operation must be a pointer to the callee + function. The rest operands are arguments to the callee function. Example: @@ -1905,14 +1917,25 @@ def CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> { let results = (outs Optional<CIR_AnyType>:$result); let arguments = commonArgs; - let builders = [OpBuilder<(ins "mlir::SymbolRefAttr":$callee, - "mlir::Type":$resType, - "mlir::ValueRange":$operands), [{ + let builders = [ + // Build a call op for a direct call + OpBuilder<(ins "mlir::SymbolRefAttr":$callee, "mlir::Type":$resType, + "mlir::ValueRange":$operands), [{ + assert(callee && "callee attribute is required for direct call"); $_state.addOperands(operands); $_state.addAttribute("callee", callee); if (resType && !isa<VoidType>(resType)) $_state.addTypes(resType); - }]>]; + }]>, + // Build a call op for an indirect call + OpBuilder<(ins "mlir::Value":$calleePtr, "mlir::Type":$resType, + "mlir::ValueRange":$operands), [{ + $_state.addOperands(calleePtr); + $_state.addOperands(operands); + if (resType && !isa<VoidType>(resType)) + $_state.addTypes(resType); + }]>, + ]; } //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index c0207393832f3..7b33d94483d5f 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -92,7 +92,6 @@ struct MissingFeatures { static bool opCallSideEffect() { return false; } static bool opCallNoPrototypeFunc() { return false; } static bool opCallMustTail() { return false; } - static bool opCallIndirect() { return false; } static bool opCallVirtual() { return false; } static bool opCallInAlloca() { return false; } static bool opCallAttrs() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp index bf3851544a3a5..49c50891255c3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp @@ -241,6 +241,7 @@ CIRGenTypes::arrangeFunctionDeclaration(const FunctionDecl *fd) { static cir::CIRCallOpInterface emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc, + cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal, cir::FuncOp directFuncOp, const SmallVectorImpl<mlir::Value> &cirCallArgs) { CIRGenBuilderTy &builder = cgf.getBuilder(); @@ -249,7 +250,13 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc, assert(!cir::MissingFeatures::invokeOp()); assert(builder.getInsertionBlock() && "expected valid basic block"); - assert(!cir::MissingFeatures::opCallIndirect()); + + if (indirectFuncTy) { + // TODO(cir): Set calling convention for indirect calls. + assert(!cir::MissingFeatures::opCallCallConv()); + return builder.createIndirectCallOp(callLoc, indirectFuncVal, + indirectFuncTy, cirCallArgs); + } return builder.createCallOp(callLoc, directFuncOp, cirCallArgs); } @@ -275,6 +282,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo, cir::CIRCallOpInterface *callOp, mlir::Location loc) { QualType retTy = funcInfo.getReturnType(); + cir::FuncType cirFuncTy = getTypes().getFunctionType(funcInfo); SmallVector<mlir::Value, 16> cirCallArgs(args.size()); @@ -326,12 +334,27 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo, assert(!cir::MissingFeatures::invokeOp()); - auto directFuncOp = dyn_cast<cir::FuncOp>(calleePtr); - assert(!cir::MissingFeatures::opCallIndirect()); + cir::FuncType indirectFuncTy; + mlir::Value indirectFuncVal; + cir::FuncOp directFuncOp; + if (auto fnOp = dyn_cast<cir::FuncOp>(calleePtr)) { + directFuncOp = fnOp; + } else { + [[maybe_unused]] mlir::ValueTypeRange<mlir::ResultRange> resultTypes = + calleePtr->getResultTypes(); + [[maybe_unused]] auto funcPtrTy = + mlir::dyn_cast<cir::PointerType>(resultTypes.front()); + assert(funcPtrTy && mlir::isa<cir::FuncType>(funcPtrTy.getPointee()) && + "expected pointer to function"); + + indirectFuncTy = cirFuncTy; + indirectFuncVal = calleePtr->getResult(0); + } + assert(!cir::MissingFeatures::opCallAttrs()); - cir::CIRCallOpInterface theCall = - emitCallLikeOp(*this, loc, directFuncOp, cirCallArgs); + cir::CIRCallOpInterface theCall = emitCallLikeOp( + *this, loc, indirectFuncTy, indirectFuncVal, directFuncOp, cirCallArgs); if (callOp) *callOp = theCall; @@ -431,7 +454,7 @@ void CIRGenFunction::emitCallArgs( auto maybeEmitImplicitObjectSize = [&](size_t i, const Expr *arg, RValue emittedArg) { - if (callee.hasFunctionDecl() || i >= callee.getNumParams()) + if (!callee.hasFunctionDecl() || i >= callee.getNumParams()) return; auto *ps = callee.getParamDecl(i)->getAttr<PassObjectSizeAttr>(); if (!ps) diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.h b/clang/lib/CIR/CodeGen/CIRGenCall.h index d3c241c27d048..605625705a75c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCall.h +++ b/clang/lib/CIR/CodeGen/CIRGenCall.h @@ -25,11 +25,20 @@ class CIRGenFunction; /// Abstract information about a function or function prototype. class CIRGenCalleeInfo { + const clang::FunctionProtoType *calleeProtoTy; clang::GlobalDecl calleeDecl; public: - explicit CIRGenCalleeInfo() : calleeDecl() {} + explicit CIRGenCalleeInfo() : calleeProtoTy(nullptr), calleeDecl() {} + CIRGenCalleeInfo(const clang::FunctionProtoType *calleeProtoTy, + clang::GlobalDecl calleeDecl) + : calleeProtoTy(calleeProtoTy), calleeDecl(calleeDecl) {} CIRGenCalleeInfo(clang::GlobalDecl calleeDecl) : calleeDecl(calleeDecl) {} + + const clang::FunctionProtoType *getCalleeFunctionProtoType() const { + return calleeProtoTy; + } + clang::GlobalDecl getCalleeDecl() const { return calleeDecl; } }; class CIRGenCallee { diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index b8e644d80d747..3b0ade2c52d5b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -931,14 +931,43 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *e) { implicitCast->getCastKind() == CK_BuiltinFnToFnPtr) { return emitCallee(implicitCast->getSubExpr()); } + // When performing an indirect call through a function pointer lvalue, the + // function pointer lvalue is implicitly converted to an rvalue through an + // lvalue-to-rvalue conversion. + assert(implicitCast->getCastKind() == CK_LValueToRValue && + "unexpected implicit cast on function pointers"); } else if (const auto *declRef = dyn_cast<DeclRefExpr>(e)) { // Resolve direct calls. - if (const auto *funcDecl = dyn_cast<FunctionDecl>(declRef->getDecl())) - return emitDirectCallee(cgm, funcDecl); + const auto *funcDecl = cast<FunctionDecl>(declRef->getDecl()); + return emitDirectCallee(cgm, funcDecl); + } else if (isa<MemberExpr>(e)) { + cgm.errorNYI(e->getSourceRange(), + "emitCallee: call to member function is NYI"); + return {}; } - cgm.errorNYI(e->getSourceRange(), "Unsupported callee kind"); - return {}; + assert(!cir::MissingFeatures::opCallPseudoDtor()); + + // Otherwise, we have an indirect reference. + mlir::Value calleePtr; + QualType functionType; + if (const auto *ptrType = e->getType()->getAs<clang::PointerType>()) { + calleePtr = emitScalarExpr(e); + functionType = ptrType->getPointeeType(); + } else { + functionType = e->getType(); + calleePtr = emitLValue(e).getPointer(); + } + assert(functionType->isFunctionType()); + + GlobalDecl gd; + if (const auto *vd = + dyn_cast_or_null<VarDecl>(e->getReferencedDeclOfCallee())) + gd = GlobalDecl(vd); + + CIRGenCalleeInfo calleeInfo(functionType->getAs<FunctionProtoType>(), gd); + CIRGenCallee callee(calleeInfo, calleePtr.getDefiningOp()); + return callee; } RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e, diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h index b74460b09a44e..59b6bc9109894 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_CIR_CIRGENFUNCTIONINFO_H #include "clang/AST/CanonicalType.h" +#include "clang/CIR/MissingFeatures.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/Support/TrailingObjects.h" diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h index aaad7f933c60d..a0f546d0a7cd6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.h +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h @@ -68,7 +68,6 @@ class CIRGenTypes { /// types will be in this set. llvm::SmallPtrSet<const clang::Type *, 4> recordsBeingLaidOut; - llvm::SmallPtrSet<const CIRGenFunctionInfo *, 4> functionsBeingProcessed; /// Heper for convertType. mlir::Type convertFunctionTypeInternal(clang::QualType ft); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index bd1aa1f4fe5bb..36dcbc6a4be4a 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -465,15 +465,35 @@ OpFoldResult cir::CastOp::fold(FoldAdaptor adaptor) { // CallOp //===----------------------------------------------------------------------===// +mlir::OperandRange cir::CallOp::getArgOperands() { + if (isIndirect()) + return getArgs().drop_front(1); + return getArgs(); +} + +mlir::MutableOperandRange cir::CallOp::getArgOperandsMutable() { + mlir::MutableOperandRange args = getArgsMutable(); + if (isIndirect()) + return args.slice(1, args.size() - 1); + return args; +} + +mlir::Value cir::CallOp::getIndirectCall() { + assert(isIndirect()); + return getOperand(0); +} + /// Return the operand at index 'i'. Value cir::CallOp::getArgOperand(unsigned i) { - assert(!cir::MissingFeatures::opCallIndirect()); + if (isIndirect()) + ++i; return getOperand(i); } /// Return the number of operands. unsigned cir::CallOp::getNumArgOperands() { - assert(!cir::MissingFeatures::opCallIndirect()); + if (isIndirect()) + return this->getOperation()->getNumOperands() - 1; return this->getOperation()->getNumOperands(); } @@ -484,9 +504,15 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser, mlir::FlatSymbolRefAttr calleeAttr; llvm::ArrayRef<mlir::Type> allResultTypes; + // If we cannot parse a string callee, it means this is an indirect call. if (!parser.parseOptionalAttribute(calleeAttr, "callee", result.attributes) - .has_value()) - return mlir::failure(); + .has_value()) { + OpAsmParser::UnresolvedOperand indirectVal; + // Do not resolve right now, since we need to figure out the type + if (parser.parseOperand(indirectVal).failed()) + return failure(); + ops.push_back(indirectVal); + } if (parser.parseLParen()) return mlir::failure(); @@ -518,13 +544,21 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser, static void printCallCommon(mlir::Operation *op, mlir::FlatSymbolRefAttr calleeSym, + mlir::Value indirectCallee, mlir::OpAsmPrinter &printer) { printer << ' '; auto callLikeOp = mlir::cast<cir::CIRCallOpInterface>(op); auto ops = callLikeOp.getArgOperands(); - printer.printAttributeWithoutType(calleeSym); + if (calleeSym) { + // Direct calls + printer.printAttributeWithoutType(calleeSym); + } else { + // Indirect calls + assert(indirectCallee); + printer << indirectCallee; + } printer << "(" << ops << ")"; printer.printOptionalAttrDict(op->getAttrs(), {"callee"}); @@ -540,15 +574,18 @@ mlir::ParseResult cir::CallOp::parse(mlir::OpAsmParser &parser, } void cir::CallOp::print(mlir::OpAsmPrinter &p) { - printCallCommon(*this, getCalleeAttr(), p); + mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr; + printCallCommon(*this, getCalleeAttr(), indirectCallee, p); } static LogicalResult verifyCallCommInSymbolUses(mlir::Operation *op, SymbolTableCollection &symbolTable) { auto fnAttr = op->getAttrOfType<FlatSymbolRefAttr>("callee"); - if (!fnAttr) - return mlir::failure(); + if (!fnAttr) { + // This is an indirect call, thus we don't have to check the symbol uses. + return mlir::success(); + } auto fn = symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, fnAttr); if (!fn) diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 55cec3072bb86..365569ce1f48a 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -674,8 +674,15 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>( converter->convertType(fn.getFunctionType())); } else { // indirect call - assert(!cir::MissingFeatures::opCallIndirect()); - return op->emitError("Indirect calls are NYI"); + assert(!op->getOperands().empty() && + "operands list must no be empty for the indirect call"); + auto calleeTy = op->getOperands().front().getType(); + auto calleePtrTy = cast<cir::PointerType>(calleeTy); + auto calleeFuncTy = cast<cir::FuncType>(calleePtrTy.getPointee()); + calleeFuncTy.dump(); + converter->convertType(calleeFuncTy).dump(); + llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>( + converter->convertType(calleeFuncTy)); } assert(!cir::MissingFeatures::opCallLandingPad()); @@ -1505,6 +1512,15 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter, converter.addConversion([&](cir::BF16Type type) -> mlir::Type { return mlir::BFloat16Type::get(type.getContext()); }); + converter.addConversion([&](cir::FuncType type) -> std::optional<mlir::Type> { + auto result = converter.convertType(type.getReturnType()); + llvm::SmallVector<mlir::Type> arguments; + arguments.reserve(type.getNumInputs()); + if (converter.convertTypes(type.getInputs(), arguments).failed()) + return std::nullopt; + auto varArg = type.isVarArg(); + return mlir::LLVM::LLVMFunctionType::get(result, arguments, varArg); + }); converter.addConversion([&](cir::RecordType type) -> mlir::Type { // Convert struct members. llvm::SmallVector<mlir::Type> llvmMembers; diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp index 3b1ab8b5fc498..8b8f1296b5108 100644 --- a/clang/test/CIR/CodeGen/call.cpp +++ b/clang/test/CIR/CodeGen/call.cpp @@ -42,3 +42,17 @@ int f6() { // LLVM-LABEL: define i32 @_Z2f6v() { // LLVM: %{{.+}} = call i32 @_Z2f5iPib(i32 2, ptr %{{.+}}, i1 false) + +int f7(int (*ptr)(int, int)) { + return ptr(1, 2); +} + +// CIR-LABEL: cir.func @_Z2f7PFiiiE +// CIR: %[[#ptr:]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>>, !cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>> +// CIR-NEXT: %[[#a:]] = cir.const #cir.int<1> : !s32i +// CIR-NEXT: %[[#b:]] = cir.const #cir.int<2> : !s32i +// CIR-NEXT: %{{.+}} = cir.call %[[#ptr]](%[[#a]], %[[#b]]) : (!cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>, !s32i, !s32i) -> !s32i + +// LLVM-LABEL: define i32 @_Z2f7PFiiiE +// LLVM: %[[#ptr:]] = load ptr, ptr %{{.+}} +// LLVM-NEXT: %{{.+}} = call i32 %[[#ptr]](i32 1, i32 2) diff --git a/clang/test/CIR/IR/call.cir b/clang/test/CIR/IR/call.cir index 8276c0cb9e39d..e35c201b6ed48 100644 --- a/clang/test/CIR/IR/call.cir +++ b/clang/test/CIR/IR/call.cir @@ -43,4 +43,18 @@ cir.func @f6() -> !s32i { // CHECK-NEXT: cir.return %[[#c]] : !s32i // CHECK-NEXT: } +cir.func @f7(%arg0: !cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>) -> !s32i { + %0 = cir.const #cir.int<1> : !s32i + %1 = cir.const #cir.int<2> : !s32i + %2 = cir.call %arg0(%0, %1) : (!cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>, !s32i, !s32i) -> !s32i + cir.return %2 : !s32i +} + +// CHECK: cir.func @f7(%[[ptr:.+]]: !cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>) -> !s32i { +// CHECK-NEXT: %[[#a:]] = cir.const #cir.int<1> : !s32i +// CHECK-NEXT: %[[#b:]] = cir.const #cir.int<2> : !s32i +// CHECK-NEXT: %[[#ret:]] = cir.call %[[ptr]](%[[#a]], %[[#b]]) : (!cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>, !s32i, !s32i) -> !s32i +// CHECK-NEXT: cir.return %[[#ret]] : !s32i +// CHECK-NEXT: } + } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits