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 <[email protected]>
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@+1 {{attribute 'frame_pointer' must be a 
#cir.frame_pointer attribute}}
+  cir.func @badFramePointerKind() attributes {frame_pointer = "all"} {
+    cir.return
+  }
+}
+
+// -----
+
+module {
+  // expected-error@+2 {{expected one of [none, non_leaf, all, reserved, 
non_leaf_no_reserve] for Frame Pointer Kind, got: bogus}}
+  // expected-error@+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@+1 {{expects 'cir.frame_pointer' to be a #cir.frame_pointer 
attribute}}
+module attributes {cir.frame_pointer = 2 : i32} {
+}
+
+// -----
+
+module {
+  // expected-error@+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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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">,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to