https://github.com/erichkeane created https://github.com/llvm/llvm-project/pull/214789
When these appear in an AttributedStmt, we have to annotate the call with these attributes. This patch implements them for all of the CallOpInterface types. >From b378b9fc224cfb5ba9b3a8374e610e92245d051b Mon Sep 17 00:00:00 2001 From: Erich Keane <[email protected]> Date: Fri, 7 Aug 2026 08:09:34 -0700 Subject: [PATCH] [CIR] Implement alwaysinline/noinline on call sites When these appear in an AttributedStmt, we have to annotate the call with these attributes. This patch implements them for all of the CallOpInterface types. --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 1 + .../clang/CIR/Interfaces/CIROpInterfaces.td | 20 ++++++- clang/lib/CIR/CodeGen/CIRGenCall.cpp | 15 +++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 6 ++ clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 14 ++++- clang/lib/CIR/CodeGen/TargetInfo.h | 18 ++++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 8 +++ .../CodeGen/callsite-inline-attributes.cpp | 55 +++++++++++++++++++ 8 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 clang/test/CIR/CodeGen/callsite-inline-attributes.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 49ecec207cd45..cf2a3f0bdad88 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4540,6 +4540,7 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []> dag commonArgs = (ins OptionalAttr<FlatSymbolRefAttr>:$callee, Variadic<CIR_AnyType>:$args, UnitAttr:$nothrow, + OptionalAttr<CIR_InlineKind>:$inline_kind, UnitAttr:$musttail, DefaultValuedAttr<CIR_SideEffect, "SideEffect::All">:$side_effect, OptionalAttr<DictArrayAttr>:$arg_attrs, diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td index 9f0e91c0ac40c..08f6bdc024866 100644 --- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td +++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td @@ -23,8 +23,6 @@ let cppNamespace = "::cir" in { // that's useful for handling indirect calls and other details. def CIRCallOpInterface : OpInterface<"CIRCallOpInterface", [CallOpInterface]> { - // Currently we don't have any methods defined in CIRCallOpInterface. We'll - // add more methods as the upstreaming proceeds. let methods = [ InterfaceMethod< "Return the operand at index 'i', accounts for indirect call or " @@ -47,6 +45,24 @@ let cppNamespace = "::cir" in { "bool", "getNothrow", (ins)>, InterfaceMethod<"Return the side effects of the call operation", "cir::SideEffect", "getSideEffect", (ins)>, + InterfaceMethod<[{"Set the inline-kind of a call operation"}], + "void", "setInlineKind", + (ins "std::optional<cir::InlineKind>":$kind), [{}], + /*defaultImplementation=*/[{ + $_op.setInlineKind(kind); + }]>, + InterfaceMethod<[{"Get the inline-kind of a call operation"}], + "std::optional<cir::InlineKind>", "getInlineKind", + (ins), [{}], + /*defaultImplementation=*/[{ + $_op.getInlineKind(); + }]>, + InterfaceMethod<[{"Get the inline-kind attribute name"}], + "mlir::StringAttr", "getInlineKindAttrName", + (ins), [{}], + /*defaultImplementation=*/[{ + $_op.getInlineKindAttrName(); + }]>, ]; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp index 28670cf31694a..940294c923179 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp @@ -1179,6 +1179,10 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo, SmallVector<mlir::Value, 16> cirCallArgs(args.size()); + const Decl *targetDecl = callee.getAbstractInfo().getCalleeDecl().getDecl(); + const FunctionDecl *callerDecl = dyn_cast_or_null<FunctionDecl>(curCodeDecl); + const FunctionDecl *calleeDecl = dyn_cast_or_null<FunctionDecl>(targetDecl); + assert(!cir::MissingFeatures::emitLifetimeMarkers()); // Translate all of the arguments as necessary to match the CIR lowering. @@ -1350,6 +1354,17 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo, if (callOp) *callOp = theCall; + // Sema/emitAttributedStmt (see + // https://github.com/llvm/llvm-project/issues/214764) should one-day enforce + // that only one of these is valid at a time. For now, we have the same 'bug' + // as classic codegen where we can end up having BOTH of these. + if (inNoInlineAttributedStmt) + theCall.setInlineKind(cir::InlineKind::NoInline); + if (inAlwaysInlineAttributedStmt && + !cgm.getTargetCIRGenInfo().wouldInliningViolateFunctionCallABI( + callerDecl, calleeDecl)) + theCall.setInlineKind(cir::InlineKind::AlwaysInline); + if (isMustTail) { // PPC/MIPS have some diagnostics for classic-codegen, but we don't support // them yet. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index d318338187f12..9f8454309f13a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -617,6 +617,12 @@ class CIRGenFunction : public CIRGenTypeCache { } }; + /// True if the current statement has noinline attribute. + bool inNoInlineAttributedStmt = false; + + /// True if the current statement has always_inline attribute. + bool inAlwaysInlineAttributedStmt = false; + // The CallExpr within the current statement that the musttail attribute // applies to. nullptr if there is no 'musttail' on the current statement. const CallExpr *mustTailCall = nullptr; diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index ceda5811cd065..38b65f16197eb 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -90,6 +90,8 @@ mlir::LogicalResult CIRGenFunction::emitCompoundStmtWithoutScope( mlir::LogicalResult CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { + bool noinline = false; + bool alwaysinline = false; const CallExpr *musttail = nullptr; for (const Attr *attr : s.getAttrs()) { @@ -97,14 +99,19 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { default: break; case attr::NoMerge: - case attr::NoInline: - case attr::AlwaysInline: case attr::NoConvergent: case attr::Atomic: + case attr::AMDGPUAvailableVisible: case attr::HLSLControlFlowHint: cgm.errorNYI(s.getSourceRange(), "Unimplemented statement attribute: ", attr->getKind()); break; + case attr::NoInline: + noinline = true; + break; + case attr::AlwaysInline: + alwaysinline = true; + break; case attr::MustTail: { const Stmt *sub = s.getSubStmt(); const ReturnStmt *ret = cast<ReturnStmt>(sub); @@ -124,6 +131,9 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { } } + SaveAndRestore save_noinline(inNoInlineAttributedStmt, noinline); + SaveAndRestore save_alwaysinline(inAlwaysInlineAttributedStmt, alwaysinline); + SaveAndRestore save_musttail(mustTailCall, musttail); return emitStmt(s.getSubStmt(), /*useCurrentScope=*/true, s.getAttrs()); diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h index 308d472234f99..e720a4ad2ec5c 100644 --- a/clang/lib/CIR/CodeGen/TargetInfo.h +++ b/clang/lib/CIR/CodeGen/TargetInfo.h @@ -114,6 +114,24 @@ class TargetCIRGenInfo { /// right thing when calling a function with no know signature. virtual bool isNoProtoCallVariadic(const FunctionNoProtoType *fnType) const; + /// Returns true if inlining the function call would produce incorrect code + /// for the current target and should be ignored (even with the always_inline + /// or flatten attributes). + /// + /// Note: This probably should be handled in LLVM. However, the LLVM + /// `alwaysinline` attribute currently means the inliner will ignore + /// mismatched attributes (which sometimes can generate invalid code). So, + /// this hook allows targets to avoid adding the LLVM `alwaysinline` attribute + /// based on C/C++ attributes or other target-specific reasons. + /// + /// See previous discussion here: + /// https://discourse.llvm.org/t/rfc-avoid-inlining-alwaysinline-functions-when-they-cannot-be-inlined/79528 + virtual bool + wouldInliningViolateFunctionCallABI(const FunctionDecl *Caller, + const FunctionDecl *Callee) const { + return false; + } + /// Provides a convenient hook to handle extra target-specific attributes /// for the given global. /// In OG, the function receives an llvm::GlobalValue. However, functions diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 4e923f2bcee28..cc5a49d0effcb 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -2044,6 +2044,7 @@ static void lowerCallAttributes(cir::CIRCallOpInterface op, attr.getName() == CIRDialect::getNoThrowAttrName() || attr.getName() == CIRDialect::getNoUnwindAttrName() || attr.getName() == CIRDialect::getNoReturnAttrName() || + attr.getName() == op.getInlineKindAttrName() || attr.getName() == CIRDialect::getMustTailAttrName()) continue; @@ -2155,6 +2156,13 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, newOp.setNoreturn(noReturn); if (op->hasAttr(CIRDialect::getMustTailAttrName())) newOp.setTailCallKind(mlir::LLVM::TailCallKind::MustTail); + + if (std::optional<cir::InlineKind> inlineKind = call.getInlineKind()) { + newOp.setNoInline(*inlineKind == cir::InlineKind::NoInline); + newOp.setInlineHint(*inlineKind == cir::InlineKind::InlineHint); + newOp.setAlwaysInline(*inlineKind == cir::InlineKind::AlwaysInline); + } + } return mlir::success(); diff --git a/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp b/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp new file mode 100644 index 0000000000000..06422d36539c6 --- /dev/null +++ b/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s + +void callee(); + +void (*fptr)(void) = &callee; + +void caller() { + // CIR-LABEL: cir.func{{.*}}@_Z6callerv() + + [[clang::always_inline]] + callee(); + // CIR: cir.call @_Z6calleev() {inline_kind = 2 : i32} + // LLVM: call void @_Z6calleev() #[[ALWAYSINLINE:.*]] + [[clang::noinline]] + callee(); + // CIR: cir.call @_Z6calleev() {inline_kind = 1 : i32} + // LLVM: call void @_Z6calleev() #[[NOINLINE:.*]] + + [[clang::always_inline]] + fptr(); + // CIR: cir.call %{{.*}}() {inline_kind = 2 : i32} + // LLVM: call void %{{.*}}() #[[ALWAYSINLINE]] + [[clang::noinline]] + fptr(); + // CIR: cir.call %{{.*}}() {inline_kind = 1 : i32} + // LLVM: call void %{{.*}}() #[[NOINLINE]] + + [[clang::always_inline]] + { + callee(); + // CIR: cir.call @_Z6calleev() {inline_kind = 2 : i32} + // LLVM: call void @_Z6calleev() #[[ALWAYSINLINE]] + fptr(); + // CIR: cir.call %{{.*}}() {inline_kind = 2 : i32} + // LLVM: call void %{{.*}}() #[[ALWAYSINLINE]] + } + + [[clang::noinline]] + { + callee(); + // CIR: cir.call @_Z6calleev() {inline_kind = 1 : i32} + // LLVM: call void @_Z6calleev() #[[NOINLINE]] + fptr(); + // CIR: cir.call %{{.*}}() {inline_kind = 1 : i32} + // LLVM: call void %{{.*}}() #[[NOINLINE]] + } +} + +// LLVM: attributes #[[ALWAYSINLINE]] = { alwaysinline } +// LLVM: attributes #[[NOINLINE]] = { noinline } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
