[clang] [CIR] Emit the frame-pointer function attribute and module flag (PR #230697)
Adam Smith via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 13 08:46:17 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/230697
>From cfc534a2edf0723f35da481f58020625c1945206 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Fri, 9 Oct 2026 15:43:45 -0700
Subject: [PATCH 1/5] [CIR] Emit the frame-pointer function attribute and
module flag
CIR now honors -mframe-pointer= the way classic CodeGen does. Functions
and the module carry a new #cir.frame_pointer attribute, which lowers to
the "frame-pointer" function attribute and module flag.
Assisted-by: Cursor / claude-opus-5.5
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 45 +++++++++
.../clang/CIR/Dialect/IR/CIRDialect.td | 2 +
clang/include/clang/CIR/Dialect/IR/CIROps.td | 3 +-
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 23 ++++-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 6 ++
clang/lib/CIR/CodeGen/CIRGenModule.h | 6 ++
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 39 ++++++--
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 25 +++++
.../Lowering/DirectToLLVM/LowerToLLVMIR.cpp | 22 +++++
clang/test/CIR/CodeGen/frame-pointer.cpp | 93 ++++++++++++++++++
clang/test/CIR/IR/func-attrs.cir | 31 ++++++
clang/test/CIR/IR/invalid-func-attr.cir | 19 ++++
.../CIR/IR/invalid-module-frame-pointer.cir | 19 ++++
clang/test/CIR/Lowering/frame-pointer.cir | 96 +++++++++++++++++++
14 files changed, 418 insertions(+), 11 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/frame-pointer.cpp
create mode 100644 clang/test/CIR/IR/invalid-module-frame-pointer.cir
create mode 100644 clang/test/CIR/Lowering/frame-pointer.cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index eb4cf65b919c49..e9eac89b34ec5f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1454,6 +1454,51 @@ def CIR_UnwindTableKindAttr : CIR_EnumAttr<CIR_UnwindTableKind, "uwtable"> {
}];
}
+//===----------------------------------------------------------------------===//
+// FramePointerKind
+//===----------------------------------------------------------------------===//
+def CIR_FramePointerKind : CIR_I32Enum<"FramePointerKind",
+ "Frame Pointer Kind", [
+ I32EnumCase<"None", 0, "none">,
+ I32EnumCase<"NonLeaf", 1, "non_leaf">,
+ I32EnumCase<"All", 2, "all">,
+ I32EnumCase<"Reserved", 3, "reserved">,
+ I32EnumCase<"NonLeafNoReserve", 4, "non_leaf_no_reserve">
+]>;
+
+def CIR_FramePointerKindAttr
+ : CIR_EnumAttr<CIR_FramePointerKind, "frame_pointer"> {
+ let summary = "Encodes the frame pointer kind for a function or a module.";
+
+ let description = [{
+ This mirrors the FramePointerKind enum of the LLVM dialect. On a
+ `cir.func` it is the `frame_pointer` attribute and lowers to the
+ `frame-pointer` function attribute in LLVM IR. As the value of the
+ module attribute `cir.frame_pointer` it lowers to the `frame-pointer`
+ module flag, which LLVM gives to the functions it synthesizes. A
+ function or module without the attribute behaves as `none`.
+
+ `none` lets the frame pointer be eliminated. `all` keeps the frame
+ pointer, and `non_leaf` keeps it if the function calls other functions.
+ `reserved` lets the frame pointer be eliminated but keeps its register
+ from being used for anything else, so the register either points to a
+ valid frame record for the current function or is left unmodified.
+ `non_leaf_no_reserve` is `non_leaf`, except that a leaf function may use
+ the frame pointer register as a general-purpose register.
+
+ Example:
+
+ ```
+ module attributes {cir.frame_pointer = #cir.frame_pointer<all>} {
+ cir.func private @f() attributes {frame_pointer = #cir.frame_pointer<all>}
+ cir.func private @g() attributes {
+ frame_pointer = #cir.frame_pointer<non_leaf>
+ }
+ }
+ ```
+ }];
+}
+
//===----------------------------------------------------------------------===//
// GloblCtorAttr
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 2086646949a116..c8cf2870a7eb5c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -52,6 +52,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getNoReturnAttrName() { return "noreturn"; }
static llvm::StringRef getMemoryEffectsAttrName() { return "memory_effects"; }
static llvm::StringRef getUwtableAttrName() { return "uwtable"; }
+ static llvm::StringRef getFramePointerAttrName() { return "frame_pointer"; }
static llvm::StringRef getReturnsTwiceAttrName() { return "returns_twice"; }
static llvm::StringRef getColdAttrName() { return "cold"; }
static llvm::StringRef getHotAttrName() { return "hot"; }
@@ -67,6 +68,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getNoUnwindAttrName() { return "nounwind"; }
static llvm::StringRef getWillReturnAttrName() { return "willreturn"; }
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
+ static llvm::StringRef getModuleFramePointerAttrName() { return "cir.frame_pointer"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
static llvm::StringRef getOffloadContainerAttrName() { return "cir.offload.container"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 53f693a7863eee..4c26dcf630ecc9 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4362,7 +4362,8 @@ def CIR_FuncOp : CIR_Op<"func", [
CIR_OptionalPriorityAttr:$global_dtor_priority,
OptionalAttr<CIR_FuncInfoAttr>:$func_info,
OptionalAttr<CIR_AnnotationArrayAttr>:$annotations,
- OptionalAttr<CIR_UnwindTableKindAttr>:$uwtable
+ OptionalAttr<CIR_UnwindTableKindAttr>:$uwtable,
+ OptionalAttr<CIR_FramePointerKindAttr>:$frame_pointer
);
let regions = (region AnyRegion:$body);
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index ec4dde920a2707..6499f3dcceab5c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -204,6 +204,23 @@ getZeroCallUsedRegsKindStr(llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind k) {
llvm_unreachable("Unknown kind?");
}
+std::optional<cir::FramePointerKind>
+CIRGenModule::getFramePointerKind(CodeGenOptions::FramePointerKind kind) {
+ switch (kind) {
+ case CodeGenOptions::FramePointerKind::None:
+ return std::nullopt;
+ case CodeGenOptions::FramePointerKind::Reserved:
+ return cir::FramePointerKind::Reserved;
+ case CodeGenOptions::FramePointerKind::NonLeafNoReserve:
+ return cir::FramePointerKind::NonLeafNoReserve;
+ case CodeGenOptions::FramePointerKind::NonLeaf:
+ return cir::FramePointerKind::NonLeaf;
+ case CodeGenOptions::FramePointerKind::All:
+ return cir::FramePointerKind::All;
+ }
+ llvm_unreachable("Unknown frame pointer kind");
+}
+
/// Add default attributes to a function, which have merge semantics under
/// -mlink-builtin-bitcode and should not simply overwrite any existing
/// attributes in the linked library.
@@ -235,7 +252,11 @@ static void addTrivialDefaultFunctionAttributes(
attrs.set(cir::CIRDialect::getTrapFuncNameAttrName(),
mlir::StringAttr::get(mlirCtx, codeGenOpts.TrapFuncName));
} else {
- // TODO(cir): Set frame pointer attribute here.
+ if (std::optional<cir::FramePointerKind> kind =
+ CIRGenModule::getFramePointerKind(codeGenOpts.getFramePointer()))
+ attrs.set(cir::CIRDialect::getFramePointerAttrName(),
+ cir::FramePointerKindAttr::get(mlirCtx, *kind));
+
// TODO(cir): a number of other attribute 1-offs based on codegen/lang opts
// should be done here: less-recise-fpmad null-pointer-is-valid
// no-trapping-math
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 1f80f4e0ebf085..f4074aecb051ca 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3923,6 +3923,12 @@ void CIRGenModule::release() {
emitGlobalAnnotations();
+ if (std::optional<cir::FramePointerKind> kind =
+ getFramePointerKind(codeGenOpts.getFramePointer()))
+ theModule->setAttr(
+ cir::CIRDialect::getModuleFramePointerAttrName(),
+ cir::FramePointerKindAttr::get(&getMLIRContext(), *kind));
+
if (!recordLayoutEntries.empty())
theModule->setAttr(
cir::CIRDialect::getRecordLayoutsAttrName(),
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 05ba8b5ee212e0..ed5b7409eaa122 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -385,6 +385,12 @@ class CIRGenModule : public CIRGenTypeCache {
bool attrOnCallSite,
mlir::NamedAttrList &attrs);
+ /// Returns the frame pointer kind to record on functions and on the module,
+ /// or std::nullopt when \p kind is CodeGenOptions::FramePointerKind::None,
+ /// the default, which is left implicit.
+ static std::optional<cir::FramePointerKind>
+ getFramePointerKind(CodeGenOptions::FramePointerKind kind);
+
/// Will return a global variable of the given type. If a variable with a
/// different type already exists then a new variable with the right type
/// will be created and all uses of the old variable will be replaced with a
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 8a62ae541b4b6e..96c6c05e0a11af 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -272,6 +272,16 @@ cir::CIRDialect::verifyOperationAttribute(mlir::Operation *op,
<< "' attribute to be attached to '"
<< mlir::ModuleOp::getOperationName() << "'";
+ if (attrName == getModuleFramePointerAttrName()) {
+ if (!mlir::isa<mlir::ModuleOp>(op))
+ return op->emitOpError() << "expects '" << getModuleFramePointerAttrName()
+ << "' attribute to be attached to '"
+ << mlir::ModuleOp::getOperationName() << "'";
+ if (!mlir::isa<cir::FramePointerKindAttr>(attr.getValue()))
+ return op->emitOpError() << "expects '" << getModuleFramePointerAttrName()
+ << "' to be a #cir.frame_pointer attribute";
+ }
+
// LoweringPrepare uses this attribute directly as the fatbin global's
// initializer, so it must be a valid #cir.const_array payload for its type.
if (attrName == getCUDADeviceBinaryAttrName()) {
@@ -1371,9 +1381,10 @@ parseTryCallDestinations(mlir::OpAsmParser &parser,
return mlir::success();
}
-/// Reject an effect attribute of the wrong kind in an explicit attribute
-/// dictionary. Where these are declared they are stored as properties, so a
-/// value of the wrong kind would be dropped without a diagnostic.
+/// Reject an attribute in an explicit attribute dictionary whose value is not
+/// the attribute class expected for its name. Where an op declares the
+/// attribute it is stored as a property, so a value of the wrong class would
+/// be dropped without a diagnostic.
static ParseResult checkEffectAttrKinds(mlir::OpAsmParser &parser,
llvm::SMLoc loc,
const mlir::NamedAttrList &attrs) {
@@ -1390,6 +1401,13 @@ static ParseResult checkEffectAttrKinds(mlir::OpAsmParser &parser,
<< CIRDialect::getUwtableAttrName()
<< "' must be a #cir.uwtable attribute";
+ if (mlir::Attribute framePointer =
+ attrs.get(CIRDialect::getFramePointerAttrName()))
+ if (!mlir::isa<cir::FramePointerKindAttr>(framePointer))
+ return parser.emitError(loc, "attribute '")
+ << CIRDialect::getFramePointerAttrName()
+ << "' must be a #cir.frame_pointer attribute";
+
for (llvm::StringRef name :
{CIRDialect::getNoUnwindAttrName(), CIRDialect::getWillReturnAttrName(),
CIRDialect::getMustProgressAttrName()})
@@ -3098,11 +3116,13 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
return failure();
// Every other declared attribute has dedicated syntax above, so
- // memory_effects and uwtable is the only one the explicit list may carry.
- // Without the exception cir.func could not parse back what it prints.
+ // memory_effects, uwtable and frame_pointer are the only ones the explicit
+ // list may carry. Without these exceptions cir.func could not parse back
+ // what it prints.
for (StringRef disallowed : cir::FuncOp::getAttributeNames()) {
if (disallowed == CIRDialect::getMemoryEffectsAttrName() ||
- disallowed == CIRDialect::getUwtableAttrName())
+ disallowed == CIRDialect::getUwtableAttrName() ||
+ disallowed == CIRDialect::getFramePointerAttrName())
continue;
if (parsedAttrs.get(disallowed))
return parser.emitError(loc, "attribute '")
@@ -3295,12 +3315,13 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
}
// Every declared attribute is printed by the syntax above, except
- // memory_effects and uwtable, which have none and so must reach the
- // dictionary.
+ // memory_effects, uwtable and frame_pointer, which have none and so must
+ // reach the dictionary.
llvm::SmallVector<llvm::StringRef> elidedAttrs;
for (llvm::StringRef name : cir::FuncOp::getAttributeNames())
if (name != CIRDialect::getMemoryEffectsAttrName() &&
- name != CIRDialect::getUwtableAttrName())
+ name != CIRDialect::getUwtableAttrName() &&
+ name != CIRDialect::getFramePointerAttrName())
elidedAttrs.push_back(name);
function_interface_impl::printFunctionAttributes(p, *this, elidedAttrs);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index d919ed2e98a5b7..11679fbe530917 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -305,6 +305,26 @@ convertUWTableKind(cir::UnwindTableKind kind) {
llvm_unreachable("Unknown CIR unwind table kind");
}
+static mlir::LLVM::framePointerKind::FramePointerKind
+convertFramePointerKind(cir::FramePointerKind kind) {
+ using CIR = cir::FramePointerKind;
+ using LLVM = mlir::LLVM::framePointerKind::FramePointerKind;
+
+ switch (kind) {
+ case CIR::None:
+ return LLVM::None;
+ case CIR::NonLeaf:
+ return LLVM::NonLeaf;
+ case CIR::All:
+ return LLVM::All;
+ case CIR::Reserved:
+ return LLVM::Reserved;
+ case CIR::NonLeafNoReserve:
+ return LLVM::NonLeafNoReserve;
+ }
+ llvm_unreachable("Unknown CIR frame pointer kind");
+}
+
mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
cir::CopyOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
@@ -2933,6 +2953,11 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
fn.setUwtableKindAttr(mlir::LLVM::UWTableKindAttr::get(
fn.getContext(), convertUWTableKind(*uwtableKind)));
+ if (std::optional<cir::FramePointerKind> framePointerKind =
+ op.getFramePointer())
+ fn.setFramePointerAttr(mlir::LLVM::FramePointerKindAttr::get(
+ fn.getContext(), convertFramePointerKind(*framePointerKind)));
+
// Function attributes with no dedicated field on the LLVM dialect's
// LLVMFuncOp are routed through the `passthrough` array. The MLIR LLVM IR
// translator forwards `passthrough` entries to LLVM IR as function
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
index 213f80f0defb01..71f59bafdb7c92 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
@@ -27,6 +27,23 @@ using namespace llvm;
namespace cir {
namespace direct {
+static llvm::FramePointerKind
+convertFramePointerKind(cir::FramePointerKind kind) {
+ switch (kind) {
+ case cir::FramePointerKind::None:
+ return llvm::FramePointerKind::None;
+ case cir::FramePointerKind::NonLeaf:
+ return llvm::FramePointerKind::NonLeaf;
+ case cir::FramePointerKind::All:
+ return llvm::FramePointerKind::All;
+ case cir::FramePointerKind::Reserved:
+ return llvm::FramePointerKind::Reserved;
+ case cir::FramePointerKind::NonLeafNoReserve:
+ return llvm::FramePointerKind::NonLeafNoReserve;
+ }
+ llvm_unreachable("Unknown CIR frame pointer kind");
+}
+
/// Implementation of the dialect interface that converts CIR attributes to LLVM
/// IR metadata.
class CIRDialectLLVMIRTranslationInterface
@@ -135,6 +152,11 @@ class CIRDialectLLVMIRTranslationInterface
llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
llvm::LLVMContext &llvmContext = llvmModule->getContext();
+ if (attribute.getName() == cir::CIRDialect::getModuleFramePointerAttrName())
+ llvmModule->setFramePointer(convertFramePointerKind(
+ mlir::cast<cir::FramePointerKindAttr>(attribute.getValue())
+ .getValue()));
+
if (attribute.getName() == "cir.amdhsa_code_object_version") {
if (auto intAttr =
mlir::dyn_cast<mlir::IntegerAttr>(attribute.getValue())) {
diff --git a/clang/test/CIR/CodeGen/frame-pointer.cpp b/clang/test/CIR/CodeGen/frame-pointer.cpp
new file mode 100644
index 00000000000000..a99ac0c20e309b
--- /dev/null
+++ b/clang/test/CIR/CodeGen/frame-pointer.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t-default.cir
+// RUN: FileCheck --input-file=%t-default.cir %s --check-prefix=CIR-NONE
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-default-cir.ll
+// RUN: FileCheck --input-file=%t-default-cir.ll %s --check-prefix=LLVM-NONE
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t-default.ll
+// RUN: FileCheck --input-file=%t-default.ll %s --check-prefix=LLVM-NONE
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=none -fclangir -emit-cir %s -o %t-none.cir
+// RUN: FileCheck --input-file=%t-none.cir %s --check-prefix=CIR-NONE
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=none -fclangir -emit-llvm %s -o %t-none-cir.ll
+// RUN: FileCheck --input-file=%t-none-cir.ll %s --check-prefix=LLVM-NONE
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=none -emit-llvm %s -o %t-none.ll
+// RUN: FileCheck --input-file=%t-none.ll %s --check-prefix=LLVM-NONE
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf -fclangir -emit-cir %s -o %t-non-leaf.cir
+// RUN: FileCheck --input-file=%t-non-leaf.cir %s --check-prefix=CIR-NON-LEAF
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf -fclangir -emit-llvm %s -o %t-non-leaf-cir.ll
+// RUN: FileCheck --input-file=%t-non-leaf-cir.ll %s --check-prefix=LLVM-NON-LEAF
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf -emit-llvm %s -o %t-non-leaf.ll
+// RUN: FileCheck --input-file=%t-non-leaf.ll %s --check-prefix=LLVM-NON-LEAF
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=all -fclangir -emit-cir %s -o %t-all.cir
+// RUN: FileCheck --input-file=%t-all.cir %s --check-prefix=CIR-ALL
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=all -fclangir -emit-llvm %s -o %t-all-cir.ll
+// RUN: FileCheck --input-file=%t-all-cir.ll %s --check-prefix=LLVM-ALL
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=all -emit-llvm %s -o %t-all.ll
+// RUN: FileCheck --input-file=%t-all.ll %s --check-prefix=LLVM-ALL
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=reserved -fclangir -emit-cir %s -o %t-reserved.cir
+// RUN: FileCheck --input-file=%t-reserved.cir %s --check-prefix=CIR-RESERVED
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=reserved -fclangir -emit-llvm %s -o %t-reserved-cir.ll
+// RUN: FileCheck --input-file=%t-reserved-cir.ll %s --check-prefix=LLVM-RESERVED
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=reserved -emit-llvm %s -o %t-reserved.ll
+// RUN: FileCheck --input-file=%t-reserved.ll %s --check-prefix=LLVM-RESERVED
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf-no-reserve -fclangir -emit-cir %s -o %t-nlnr.cir
+// RUN: FileCheck --input-file=%t-nlnr.cir %s --check-prefix=CIR-NLNR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf-no-reserve -fclangir -emit-llvm %s -o %t-nlnr-cir.ll
+// RUN: FileCheck --input-file=%t-nlnr-cir.ll %s --check-prefix=LLVM-NLNR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mframe-pointer=non-leaf-no-reserve -emit-llvm %s -o %t-nlnr.ll
+// RUN: FileCheck --input-file=%t-nlnr.ll %s --check-prefix=LLVM-NLNR
+
+void declaration();
+void definition() { declaration(); }
+
+// CIR-NONE-NOT: frame_pointer
+// CIR-NONE: cir.func {{.*}}@_Z10definitionv()
+// CIR-NONE-NOT: frame_pointer
+
+// CIR-NON-LEAF: module {{.*}}attributes {{.*}}cir.frame_pointer = #cir.frame_pointer<non_leaf>
+// CIR-NON-LEAF: cir.func {{.*}}@_Z10definitionv() attributes {{.*}}frame_pointer = #cir.frame_pointer<non_leaf>
+// CIR-NON-LEAF: cir.func private @_Z11declarationv() attributes {{.*}}frame_pointer = #cir.frame_pointer<non_leaf>
+
+// CIR-ALL: module {{.*}}attributes {{.*}}cir.frame_pointer = #cir.frame_pointer<all>
+// CIR-ALL: cir.func {{.*}}@_Z10definitionv() attributes {{.*}}frame_pointer = #cir.frame_pointer<all>
+// CIR-ALL: cir.call @_Z11declarationv() : () -> () loc(
+// CIR-ALL: cir.func private @_Z11declarationv() attributes {{.*}}frame_pointer = #cir.frame_pointer<all>
+
+// CIR-RESERVED: module {{.*}}attributes {{.*}}cir.frame_pointer = #cir.frame_pointer<reserved>
+// CIR-RESERVED: cir.func {{.*}}@_Z10definitionv() attributes {{.*}}frame_pointer = #cir.frame_pointer<reserved>
+// CIR-RESERVED: cir.func private @_Z11declarationv() attributes {{.*}}frame_pointer = #cir.frame_pointer<reserved>
+
+// CIR-NLNR: module {{.*}}attributes {{.*}}cir.frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>
+// CIR-NLNR: cir.func {{.*}}@_Z10definitionv() attributes {{.*}}frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>
+// CIR-NLNR: cir.func private @_Z11declarationv() attributes {{.*}}frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>
+
+// LLVM-NONE: define {{.*}}void @_Z10definitionv()
+// LLVM-NONE-NOT: "frame-pointer"
+
+// LLVM-NON-LEAF: define {{.*}}void @_Z10definitionv() #[[DEF_ATTR:[0-9]+]]
+// LLVM-NON-LEAF: declare void @_Z11declarationv() #[[DECL_ATTR:[0-9]+]]
+// LLVM-NON-LEAF: attributes #[[DEF_ATTR]] = {{.*}}"frame-pointer"="non-leaf"
+// LLVM-NON-LEAF: attributes #[[DECL_ATTR]] = {{.*}}"frame-pointer"="non-leaf"
+// LLVM-NON-LEAF: !{i32 7, !"frame-pointer", i32 1}
+
+// LLVM-ALL: define {{.*}}void @_Z10definitionv() #[[DEF_ATTR:[0-9]+]]
+// LLVM-ALL: call void @_Z11declarationv(){{$}}
+// LLVM-ALL: declare void @_Z11declarationv() #[[DECL_ATTR:[0-9]+]]
+// LLVM-ALL: attributes #[[DEF_ATTR]] = {{.*}}"frame-pointer"="all"
+// LLVM-ALL: attributes #[[DECL_ATTR]] = {{.*}}"frame-pointer"="all"
+// LLVM-ALL: !{i32 7, !"frame-pointer", i32 2}
+
+// LLVM-RESERVED: define {{.*}}void @_Z10definitionv() #[[DEF_ATTR:[0-9]+]]
+// LLVM-RESERVED: declare void @_Z11declarationv() #[[DECL_ATTR:[0-9]+]]
+// LLVM-RESERVED: attributes #[[DEF_ATTR]] = {{.*}}"frame-pointer"="reserved"
+// LLVM-RESERVED: attributes #[[DECL_ATTR]] = {{.*}}"frame-pointer"="reserved"
+// LLVM-RESERVED: !{i32 7, !"frame-pointer", i32 3}
+
+// LLVM-NLNR: define {{.*}}void @_Z10definitionv() #[[DEF_ATTR:[0-9]+]]
+// LLVM-NLNR: declare void @_Z11declarationv() #[[DECL_ATTR:[0-9]+]]
+// LLVM-NLNR: attributes #[[DEF_ATTR]] = {{.*}}"frame-pointer"="non-leaf-no-reserve"
+// LLVM-NLNR: attributes #[[DECL_ATTR]] = {{.*}}"frame-pointer"="non-leaf-no-reserve"
+// LLVM-NLNR: !{i32 7, !"frame-pointer", i32 4}
diff --git a/clang/test/CIR/IR/func-attrs.cir b/clang/test/CIR/IR/func-attrs.cir
index fd51e1cee6f4ae..aa781f6e8f604f 100644
--- a/clang/test/CIR/IR/func-attrs.cir
+++ b/clang/test/CIR/IR/func-attrs.cir
@@ -126,3 +126,34 @@ cir.func no_inline dso_local @uwtableWithOthers() attributes {mustprogress, uwta
// CHECK: cir.func no_inline dso_local @uwtableWithOthers() attributes {mustprogress, uwtable = #cir.uwtable<async>} {
// CHECK: cir.return
// CHECK: }
+
+cir.func @noneFramePointer() attributes {frame_pointer = #cir.frame_pointer<none>} {
+ cir.return
+}
+// CHECK: cir.func @noneFramePointer() attributes {frame_pointer = #cir.frame_pointer<none>} {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @allFramePointer() attributes {frame_pointer = #cir.frame_pointer<all>} {
+ cir.return
+}
+// CHECK: cir.func @allFramePointer() attributes {frame_pointer = #cir.frame_pointer<all>} {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func @nonLeafNoReserveFramePointer() attributes {frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>} {
+ cir.return
+}
+// CHECK: cir.func @nonLeafNoReserveFramePointer() attributes {frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>} {
+// CHECK: cir.return
+// CHECK: }
+
+cir.func private @framePointerDecl() attributes {frame_pointer = #cir.frame_pointer<non_leaf>}
+// CHECK: cir.func private @framePointerDecl() attributes {frame_pointer = #cir.frame_pointer<non_leaf>}
+
+cir.func no_inline dso_local @framePointerWithOthers() attributes {mustprogress, frame_pointer = #cir.frame_pointer<reserved>, uwtable = #cir.uwtable<async>} {
+ cir.return
+}
+// CHECK: cir.func no_inline dso_local @framePointerWithOthers() attributes {mustprogress, frame_pointer = #cir.frame_pointer<reserved>, uwtable = #cir.uwtable<async>} {
+// CHECK: cir.return
+// CHECK: }
diff --git a/clang/test/CIR/IR/invalid-func-attr.cir b/clang/test/CIR/IR/invalid-func-attr.cir
index 81054bda03db98..ac1ea2bc64c96d 100644
--- a/clang/test/CIR/IR/invalid-func-attr.cir
+++ b/clang/test/CIR/IR/invalid-func-attr.cir
@@ -37,3 +37,22 @@ module {
cir.return
}
}
+
+// -----
+
+module {
+ // expected-error at +1 {{attribute 'frame_pointer' must be a #cir.frame_pointer attribute}}
+ cir.func @badFramePointerKind() attributes {frame_pointer = "all"} {
+ cir.return
+ }
+}
+
+// -----
+
+module {
+ // expected-error at +2 {{expected one of [none, non_leaf, all, reserved, non_leaf_no_reserve] for Frame Pointer Kind, got: bogus}}
+ // expected-error at +1 {{failed to parse CIR_FramePointerKindAttr parameter 'value' which is to be a `::cir::FramePointerKind`}}
+ cir.func @badFramePointerSpelling() attributes {frame_pointer = #cir.frame_pointer<bogus>} {
+ cir.return
+ }
+}
diff --git a/clang/test/CIR/IR/invalid-module-frame-pointer.cir b/clang/test/CIR/IR/invalid-module-frame-pointer.cir
new file mode 100644
index 00000000000000..9b5bc18fa18374
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-module-frame-pointer.cir
@@ -0,0 +1,19 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+module attributes {cir.frame_pointer = #cir.frame_pointer<all>} {
+}
+
+// -----
+
+// expected-error at +1 {{expects 'cir.frame_pointer' to be a #cir.frame_pointer attribute}}
+module attributes {cir.frame_pointer = 2 : i32} {
+}
+
+// -----
+
+module {
+ // expected-error at +1 {{expects 'cir.frame_pointer' attribute to be attached to 'builtin.module'}}
+ cir.func @f() attributes {cir.frame_pointer = #cir.frame_pointer<all>} {
+ cir.return
+ }
+}
diff --git a/clang/test/CIR/Lowering/frame-pointer.cir b/clang/test/CIR/Lowering/frame-pointer.cir
new file mode 100644
index 00000000000000..ca3431094212f7
--- /dev/null
+++ b/clang/test/CIR/Lowering/frame-pointer.cir
@@ -0,0 +1,96 @@
+// RUN: cir-opt %s -cir-to-llvm --split-input-file -o - | FileCheck %s -check-prefix=MLIR
+// RUN: cir-translate %s -cir-to-llvmir --disable-cc-lowering --split-input-file | FileCheck %s -check-prefix=LLVM
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+ cir.func @fp_none() attributes {frame_pointer = #cir.frame_pointer<none>} {
+ cir.return
+ }
+ cir.func @fp_non_leaf() attributes {frame_pointer = #cir.frame_pointer<non_leaf>} {
+ cir.return
+ }
+ cir.func @fp_all() attributes {frame_pointer = #cir.frame_pointer<all>} {
+ cir.return
+ }
+ cir.func @fp_reserved() attributes {frame_pointer = #cir.frame_pointer<reserved>} {
+ cir.return
+ }
+ cir.func @fp_non_leaf_no_reserve() attributes {frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>} {
+ cir.return
+ }
+ cir.func private @fp_decl() attributes {frame_pointer = #cir.frame_pointer<all>}
+ cir.func @fp_absent() {
+ cir.return
+ }
+}
+
+// MLIR: llvm.func @fp_none() attributes {frame_pointer = #llvm.framePointerKind<none>
+// MLIR: llvm.func @fp_non_leaf() attributes {frame_pointer = #llvm.framePointerKind<"non-leaf">
+// MLIR: llvm.func @fp_all() attributes {frame_pointer = #llvm.framePointerKind<all>
+// MLIR: llvm.func @fp_reserved() attributes {frame_pointer = #llvm.framePointerKind<reserved>
+// MLIR: llvm.func @fp_non_leaf_no_reserve() attributes {frame_pointer = #llvm.framePointerKind<"non-leaf-no-reserve">
+// MLIR: llvm.func @fp_decl() attributes {frame_pointer = #llvm.framePointerKind<all>
+// MLIR: llvm.func @fp_absent()
+// MLIR-NOT: frame_pointer
+// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<none>
+// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<non_leaf>
+// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<all>
+// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<reserved>
+// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>
+
+// LLVM: define void @fp_none() #[[NONE:[0-9]+]]
+// LLVM: define void @fp_non_leaf() #[[NON_LEAF:[0-9]+]]
+// LLVM: define void @fp_all() #[[ALL:[0-9]+]]
+// LLVM: define void @fp_reserved() #[[RESERVED:[0-9]+]]
+// LLVM: define void @fp_non_leaf_no_reserve() #[[NLNR:[0-9]+]]
+// LLVM: declare void @fp_decl() #[[ALL]]
+// LLVM: define void @fp_absent() {
+// LLVM: attributes #[[NONE]] = { "frame-pointer"="none" }
+// LLVM: attributes #[[NON_LEAF]] = { "frame-pointer"="non-leaf" }
+// LLVM: attributes #[[ALL]] = { "frame-pointer"="all" }
+// LLVM: attributes #[[RESERVED]] = { "frame-pointer"="reserved" }
+// LLVM: attributes #[[NLNR]] = { "frame-pointer"="non-leaf-no-reserve" }
+// LLVM-NOT: !"frame-pointer"
+// LLVM: ; ModuleID
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu", cir.frame_pointer = #cir.frame_pointer<none>} {
+}
+
+// LLVM: !{i32 7, !"frame-pointer", i32 0}
+// LLVM-NOT: !"frame-pointer"
+// LLVM: ; ModuleID
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu", cir.frame_pointer = #cir.frame_pointer<non_leaf>} {
+}
+
+// LLVM: !{i32 7, !"frame-pointer", i32 1}
+// LLVM-NOT: !"frame-pointer"
+// LLVM: ; ModuleID
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu", cir.frame_pointer = #cir.frame_pointer<all>} {
+}
+
+// LLVM: !{i32 7, !"frame-pointer", i32 2}
+// LLVM-NOT: !"frame-pointer"
+// LLVM: ; ModuleID
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu", cir.frame_pointer = #cir.frame_pointer<reserved>} {
+}
+
+// LLVM: !{i32 7, !"frame-pointer", i32 3}
+// LLVM-NOT: !"frame-pointer"
+// LLVM: ; ModuleID
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu", cir.frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>} {
+}
+
+// LLVM: !{i32 7, !"frame-pointer", i32 4}
>From 1164fc45242eaba0f8dc3fcdf3d0ec9664915b53 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Mon, 12 Oct 2026 09:17:12 -0700
Subject: [PATCH 2/5] [CIR] Address reviewer feedback
Assisted-by: Cursor / claude-opus-5.5
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 38 +++++++++++--------
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 13 +++++++
.../Lowering/DirectToLLVM/LowerToLLVMIR.cpp | 22 -----------
clang/test/CIR/Lowering/frame-pointer.cir | 6 +++
4 files changed, 41 insertions(+), 38 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index e9eac89b34ec5f..ff66c4db41c43e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1471,29 +1471,35 @@ def CIR_FramePointerKindAttr
let summary = "Encodes the frame pointer kind for a function or a module.";
let description = [{
- This mirrors the FramePointerKind enum of the LLVM dialect. On a
- `cir.func` it is the `frame_pointer` attribute and lowers to the
- `frame-pointer` function attribute in LLVM IR. As the value of the
- module attribute `cir.frame_pointer` it lowers to the `frame-pointer`
- module flag, which LLVM gives to the functions it synthesizes. A
+ A frame pointer is a register that holds the address of the current
+ function's frame record, so that debuggers, profilers and stack
+ unwinders can walk the call stack by following the chain of frame
+ records. Eliminating it frees the register for other uses. The frame
+ pointer kind says which functions must keep one:
+
+ - `none`: the frame pointer may be eliminated and its register used for
+ other purposes.
+ - `non_leaf`: functions that call other functions keep the frame
+ pointer. A leaf function may eliminate it, but may not use its
+ register for anything else.
+ - `all`: every function keeps the frame pointer.
+ - `reserved`: the frame pointer may be eliminated, but its register is
+ not used for anything else, so it either points to a valid frame
+ record for the current function or is left unmodified.
+ - `non_leaf_no_reserve`: like `non_leaf`, except that a leaf function
+ may use the frame pointer register as a general-purpose register.
+
+ The kind is a minimum: the code generator may keep a frame pointer that
+ the kind allows it to eliminate. On a function the attribute applies
+ to that function. On the module it applies to the functions LLVM
+ creates after CIR is lowered, such as sanitizer constructors. A
function or module without the attribute behaves as `none`.
- `none` lets the frame pointer be eliminated. `all` keeps the frame
- pointer, and `non_leaf` keeps it if the function calls other functions.
- `reserved` lets the frame pointer be eliminated but keeps its register
- from being used for anything else, so the register either points to a
- valid frame record for the current function or is left unmodified.
- `non_leaf_no_reserve` is `non_leaf`, except that a leaf function may use
- the frame pointer register as a general-purpose register.
-
Example:
```
module attributes {cir.frame_pointer = #cir.frame_pointer<all>} {
cir.func private @f() attributes {frame_pointer = #cir.frame_pointer<all>}
- cir.func private @g() attributes {
- frame_pointer = #cir.frame_pointer<non_leaf>
- }
}
```
}];
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 11679fbe530917..719c364533195c 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4564,6 +4564,19 @@ void ConvertCIRToLLVMPass::processCIRAttrs(mlir::ModuleOp module) {
module->setAttr(mlir::LLVM::LLVMDialect::getModuleLevelAsmAttrName(),
asmAttr);
+ if (auto framePointerAttr = module->getAttrOfType<cir::FramePointerKindAttr>(
+ cir::CIRDialect::getModuleFramePointerAttrName())) {
+ mlir::OpBuilder builder(module.getContext());
+ builder.setInsertionPointToEnd(module.getBody());
+ auto flag = mlir::LLVM::ModuleFlagAttr::get(
+ module.getContext(), mlir::LLVM::ModFlagBehavior::Max,
+ builder.getStringAttr("frame-pointer"),
+ builder.getI32IntegerAttr(static_cast<int32_t>(
+ convertFramePointerKind(framePointerAttr.getValue()))));
+ mlir::LLVM::ModuleFlagsOp::create(builder, module.getLoc(),
+ builder.getArrayAttr({flag}));
+ }
+
lowerOpenCLModuleMetadataAttrs(module);
}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
index 71f59bafdb7c92..213f80f0defb01 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp
@@ -27,23 +27,6 @@ using namespace llvm;
namespace cir {
namespace direct {
-static llvm::FramePointerKind
-convertFramePointerKind(cir::FramePointerKind kind) {
- switch (kind) {
- case cir::FramePointerKind::None:
- return llvm::FramePointerKind::None;
- case cir::FramePointerKind::NonLeaf:
- return llvm::FramePointerKind::NonLeaf;
- case cir::FramePointerKind::All:
- return llvm::FramePointerKind::All;
- case cir::FramePointerKind::Reserved:
- return llvm::FramePointerKind::Reserved;
- case cir::FramePointerKind::NonLeafNoReserve:
- return llvm::FramePointerKind::NonLeafNoReserve;
- }
- llvm_unreachable("Unknown CIR frame pointer kind");
-}
-
/// Implementation of the dialect interface that converts CIR attributes to LLVM
/// IR metadata.
class CIRDialectLLVMIRTranslationInterface
@@ -152,11 +135,6 @@ class CIRDialectLLVMIRTranslationInterface
llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
llvm::LLVMContext &llvmContext = llvmModule->getContext();
- if (attribute.getName() == cir::CIRDialect::getModuleFramePointerAttrName())
- llvmModule->setFramePointer(convertFramePointerKind(
- mlir::cast<cir::FramePointerKindAttr>(attribute.getValue())
- .getValue()));
-
if (attribute.getName() == "cir.amdhsa_code_object_version") {
if (auto intAttr =
mlir::dyn_cast<mlir::IntegerAttr>(attribute.getValue())) {
diff --git a/clang/test/CIR/Lowering/frame-pointer.cir b/clang/test/CIR/Lowering/frame-pointer.cir
index ca3431094212f7..d95a53e946fe5c 100644
--- a/clang/test/CIR/Lowering/frame-pointer.cir
+++ b/clang/test/CIR/Lowering/frame-pointer.cir
@@ -31,11 +31,17 @@ module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
// MLIR: llvm.func @fp_decl() attributes {frame_pointer = #llvm.framePointerKind<all>
// MLIR: llvm.func @fp_absent()
// MLIR-NOT: frame_pointer
+// MLIR-NOT: module_flags
// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<none>
+// MLIR-NEXT: llvm.module_flags [#llvm.mlir.module_flag<max, "frame-pointer", 0 : i32>]
// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<non_leaf>
+// MLIR-NEXT: llvm.module_flags [#llvm.mlir.module_flag<max, "frame-pointer", 1 : i32>]
// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<all>
+// MLIR-NEXT: llvm.module_flags [#llvm.mlir.module_flag<max, "frame-pointer", 2 : i32>]
// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<reserved>
+// MLIR-NEXT: llvm.module_flags [#llvm.mlir.module_flag<max, "frame-pointer", 3 : i32>]
// MLIR: module attributes {cir.frame_pointer = #cir.frame_pointer<non_leaf_no_reserve>
+// MLIR-NEXT: llvm.module_flags [#llvm.mlir.module_flag<max, "frame-pointer", 4 : i32>]
// LLVM: define void @fp_none() #[[NONE:[0-9]+]]
// LLVM: define void @fp_non_leaf() #[[NON_LEAF:[0-9]+]]
>From bcd3e6f5ab17bc8b5d1bd7040cf61efdd938888c Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 13 Oct 2026 07:56:03 -0700
Subject: [PATCH 3/5] [CIR] Address reviewer feedback
Assisted-by: Cursor / claude-opus-5.5
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 43 +++++++++----------
1 file changed, 20 insertions(+), 23 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index ff66c4db41c43e..83fb881e76b652 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1471,29 +1471,26 @@ def CIR_FramePointerKindAttr
let summary = "Encodes the frame pointer kind for a function or a module.";
let description = [{
- A frame pointer is a register that holds the address of the current
- function's frame record, so that debuggers, profilers and stack
- unwinders can walk the call stack by following the chain of frame
- records. Eliminating it frees the register for other uses. The frame
- pointer kind says which functions must keep one:
-
- - `none`: the frame pointer may be eliminated and its register used for
- other purposes.
- - `non_leaf`: functions that call other functions keep the frame
- pointer. A leaf function may eliminate it, but may not use its
- register for anything else.
- - `all`: every function keeps the frame pointer.
- - `reserved`: the frame pointer may be eliminated, but its register is
- not used for anything else, so it either points to a valid frame
- record for the current function or is left unmodified.
- - `non_leaf_no_reserve`: like `non_leaf`, except that a leaf function
- may use the frame pointer register as a general-purpose register.
-
- The kind is a minimum: the code generator may keep a frame pointer that
- the kind allows it to eliminate. On a function the attribute applies
- to that function. On the module it applies to the functions LLVM
- creates after CIR is lowered, such as sanitizer constructors. A
- function or module without the attribute behaves as `none`.
+ Mirrors the `frame-pointer` function attribute and module flag of LLVM
+ IR (https://llvm.org/docs/LangRef.html#function-attributes), which tell
+ the code generator whether a function should keep its frame pointer. The
+ code generator may keep a frame pointer even when the kind says it can
+ be eliminated.
+
+ - `none`: the frame pointer can be eliminated, and its register can be
+ used for other purposes.
+ - `reserved`: the frame pointer register must either be updated to point
+ to a valid frame record for the current function, or not be modified.
+ - `non_leaf`: the frame pointer should be kept if the function calls
+ other functions.
+ - `non_leaf_no_reserve`: like `non_leaf`, but a leaf function may use
+ the frame pointer register for other purposes.
+ - `all`: the frame pointer should be kept.
+
+ On a function, the attribute applies to that function. On the module,
+ it becomes the `frame-pointer` module flag, which LLVM applies to the
+ functions it creates after CIR is lowered, such as sanitizer
+ constructors. Without the attribute, the kind is `none`.
Example:
>From 9116751c78658a71b788c5491702d964931178bc Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 13 Oct 2026 10:45:26 -0500
Subject: [PATCH 4/5] Update clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Co-authored-by: Henrich Lauko <xlauko at mail.muni.cz>
---
clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 83fb881e76b652..526b108b3438fb 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1458,7 +1458,7 @@ def CIR_UnwindTableKindAttr : CIR_EnumAttr<CIR_UnwindTableKind, "uwtable"> {
// FramePointerKind
//===----------------------------------------------------------------------===//
def CIR_FramePointerKind : CIR_I32Enum<"FramePointerKind",
- "Frame Pointer Kind", [
+ "frame pointer kind", [
I32EnumCase<"None", 0, "none">,
I32EnumCase<"NonLeaf", 1, "non_leaf">,
I32EnumCase<"All", 2, "all">,
>From 54143aeb03f35a02d25847a260f01057c4da79d8 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 13 Oct 2026 10:46:00 -0500
Subject: [PATCH 5/5] Update clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Co-authored-by: Henrich Lauko <xlauko at mail.muni.cz>
---
clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 526b108b3438fb..51c07103884f10 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1457,6 +1457,7 @@ def CIR_UnwindTableKindAttr : CIR_EnumAttr<CIR_UnwindTableKind, "uwtable"> {
//===----------------------------------------------------------------------===//
// FramePointerKind
//===----------------------------------------------------------------------===//
+
def CIR_FramePointerKind : CIR_I32Enum<"FramePointerKind",
"frame pointer kind", [
I32EnumCase<"None", 0, "none">,
More information about the cfe-commits
mailing list