From 029d9fce6cdb75ea4819a107c7ed9f074bb36678 Mon Sep 17 00:00:00 2001 From: moorabbit <215698969+moorabbit@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. + 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@+1 {{argument value 1048575 is outside the valid range [0, 65535]}} return __builtin_frame_address(0xFFFFF); } + +void *i() { +// expected-error@+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>]>; 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;