Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------------------

Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 14 additions & 0 deletions clang/test/CodeGen/builtin-stackaddress.c
Original file line number Diff line number Diff line change
@@ -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();
}
36 changes: 36 additions & 0 deletions clang/test/CodeGenCXX/builtin-stackaddress.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
16 changes: 16 additions & 0 deletions clang/test/Sema/builtin-stackaddress-target-support.c
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions clang/test/Sema/builtin-stackaddress.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// expected-error@+1 {{too many arguments to function call, expected 0, have 1}}
return __builtin_stack_address(0);
// expected-error@+1 {{too many arguments to function call, expected 0, have 1}}
return __builtin_stack_address(0);

Other test cases I would like to see are:

 // As a global variable where there is no stack address to get.
// This should be diagnosed as an error?
void *ptr = __builtin_stack_address();

inline void *what() {
  return __builtin_stack_address();
}

void func() {
  void *ptr = what(); // Is this getting the stack address of? The inline function or is it getting the stack address of the caller? Or depends on optimization level?
}

Should we also have tests for what happens with naked functions or other kind of odd situations where the stack may be different from the normal case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// As a global variable where there is no stack address to get.
// This should be diagnosed as an error?
void *ptr = __builtin_stack_address();

It looks like GCC doesn't diagnose this as an error (https://godbolt.org/z/x35e114de) and returns the stack address of the initialization function. I'm not sure if it's intended or not.
Should we still diagnose this as an error?

Should we also have tests for what happens with naked functions or other kind of odd situations where the stack may be different from the normal case?

Noted, will add them, thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// As a global variable where there is no stack address to get.
// This should be diagnosed as an error?
void *ptr = __builtin_stack_address();

It looks like GCC doesn't diagnose this as an error (https://godbolt.org/z/x35e114de) and returns the stack address of the initialization function. I'm not sure if it's intended or not. Should we still diagnose this as an error?

My intuition is that it's kinder to the user to reject trying to get a stack address when there's no stack involved, but I don't know that it's a strongly held opinion. CC @nikic @efriedma-quic for more opinions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't evaluate this at compile-time, so we know this is running at runtime. And at runtime, we have a stack: the stack of __cxx_global_var_init(). So the semantics here seem fine, if maybe a little weird at first glance. And trying to restrict the set of builtins you can use from runtime init leads to weird questions, like what happens if you have a function static inline void* stackaddr() { return __builtin_stack_address(); }.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes a lot more sense to me, thank you! Then I'm happy with the current behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have tests for what happens with naked functions or other kind of odd situations where the stack may be different from the normal case?

I might need a little bit of help with this, it seems like you can't have non-ASM code inside an __attribute__((naked)) function, so you can't call __builtin_stack_address() inside one.

I'm also not very sure about the odd situations where the stack may be different from the normal case. Are you referring to something similar to an interrupt handler (i.e. __attribute__((interrupt)) void isr(void*)?

Using something like __attribute__((interrupt)) simply translates to an additional function attribute in the generated IR (e.g. x86_intrcc). Is it more appropriate to test those scenarios in llvm/test/CodeGen/${Target}/ instead?

}
30 changes: 30 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14295,6 +14295,36 @@ Semantics:

Note this intrinsic is only verified on AArch64 and ARM.

'``llvm.stackaddress``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

::

declare ptr @llvm.stackaddress()

Overview:
"""""""""

The '``llvm.stackaddress``' instrinsic 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.

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 differ from the physical value. '``llvm.stackaddress``' handles this
discrepancy and returns the correct boundary address.

**Note**: This intrinsic is currently only implemented for x86 and x86-64.

'``llvm.frameaddress``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
5 changes: 5 additions & 0 deletions llvm/include/llvm/CodeGen/ISDOpcodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@ void SelectionDAGLegalize::LegalizeOp(SDNode *Node) {
case ISD::ADJUST_TRAMPOLINE:
case ISD::FRAMEADDR:
case ISD::RETURNADDR:
case ISD::STACKADDRESS:
case ISD::ADDROFRETURNADDR:
case ISD::SPONENTRY:
// These operations lie about being legal: when they claim to be legal,
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::STACKADDRESS, sdl,
TLI.getValueType(DAG.getDataLayout(), I.getType())));
return;
}
case Intrinsic::read_volatile_register:
case Intrinsic::read_register: {
Value *Reg = I.getArgOperand(0);
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
9 changes: 9 additions & 0 deletions llvm/lib/Target/X86/X86ISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28274,6 +28274,14 @@ SDValue X86TargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const {
return FrameAddr;
}

SDValue X86TargetLowering::LowerSTACKADDRESS(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,
Expand Down Expand Up @@ -33637,6 +33645,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::STACKADDRESS: return LowerSTACKADDRESS(Op, DAG);
case ISD::FRAME_TO_ARGS_OFFSET:
return LowerFRAME_TO_ARGS_OFFSET(Op, DAG);
case ISD::DYNAMIC_STACKALLOC: return LowerDYNAMIC_STACKALLOC(Op, DAG);
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/X86/X86ISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 LowerSTACKADDRESS(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;
Expand Down