[clang] [llvm] [Clang] Add `__builtin_stack_address` (PR #148281)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 11 12:35:31 PDT 2025


https://github.com/moorabbit created https://github.com/llvm/llvm-project/pull/148281

Add support for `__builtin_stack_address` builtin. The semantics match those of GCC's builtin with the same name.

`__builtin_stack_address` returns the starting address of the stack region that may be used by called functions. It may or may not include the space used for on-stack arguments passed to a callee (See [GCC Bug/121013](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121013)).
 
This PR only adds support for the following architectures: x86 - x86_64.  Support for other architectures can be added in future patches.

Fixes #82632

>From 029d9fce6cdb75ea4819a107c7ed9f074bb36678 Mon Sep 17 00:00:00 2001
From: moorabbit <215698969+moorabbit at users.noreply.github.com>
Date: Mon, 7 Jul 2025 09:25:46 -0400
Subject: [PATCH] [Clang] Add `__builtin_stack_address`

Add support for `__builtin_stack_address` builtin. The semantics match those of
GCC's builtin with the same name.

`__builtin_stack_address` returns the starting address of the stack region that
may be used by called functions. This PR only adds support for the following
architectures: x86 - x86_64.  Support for other architectures can be added in
future patches.

Fixes #82632
---
 clang/docs/LanguageExtensions.rst             | 33 +++++++++++++++++
 clang/docs/ReleaseNotes.rst                   |  2 ++
 clang/include/clang/Basic/Builtins.td         |  6 ++++
 clang/lib/CodeGen/CGBuiltin.cpp               |  4 +++
 clang/lib/Sema/SemaChecking.cpp               |  9 +++++
 clang/test/CodeGen/builtin-stackaddress.c     | 14 ++++++++
 .../test/CodeGenCXX/builtin-stackaddress.cpp  | 36 +++++++++++++++++++
 .../builtin-stackaddress-target-support.c     | 16 +++++++++
 clang/test/Sema/builtin-stackaddress.c        |  5 +++
 llvm/include/llvm/CodeGen/ISDOpcodes.h        |  5 +++
 llvm/include/llvm/IR/Intrinsics.td            |  1 +
 llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp |  1 +
 .../SelectionDAG/SelectionDAGBuilder.cpp      |  6 ++++
 .../SelectionDAG/SelectionDAGDumper.cpp       |  1 +
 llvm/lib/Target/X86/X86ISelLowering.cpp       |  8 +++++
 llvm/lib/Target/X86/X86ISelLowering.h         |  1 +
 16 files changed, 148 insertions(+)
 create mode 100644 clang/test/CodeGen/builtin-stackaddress.c
 create mode 100644 clang/test/CodeGenCXX/builtin-stackaddress.cpp
 create mode 100644 clang/test/Sema/builtin-stackaddress-target-support.c

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index a42a546555716..5b78ae42559be 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -4189,6 +4189,39 @@ assignment can happen automatically.
 to a variable, have its address taken, or passed into or returned from a
 function, because doing so violates bounds safety conventions.
 
+.. _builtin_stack_address-doc:
+
+``__builtin_stack_address``
+---------------------------
+
+``__builtin_stack_address`` returns the address that separates the current
+function's (i.e. the one calling the builtin) stack space and the region of the
+stack that may be modified by called functions. The semantics match those of GCC's builtin of the same name.
+
+**Note:** Support for this builtin is currently limited to the following architectures: x86_64, x86.
+
+**Syntax**:
+
+.. code-block:: c++
+
+  void *__builtin_stack_address()
+
+**Example**:
+
+.. code-block:: c++
+
+  void *sp = __builtin_stack_address();
+
+**Description**:
+
+The address returned by ``__builtin_stack_address`` identifies the starting
+address of the stack region that may be used by called functions.
+
+On some architectures (e.g. x86), it's sufficient to return the value in the stack pointer register
+directly. On others (e.g. SPARCv9), adjustments are required to the value of the stack pointer
+register. ``__builtin_stack_address`` performs the necessary adjustments and returns the correct
+boundary address.
+
 Multiprecision Arithmetic Builtins
 ----------------------------------
 
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 57a94242c9e61..ccf83eb2f16fa 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -172,6 +172,8 @@ Resolutions to C++ Defect Reports
 C Language Changes
 ------------------
 
+- Clang now supports the :ref:`__builtin_stack_address <builtin_stack_address-doc>` () builtin.
+  The semantics match those of GCC's builtin with the same name.
 - Clang now allows an ``inline`` specifier on a typedef declaration of a
   function type in Microsoft compatibility mode. #GH124869
 - Clang now allows ``restrict`` qualifier for array types with pointer elements (#GH92847).
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 5ebb82180521d..f2012c813c9a7 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -917,6 +917,12 @@ def FrameAddress : Builtin {
   let Prototype = "void*(_Constant unsigned int)";
 }
 
+def StackAddress : Builtin {
+  let Spellings = ["__builtin_stack_address"];
+  let Attributes = [NoThrow];
+  let Prototype = "void*()";
+}
+
 def ClearCache : Builtin {
   let Spellings = ["__builtin___clear_cache"];
   let Attributes = [NoThrow];
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 48c91eb4a5b4f..641bbede4bae7 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4673,6 +4673,10 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     Function *F = CGM.getIntrinsic(Intrinsic::frameaddress, AllocaInt8PtrTy);
     return RValue::get(Builder.CreateCall(F, Depth));
   }
+  case Builtin::BI__builtin_stack_address: {
+    return RValue::get(Builder.CreateCall(
+        CGM.getIntrinsic(Intrinsic::stackaddress, AllocaInt8PtrTy)));
+  }
   case Builtin::BI__builtin_extract_return_addr: {
     Value *Address = EmitScalarExpr(E->getArg(0));
     Value *Result = getTargetHooks().decodeReturnAddress(*this, Address);
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index dd5b710d7e1d4..ca9371d6d2179 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2958,6 +2958,15 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
     break;
   }
 
+  case Builtin::BI__builtin_stack_address: {
+    if (CheckBuiltinTargetInSupported(
+            *this, TheCall,
+            /*SupportedArchs=*/{llvm::Triple::x86_64, llvm::Triple::x86})) {
+      return ExprError();
+    }
+    break;
+  }
+
   case Builtin::BI__builtin_nondeterministic_value: {
     if (BuiltinNonDeterministicValue(TheCall))
       return ExprError();
diff --git a/clang/test/CodeGen/builtin-stackaddress.c b/clang/test/CodeGen/builtin-stackaddress.c
new file mode 100644
index 0000000000000..a6b44b227947d
--- /dev/null
+++ b/clang/test/CodeGen/builtin-stackaddress.c
@@ -0,0 +1,14 @@
+// RUN: %clang -target x86_64 -S -emit-llvm %s -o - | FileCheck %s --check-prefix=llvm
+// RUN: %clang -target x86_64 -S %s -o - | FileCheck %s --check-prefix=x64
+
+extern void f(int, int, int, long, long, long, long, long, long, long, long);
+
+// llvm-LABEL: define {{[^@]+}} @a()
+// llvm:       call   {{[^@]+}} @llvm.stackaddress.p0()
+//
+// x64-LABEL: a:
+// x64:       movq  %rsp, %rax
+void *a() {
+  f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
+  return __builtin_stack_address();
+}
diff --git a/clang/test/CodeGenCXX/builtin-stackaddress.cpp b/clang/test/CodeGenCXX/builtin-stackaddress.cpp
new file mode 100644
index 0000000000000..24a949e83d9e0
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-stackaddress.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang -target x86_64 -S -emit-llvm %s -o - | llvm-cxxfilt | FileCheck %s --check-prefix=llvm
+// RUN: %clang -target x86_64 -S %s -o - | llvm-cxxfilt | FileCheck %s --check-prefix=x64
+
+extern void f(int, int, int, long, long, long, long, long, long, long, long);
+
+struct S {
+  void *a();
+};
+
+// llvm-LABEL: define {{[^@]+}} @S::a()
+// llvm:       call   {{[^@]+}} @llvm.stackaddress.p0()
+//
+// x64-LABEL: S::a():
+// x64:       movq  %rsp, %rax
+void *S::a() {
+  void *p = __builtin_stack_address();
+  f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
+  return p;
+}
+
+// llvm-LABEL: define {{[^@]+}} @two()
+// llvm:       call   {{[^@]+}} @"two()::$_0::operator()() const"
+//
+// llvm-LABEL: define {{[^@]+}} @"two()::$_0::operator()() const"
+// llvm:       call   {{[^@]+}} @llvm.stackaddress.p0()
+//
+// x64-LABEL: two()::$_0::operator()() const:
+// x64:       movq  %rsp, %rax
+void *two() {
+  auto l = []() {
+    void *p = __builtin_stack_address();
+    f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
+    return p;
+  };
+  return l();
+}
diff --git a/clang/test/Sema/builtin-stackaddress-target-support.c b/clang/test/Sema/builtin-stackaddress-target-support.c
new file mode 100644
index 0000000000000..aab077ea558f8
--- /dev/null
+++ b/clang/test/Sema/builtin-stackaddress-target-support.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -verify %s -triple x86_64-unknown-unknown -DTEST_x64
+// RUN: %clang_cc1 -verify %s -triple i386-unknown-unknown -DTEST_x86
+// RUN: %clang_cc1 -verify %s -triple riscv32-unknown-unknown -DTEST_riscv32
+// RUN: %clang_cc1 -verify %s -triple riscv64-unknown-unknown -DTEST_riscv64
+// RUN: %clang_cc1 -verify %s -triple aarch64-unknown-unknown -DTEST_aarch64
+
+#if defined(TEST_x64) || defined(TEST_x86)
+// expected-no-diagnostics
+void *a() {
+return __builtin_stack_address();
+}
+#else
+void *a() {
+return __builtin_stack_address(); // expected-error {{builtin is not supported on this target}}
+}
+#endif
diff --git a/clang/test/Sema/builtin-stackaddress.c b/clang/test/Sema/builtin-stackaddress.c
index ecdc64d899af5..03a0f5ef16714 100644
--- a/clang/test/Sema/builtin-stackaddress.c
+++ b/clang/test/Sema/builtin-stackaddress.c
@@ -36,3 +36,8 @@ void* h(unsigned x) {
 // expected-error at +1 {{argument value 1048575 is outside the valid range [0, 65535]}}
 return __builtin_frame_address(0xFFFFF);
 }
+
+void *i() {
+// expected-error at +1 {{too many arguments to function call, expected 0, have 1}}
+return __builtin_stack_address(0);
+}
diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index 465e4a0a9d0d8..916f277846a3f 100644
--- a/llvm/include/llvm/CodeGen/ISDOpcodes.h
+++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h
@@ -121,6 +121,11 @@ enum NodeType {
   /// function calling this intrinsic.
   SPONENTRY,
 
+  /// STACKADDR - Represents the llvm.stackaddr intrinsic. Takes no argument
+  /// and returns the starting address of the stack region that may be used
+  /// by called functions.
+  STACKADDR,
+
   /// LOCAL_RECOVER - Represents the llvm.localrecover intrinsic.
   /// Materializes the offset from the local object pointer of another
   /// function to a particular local object passed to llvm.localescape. The
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index bd6f94ac1286c..42f73e67e5896 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -853,6 +853,7 @@ def int_addressofreturnaddress : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], [In
 def int_frameaddress : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_i32_ty],
                                  [IntrNoMem, ImmArg<ArgIndex<0>>]>;
 def int_sponentry  : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], [IntrNoMem]>;
+def int_stackaddress : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], []>;
 def int_read_register  : DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_metadata_ty],
                                    [IntrReadMem], "llvm.read_register">;
 def int_write_register : Intrinsic<[], [llvm_metadata_ty, llvm_anyint_ty],
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
index 528136a55f14a..8ee85211da7bf 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
@@ -1120,6 +1120,7 @@ void SelectionDAGLegalize::LegalizeOp(SDNode *Node) {
   case ISD::ADJUST_TRAMPOLINE:
   case ISD::FRAMEADDR:
   case ISD::RETURNADDR:
+  case ISD::STACKADDR:
   case ISD::ADDROFRETURNADDR:
   case ISD::SPONENTRY:
     // These operations lie about being legal: when they claim to be legal,
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index ecd1ff87e7fbc..82548fb87abc5 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -6522,6 +6522,12 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
                              TLI.getFrameIndexTy(DAG.getDataLayout()),
                              getValue(I.getArgOperand(0))));
     return;
+  case Intrinsic::stackaddress: {
+    setValue(&I,
+             DAG.getNode(ISD::STACKADDR, sdl,
+                         TLI.getValueType(DAG.getDataLayout(), I.getType())));
+    return;
+  }
   case Intrinsic::read_volatile_register:
   case Intrinsic::read_register: {
     Value *Reg = I.getArgOperand(0);
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
index 7fc15581c17e4..d29f50319694c 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
@@ -148,6 +148,7 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const {
   case ISD::ADDROFRETURNADDR:           return "ADDROFRETURNADDR";
   case ISD::FRAMEADDR:                  return "FRAMEADDR";
   case ISD::SPONENTRY:                  return "SPONENTRY";
+  case ISD::STACKADDR:                  return "STACKADDR";
   case ISD::LOCAL_RECOVER:              return "LOCAL_RECOVER";
   case ISD::READ_REGISTER:              return "READ_REGISTER";
   case ISD::WRITE_REGISTER:             return "WRITE_REGISTER";
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 347ba1262b66b..7d3938154ecbc 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -28274,6 +28274,13 @@ SDValue X86TargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const {
   return FrameAddr;
 }
 
+SDValue X86TargetLowering::LowerSTACKADDR(SDValue Op, SelectionDAG &DAG) const {
+  SDLoc dl(Op);
+  return DAG.getCopyFromReg(DAG.getEntryNode(), dl,
+                            Subtarget.getRegisterInfo()->getStackRegister(),
+                            Op->getValueType(0));
+}
+
 // FIXME? Maybe this could be a TableGen attribute on some registers and
 // this table could be generated automatically from RegInfo.
 Register X86TargetLowering::getRegisterByName(const char* RegName, LLT VT,
@@ -33637,6 +33644,7 @@ SDValue X86TargetLowering::LowerOperation(SDValue Op, SelectionDAG &DAG) const {
   case ISD::RETURNADDR:         return LowerRETURNADDR(Op, DAG);
   case ISD::ADDROFRETURNADDR:   return LowerADDROFRETURNADDR(Op, DAG);
   case ISD::FRAMEADDR:          return LowerFRAMEADDR(Op, DAG);
+  case ISD::STACKADDR:          return LowerSTACKADDR(Op, DAG);
   case ISD::FRAME_TO_ARGS_OFFSET:
                                 return LowerFRAME_TO_ARGS_OFFSET(Op, DAG);
   case ISD::DYNAMIC_STACKALLOC: return LowerDYNAMIC_STACKALLOC(Op, DAG);
diff --git a/llvm/lib/Target/X86/X86ISelLowering.h b/llvm/lib/Target/X86/X86ISelLowering.h
index 5cb6b3e493a32..f7856cc4f0fd7 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.h
+++ b/llvm/lib/Target/X86/X86ISelLowering.h
@@ -1771,6 +1771,7 @@ namespace llvm {
     SDValue LowerRETURNADDR(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerADDROFRETURNADDR(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const;
+    SDValue LowerSTACKADDR(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerFRAME_TO_ARGS_OFFSET(SDValue Op, SelectionDAG &DAG) const;
     SDValue LowerEH_RETURN(SDValue Op, SelectionDAG &DAG) const;
     SDValue lowerEH_SJLJ_SETJMP(SDValue Op, SelectionDAG &DAG) const;



More information about the llvm-commits mailing list