Skip to content

[clang] Add __builtin_start_object_lifetime builtin. #82776

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
48 changes: 48 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2526,6 +2526,54 @@ implemented directly in terms of :ref:`extended vector support
<langext-vectors>` instead of builtins, in order to reduce the number of
builtins that we need to implement.

``__builtin_start_object_lifetime``
-----------------------------------

The builtin is used to instruct compiler to explicitly create an object in-place
and start the object lifetime without running any initialisation code.

**Syntax**:

.. code-block:: c++

T* __builtin_start_object_lifetime(T* p)


**Example of Use***:

.. code-block:: c++

struct Foo {};

// [buffer, buffer+sizeof(Foo)) is a memory region whose bytes represent a
// valid object representation of type Foo.
Foo* make_foo(char* buffer) {
return __builtin_start_object_lifetime(reinterpret_cast<Foo*>(buffer));
}

**Description**:

This builtin creates an object at the given memory location and start
the lifetime of the object without running any constructor code. It returns a
pointer to the same memory that the parameter `p` points to, and the returned
result can be legitimately used to access the object `T`.

It can be used to implement C++23's `std::start_lifetime_as` API.
Unlike the `std::start_lifetime_as` which only works for implicit-lifetime
types. This builtin doens't have this restriction, it can apply to
non-implicit-lifetime types.

This builtin is a no-op barrier operation taken by the compiler to address object
value propagation analysis in an opaque manner appropriately, e.g. suppressing
certain optimizations.

This builtin cannot be called in a ``constexpr`` context.

NOTE: this builtin is considered experimental at this time. It is known that it
can cause TBAA miscompile issues when using with `-fstrict-aliasing` flag (which
is on by default). Until we fix all TBAA issues (which requires more LLVM IR
support), we suggest to use it with `-fno-strict-aliasing`.

``__builtin_alloca``
--------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ Non-comprehensive list of changes in this release
- ``__typeof_unqual__`` is available in all C modes as an extension, which behaves
like ``typeof_unqual`` from C23, similar to ``__typeof__`` and ``typeof``.

- Added ``__builtin_start_object_lifetime`` for creating object in-place and
starting object lifetime without running any initialisation code.

New Compiler Flags
------------------
- ``-fsanitize=implicit-bitfield-conversion`` checks implicit truncation and
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 @@ -926,6 +926,12 @@ def Launder : Builtin {
let Prototype = "void*(void*)";
}

def StartObjectLifetime : Builtin {
let Spellings = ["__builtin_start_object_lifetime"];
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit, I'd consider __builtin_start_lifetime both for brevity and to parallel std::start_lifetime_as.
I don't think it's really ambiguous what we're starting the lifetime of.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO if the intent of this intrinsic is to handle polymorphic classes (which is not covered by std::start_lifetime_as), its name should diverge from "plain" start_lifetime.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't have strong opinion on the name. Since this builtin doesn't strictly align with std::start_lifetime_as, I think it's better to have some divergence in the name.

let Attributes = [NoThrow, CustomTypeChecking];
let Prototype = "void*(void*)";
}

def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> {
let Spellings = ["__builtin_is_constant_evaluated"];
let Attributes = [NoThrow, Constexpr];
Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12025,9 +12025,9 @@ def warn_noderef_on_non_pointer_or_array : Warning<
def warn_noderef_to_dereferenceable_pointer : Warning<
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;

def err_builtin_launder_invalid_arg : Error<
def err_builtin_launder_or_start_object_lifetime_invalid_arg : Error<
"%select{non-pointer|function pointer|void pointer}0 argument to "
"'__builtin_launder' is not allowed">;
"'%select{__builtin_launder|__builtin_start_object_lifetime}1' is not allowed">;

def err_builtin_invalid_arg_type: Error <
"%ordinal0 argument must be "
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4521,10 +4521,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,

return RValue::get(nullptr);
}
case Builtin::BI__builtin_start_object_lifetime:
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there something to be commented here about why what we do here is sufficient?
It seems surprising to my very-untrained eye that start_lifetime is the same as launder, and we don't need any extra TBAA metadata or so...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a comment for it, please take a look.

// FIXME: we need some TBAA fences to prevent strict-aliasing miscompiles.
case Builtin::BI__builtin_launder: {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should the TypeRequiresBuiltinLaunder function be generalized, at least in name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think the name is fine, we're in the CodeGen, and the BuiltinLaunder in TypeRequiresBuiltinLaunder means the LLVM IR llvm.launder intrinsics.

const Expr *Arg = E->getArg(0);
QualType ArgTy = Arg->getType()->getPointeeType();
Value *Ptr = EmitScalarExpr(Arg);
// Arguments of __builtin_launder and __builtin_start_object_lifetime may
// need the LLVM IR launder.invariant.group intrinsic barrier to prevent
// alising-based optimizations (e.g. -fstrict-vtable-pointers).
if (TypeRequiresBuiltinLaunder(CGM, ArgTy))
Ptr = Builder.CreateLaunderInvariantGroup(Ptr);

Expand Down
18 changes: 14 additions & 4 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/AddressSpaces.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/IdentifierTable.h"
Expand Down Expand Up @@ -2155,7 +2156,13 @@ static ExprResult PointerAuthAuthAndResign(Sema &S, CallExpr *Call) {
return Call;
}

static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
// Semantic check for function arguments of __builtin_launder or
// __builtin_start_object_lifetime.
static ExprResult SemaBuiltinLaunderOrStartObjectLifetime(Sema &S,
CallExpr *TheCall,
unsigned BuiltinID) {
assert(BuiltinID == Builtin::BI__builtin_launder ||
BuiltinID == Builtin::BI__builtin_start_object_lifetime);
if (checkArgCount(S, TheCall, 1))
return ExprError();

Expand Down Expand Up @@ -2186,8 +2193,10 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
return std::optional<unsigned>{};
}();
if (DiagSelect) {
S.Diag(TheCall->getBeginLoc(), diag::err_builtin_launder_invalid_arg)
<< *DiagSelect << TheCall->getSourceRange();
S.Diag(TheCall->getBeginLoc(),
diag::err_builtin_launder_or_start_object_lifetime_invalid_arg)
<< *DiagSelect << (BuiltinID == Builtin::BI__builtin_launder ? 0 : 1)
<< TheCall->getSourceRange();
return ExprError();
}

Expand Down Expand Up @@ -2641,8 +2650,9 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
TheCall->setType(Context.IntTy);
break;
}
case Builtin::BI__builtin_start_object_lifetime:
case Builtin::BI__builtin_launder:
return BuiltinLaunder(*this, TheCall);
return SemaBuiltinLaunderOrStartObjectLifetime(*this, TheCall, BuiltinID);
case Builtin::BI__sync_fetch_and_add:
case Builtin::BI__sync_fetch_and_add_1:
case Builtin::BI__sync_fetch_and_add_2:
Expand Down
10 changes: 10 additions & 0 deletions clang/test/CodeGen/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ int main(void) {
P(signbit, (1.0));

R(launder, (&N));
R(start_object_lifetime, (&N));

return 0;
}
Expand Down Expand Up @@ -511,6 +512,15 @@ void test_builtin_launder(int *p) {
int *d = __builtin_launder(p);
}

/// It should be a NOP in C since there are no vtables.
// CHECK-LABEL: define{{.*}} void @test_builtin_start_object_lifetime
void test_builtin_start_object_lifetime(int *p) {
// CHECK: [[TMP:%.*]] = load ptr,
// CHECK-NOT: @llvm.launder
// CHECK: store ptr [[TMP]],
int *d = __builtin_start_object_lifetime(p);
}

// __warn_memset_zero_len should be NOP, see https://sourceware.org/bugzilla/show_bug.cgi?id=25399
// CHECK-LABEL: define{{.*}} void @test___warn_memset_zero_len
void test___warn_memset_zero_len(void) {
Expand Down
49 changes: 49 additions & 0 deletions clang/test/CodeGenCXX/builtin-start-object-life-time.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -fstrict-vtable-pointers -o - %s \
// RUN: | FileCheck --check-prefixes=CHECK,CHECK-STRICT %s
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s \
// RUN: | FileCheck --check-prefixes=CHECK,CHECK-NONSTRICT %s

struct TestVirtualFn {
virtual void foo();
};
// CHECK-LABEL: define{{.*}} void @test_dynamic_class
extern "C" void test_dynamic_class(TestVirtualFn *p) {
// CHECK: store ptr %p, ptr %p.addr
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr %p.addr

// CHECK-NONSTRICT-NEXT: store ptr [[TMP0]], ptr %d

// CHECK-STRICT-NEXT: [[TMP2:%.*]] = call ptr @llvm.launder.invariant.group.p0(ptr [[TMP0]])
// CHECK-STRICT-NEXT: store ptr [[TMP2]], ptr %d

// CHECK-NEXT: ret void
TestVirtualFn *d = __builtin_start_object_lifetime(p);
}

// CHECK-LABEL: define{{.*}} void @test_scalar_pointer
extern "C" void test_scalar_pointer(int *p) {
// CHECK: entry
// CHECK-NEXT: %p.addr = alloca ptr
// CHECK-NEXT: %d = alloca ptr
// CHECK-NEXT: store ptr %p, ptr %p.addr, align 8
// CHECK-NEXT: [[TMP:%.*]] = load ptr, ptr %p.addr
// CHECK-NEXT: store ptr [[TMP]], ptr %d
// CHECK-NEXT: ret void
int *d = __builtin_start_object_lifetime(p);
}

struct TestNoInvariant {
int x;
};
// CHECK-LABEL: define{{.*}} void @test_non_dynamic_class
extern "C" void test_non_dynamic_class(TestNoInvariant *p) {
// CHECK: entry
// CHECK-NOT: llvm.launder.invariant.group
// CHECK-NEXT: %p.addr = alloca ptr, align 8
// CHECK-NEXT: %d = alloca ptr
// CHECK-NEXT: store ptr %p, ptr %p.addr
// CHECK-NEXT: [[TMP:%.*]] = load ptr, ptr %p.addr
// CHECK-NEXT: store ptr [[TMP]], ptr %d
// CHECK-NEXT: ret void
TestNoInvariant *d = __builtin_start_object_lifetime(p);
}
36 changes: 35 additions & 1 deletion clang/test/SemaCXX/builtins.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 %s -fsyntax-only -verify -std=c++11 -fcxx-exceptions
// RUN: %clang_cc1 %s -fsyntax-only -verify -DCXX11 -std=c++11 -fcxx-exceptions
// RUN: %clang_cc1 %s -fsyntax-only -verify -std=c++1z -fcxx-exceptions
typedef const struct __CFString * CFStringRef;
#define CFSTR __builtin___CFStringMakeConstantString
Expand Down Expand Up @@ -161,6 +161,40 @@ void test_noexcept(int *i) {
#undef TEST_TYPE
} // end namespace test_launder

namespace test_start_object_lifetime {
// The builtin is non-constant.
constexpr int test_non_constexpr(int i) { // expected-error {{constexpr function never produces a constant expression}}
__builtin_start_object_lifetime(&i); // expected-note {{subexpression not valid in a constant expression}}
#ifdef CXX11
// expected-warning@-2 {{use of this statement in a constexpr function is a C++14 extension}}
#endif
return 0;
}

struct Incomplete; // expected-note {{forward declaration}}
void test_diag(Incomplete *i) {
// Requires a complete type
__builtin_start_object_lifetime(i); // expected-error {{incomplete type 'Incomplete' where a complete type is required}}

int x;
__builtin_start_object_lifetime(x); // expected-error {{non-pointer argument to '__builtin_start_object_lifetime' is not allowed}}
}

// The builtin is type-generic.
#define TEST_TYPE(Ptr, Type) \
static_assert(__is_same(decltype(__builtin_start_object_lifetime(Ptr)), Type), "expected same type")
void test_type_generic() {
char * p;
int * i;
TEST_TYPE(p, char*);
TEST_TYPE(i, int*);
}
// The builtin is noexcept.
void test_noexcept(int *i) {
static_assert(noexcept(__builtin_start_object_lifetime(i)), "");
}
}

template<typename T> void test_builtin_complex(T v, double d) {
(void)__builtin_complex(v, d); // expected-error {{different types}} expected-error {{not a real floating}}
(void)__builtin_complex(d, v); // expected-error {{different types}} expected-error {{not a real floating}}
Expand Down
Loading