[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