[llvm] a5fa246 - [Clang] Add `__builtin_stack_address` (#148281)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jan 12 01:02:02 PST 2026
Author: moorabbit
Date: 2026-01-12T10:01:57+01:00
New Revision: a5fa2464356ced456e93ac1033e7460355d4eeaf
URL: https://github.com/llvm/llvm-project/commit/a5fa2464356ced456e93ac1033e7460355d4eeaf
DIFF: https://github.com/llvm/llvm-project/commit/a5fa2464356ced456e93ac1033e7460355d4eeaf.diff
LOG: [Clang] Add `__builtin_stack_address` (#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)).
Fixes #82632.
Added:
clang/test/CodeGen/builtin-stackaddress.c
clang/test/CodeGenCXX/builtin-stackaddress.cpp
llvm/test/CodeGen/AArch64/stackaddress.ll
llvm/test/CodeGen/ARM/stackaddress.ll
llvm/test/CodeGen/NVPTX/stackaddress.ll
llvm/test/CodeGen/SPARC/stackaddress.ll
llvm/test/CodeGen/X86/stackaddress.ll
Modified:
clang/docs/LanguageExtensions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/Builtins.td
clang/lib/CodeGen/CGBuiltin.cpp
clang/test/Sema/builtin-stackaddress.c
llvm/docs/LangRef.rst
llvm/include/llvm/CodeGen/ISDOpcodes.h
llvm/include/llvm/IR/Intrinsics.td
llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
llvm/lib/CodeGen/TargetLoweringBase.cpp
llvm/lib/Target/Sparc/SparcISelLowering.cpp
Removed:
################################################################################
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index d7963243564a1..228f7bf89ddde 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -4378,6 +4378,39 @@ implemented in a way where the counter 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.
+
+**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 a695d9f0ad4b7..c29fcef4002d2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -231,6 +231,9 @@ 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.
- Implemented the ``defer`` draft Technical Specification
(`WG14 N3734 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3734.pdf>`_); it is enabled in C mode by
passing ``-fdefer-ts``. Note, the details of this feature are subject to change given that the Technical
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 0ab50b06e11cf..7a639c858d21f 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -923,6 +923,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 f650fb3700669..dd8ba79edc219 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4750,6 +4750,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/test/CodeGen/builtin-stackaddress.c b/clang/test/CodeGen/builtin-stackaddress.c
new file mode 100644
index 0000000000000..6acc9c61d9d9b
--- /dev/null
+++ b/clang/test/CodeGen/builtin-stackaddress.c
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s
+
+// CHECK-LABEL: define {{[^@]+}} @a()
+// CHECK: call {{[^@]+}} @llvm.stackaddress.p0()
+void *a() {
+ 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..b1b8997f26cbb
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-stackaddress.cpp
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - | llvm-cxxfilt | FileCheck %s
+
+struct S {
+ void *a();
+};
+
+// CHECK-LABEL: @S::a()
+// CHECK: call ptr @llvm.stackaddress.p0()
+void *S::a() {
+ return __builtin_stack_address();
+}
+
+// CHECK-LABEL: define {{[^@]+}} @two()
+// CHECK: call {{[^@]+}} @"two()::$_0::operator()() const"
+//
+// CHECK-LABEL: define {{[^@]+}} @"two()::$_0::operator()() const"
+// CHECK: [[PTR:%.*]] = call ptr @llvm.stackaddress.p0()
+// CHECK: ret ptr [[PTR]]
+void *two() {
+ auto l = []() { return __builtin_stack_address(); };
+ return l();
+}
diff --git a/clang/test/Sema/builtin-stackaddress.c b/clang/test/Sema/builtin-stackaddress.c
index ecdc64d899af5..f95751daf8a01 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/docs/LangRef.rst b/llvm/docs/LangRef.rst
index c17dd3f707be7..be752b42ea412 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -14672,6 +14672,37 @@ Semantics:
Note this intrinsic is only verified on AArch64 and ARM.
+'``llvm.stackaddress``' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Syntax:
+"""""""
+
+::
+
+ declare ptr @llvm.stackaddress.p0()
+
+Overview:
+"""""""""
+
+The '``llvm.stackaddress``' intrinsic returns the starting address of the
+stack region that may be used by called functions.
+
+Semantics:
+""""""""""
+
+This intrinsic returns the *logical* value of the stack pointer register, that
+is, the address separating the stack space of the current function from the
+stack space that may be modified by called functions. It corresponds to the
+address returned by '``llvm.sponentry``', offset by the size of the current
+function's stack frame.
+
+On certain targets (e.g. x86), the logical and actual (or physical) values of
+the stack pointer register are the same. However, on other architectures (e.g.
+SPARCv9), the logical value of the stack pointer register may
diff er from the
+physical value. '``llvm.stackaddress``' handles this discrepancy and returns
+the correct boundary address.
+
'``llvm.frameaddress``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index ea7b21b6f6448..e4d67201c4b8e 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,
+ /// STACKADDRESS - Represents the llvm.stackaddress intrinsic. Takes no
+ /// argument and returns the starting address of the stack region that may be
+ /// used by called functions.
+ STACKADDRESS,
+
/// 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 c56b0185b4f1e..1c63b4eb90578 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -891,6 +891,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 ffc1a574777c8..abe62440376ae 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
@@ -18,6 +18,7 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/CodeGen/ISDOpcodes.h"
@@ -1010,6 +1011,7 @@ void SelectionDAGLegalize::LegalizeOp(SDNode *Node) {
case ISD::INTRINSIC_WO_CHAIN:
case ISD::INTRINSIC_VOID:
case ISD::STACKSAVE:
+ case ISD::STACKADDRESS:
Action = TLI.getOperationAction(Node->getOpcode(), MVT::Other);
break;
case ISD::GET_DYNAMIC_AREA_OFFSET:
@@ -3783,6 +3785,7 @@ bool SelectionDAGLegalize::ExpandNode(SDNode *Node) {
Results.push_back(Tmp1);
break;
}
+ case ISD::STACKADDRESS:
case ISD::STACKSAVE:
// Expand to CopyFromReg if the target set
// StackPointerRegisterToSaveRestore.
@@ -3793,6 +3796,13 @@ bool SelectionDAGLegalize::ExpandNode(SDNode *Node) {
} else {
Results.push_back(DAG.getUNDEF(Node->getValueType(0)));
Results.push_back(Node->getOperand(0));
+
+ StringRef IntrinsicName = Node->getOpcode() == ISD::STACKADDRESS
+ ? "llvm.stackaddress"
+ : "llvm.stacksave";
+ DAG.getContext()->diagnose(DiagnosticInfoLegalizationFailure(
+ Twine(IntrinsicName) + " is not supported on this target.",
+ DAG.getMachineFunction().getFunction(), dl.getDebugLoc()));
}
break;
case ISD::STACKRESTORE:
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index bff4799963fc2..b501c4bad8e8e 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -7439,10 +7439,13 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
setValue(&I, DAG.getNode(ISD::UCMP, sdl, DestVT, Op1, Op2));
break;
}
+ case Intrinsic::stackaddress:
case Intrinsic::stacksave: {
+ unsigned SDOpcode = Intrinsic == Intrinsic::stackaddress ? ISD::STACKADDRESS
+ : ISD::STACKSAVE;
SDValue Op = getRoot();
EVT VT = TLI.getValueType(DAG.getDataLayout(), I.getType());
- Res = DAG.getNode(ISD::STACKSAVE, sdl, DAG.getVTList(VT, MVT::Other), Op);
+ Res = DAG.getNode(SDOpcode, sdl, DAG.getVTList(VT, MVT::Other), Op);
setValue(&I, Res);
DAG.setRoot(Res.getValue(1));
return;
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
index f68a1b99314bb..965e4f61659db 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::STACKADDRESS: return "STACKADDRESS";
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/CodeGen/TargetLoweringBase.cpp b/llvm/lib/CodeGen/TargetLoweringBase.cpp
index c6aaea412a760..cf3f87df2305f 100644
--- a/llvm/lib/CodeGen/TargetLoweringBase.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringBase.cpp
@@ -1256,6 +1256,10 @@ void TargetLoweringBase::initActions() {
// This one by default will call __clear_cache unless the target
// wants something
diff erent.
setOperationAction(ISD::CLEAR_CACHE, MVT::Other, LibCall);
+
+ // By default, STACKADDRESS nodes are expanded like STACKSAVE nodes.
+ // On SPARC targets, custom lowering is required.
+ setOperationAction(ISD::STACKADDRESS, MVT::Other, Expand);
}
MVT TargetLoweringBase::getScalarShiftAmountTy(const DataLayout &DL,
diff --git a/llvm/lib/Target/Sparc/SparcISelLowering.cpp b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
index 8abbea147571e..05a76935b84ba 100644
--- a/llvm/lib/Target/Sparc/SparcISelLowering.cpp
+++ b/llvm/lib/Target/Sparc/SparcISelLowering.cpp
@@ -1922,6 +1922,7 @@ SparcTargetLowering::SparcTargetLowering(const TargetMachine &TM,
setOperationAction(ISD::STACKSAVE , MVT::Other, Expand);
setOperationAction(ISD::STACKRESTORE , MVT::Other, Expand);
setOperationAction(ISD::DYNAMIC_STACKALLOC, MVT::i32 , Custom);
+ setOperationAction(ISD::STACKADDRESS, MVT::Other, Custom);
setStackPointerRegisterToSaveRestore(SP::O6);
@@ -2754,6 +2755,28 @@ static SDValue LowerVAARG(SDValue Op, SelectionDAG &DAG) {
Align(std::min(PtrVT.getFixedSizeInBits(), VT.getFixedSizeInBits()) / 8));
}
+static SDValue LowerSTACKADDRESS(SDValue Op, SelectionDAG &DAG,
+ const SparcSubtarget &Subtarget) {
+ SDValue Chain = Op.getOperand(0);
+ EVT VT = Op->getValueType(0);
+ SDLoc DL(Op);
+
+ MCRegister SPReg = SP::O6;
+ SDValue SP = DAG.getCopyFromReg(Chain, DL, SPReg, VT);
+
+ // Unbias the stack pointer register.
+ unsigned OffsetToStackStart = Subtarget.getStackPointerBias();
+ // Move past the register save area: 8 in registers + 8 local registers.
+ OffsetToStackStart += 16 * (Subtarget.is64Bit() ? 8 : 4);
+ // Move past the struct return address slot (4 bytes) on SPARC 32-bit.
+ if (!Subtarget.is64Bit())
+ OffsetToStackStart += 4;
+
+ SDValue StackAddr = DAG.getNode(ISD::ADD, DL, VT, SP,
+ DAG.getConstant(OffsetToStackStart, DL, VT));
+ return DAG.getMergeValues({StackAddr, Chain}, DL);
+}
+
static SDValue LowerDYNAMIC_STACKALLOC(SDValue Op, SelectionDAG &DAG,
const SparcSubtarget *Subtarget) {
SDValue Chain = Op.getOperand(0);
@@ -3151,6 +3174,8 @@ LowerOperation(SDValue Op, SelectionDAG &DAG) const {
case ISD::VAARG: return LowerVAARG(Op, DAG);
case ISD::DYNAMIC_STACKALLOC: return LowerDYNAMIC_STACKALLOC(Op, DAG,
Subtarget);
+ case ISD::STACKADDRESS:
+ return LowerSTACKADDRESS(Op, DAG, *Subtarget);
case ISD::LOAD: return LowerLOAD(Op, DAG);
case ISD::STORE: return LowerSTORE(Op, DAG);
diff --git a/llvm/test/CodeGen/AArch64/stackaddress.ll b/llvm/test/CodeGen/AArch64/stackaddress.ll
new file mode 100644
index 0000000000000..5cc180d913e0e
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/stackaddress.ll
@@ -0,0 +1,10 @@
+; RUN: llc < %s -mtriple=aarch64 | FileCheck %s
+
+declare ptr @llvm.stackaddress.p0()
+
+define ptr @test() {
+; CHECK: mov x0, sp
+; CHECK: ret
+ %sp = call ptr @llvm.stackaddress.p0()
+ ret ptr %sp
+}
diff --git a/llvm/test/CodeGen/ARM/stackaddress.ll b/llvm/test/CodeGen/ARM/stackaddress.ll
new file mode 100644
index 0000000000000..56abff06c7bda
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/stackaddress.ll
@@ -0,0 +1,10 @@
+; RUN: llc < %s -mtriple=armv7 | FileCheck %s
+
+declare ptr @llvm.stackaddress.p0()
+
+define ptr @test() {
+; CHECK: mov r0, sp
+; CHECK: bx lr
+ %sp = call ptr @llvm.stackaddress.p0()
+ ret ptr %sp
+}
diff --git a/llvm/test/CodeGen/NVPTX/stackaddress.ll b/llvm/test/CodeGen/NVPTX/stackaddress.ll
new file mode 100644
index 0000000000000..516b0dacbf2ed
--- /dev/null
+++ b/llvm/test/CodeGen/NVPTX/stackaddress.ll
@@ -0,0 +1,9 @@
+; RUN: not llc < %s -mtriple nvptx 2>&1 | FileCheck %s
+
+declare ptr @llvm.stackaddress.p0()
+
+define ptr @test() {
+; CHECK: error: <unknown>:0:0: llvm.stackaddress is not supported on this target.
+ %sp = call ptr @llvm.stackaddress.p0()
+ ret ptr %sp
+}
diff --git a/llvm/test/CodeGen/SPARC/stackaddress.ll b/llvm/test/CodeGen/SPARC/stackaddress.ll
new file mode 100644
index 0000000000000..fce5f1cba8fb1
--- /dev/null
+++ b/llvm/test/CodeGen/SPARC/stackaddress.ll
@@ -0,0 +1,16 @@
+; RUN: llc < %s -mtriple=sparc | FileCheck --check-prefix=sparc32 %s
+; RUN: llc < %s -mtriple=sparcv9 | FileCheck --check-prefix=sparc64 %s
+
+declare ptr @llvm.stackaddress.p0()
+
+define ptr @test() {
+; sparc32: save %sp, -96, %sp
+; sparc32: ret
+; sparc32: restore %sp, 68, %o0
+;
+; sparc64: save %sp, -128, %sp
+; sparc64: ret
+; sparc64: restore %sp, 2175, %o0
+ %sp = call ptr @llvm.stackaddress.p0()
+ ret ptr %sp
+}
diff --git a/llvm/test/CodeGen/X86/stackaddress.ll b/llvm/test/CodeGen/X86/stackaddress.ll
new file mode 100644
index 0000000000000..6d73b2ec82c22
--- /dev/null
+++ b/llvm/test/CodeGen/X86/stackaddress.ll
@@ -0,0 +1,14 @@
+; RUN: llc < %s -mtriple=x86_64-linux-gnu -o - | FileCheck --check-prefix=x86_64 %s
+; RUN: llc < %s -mtriple=i386-linux-gnu -o - | FileCheck --check-prefix=i386 %s
+
+declare ptr @llvm.stackaddress.p0()
+
+define ptr @test() {
+; x86_64: movq %rsp, %rax
+; x86_64: retq
+
+; i386: movl %esp, %eax
+; i386: retl
+ %sp = call ptr @llvm.stackaddress.p0()
+ ret ptr %sp
+}
More information about the llvm-commits
mailing list