Index: include/clang/Basic/Builtins.def =================================================================== --- include/clang/Basic/Builtins.def +++ include/clang/Basic/Builtins.def @@ -483,6 +483,7 @@ BUILTIN(__builtin_vsprintf, "ic*cC*a", "nFP:1:") BUILTIN(__builtin_vsnprintf, "ic*zcC*a", "nFP:2:") BUILTIN(__builtin_thread_pointer, "v*", "nc") +BUILTIN(__builtin_launder, "v*v*", "nt") // GCC exception builtins BUILTIN(__builtin_eh_return, "vzv*", "r") // FIXME: Takes intptr_t, not size_t! Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -9397,6 +9397,10 @@ InGroup, DefaultIgnore; def note_shadow_field : Note<"declared here">; +def err_builtin_launder_invalid_arg : Error< + "%select{non-pointer|function pointer|void pointer}0 argument to " + "'__builtin_launder' is not allowed">; + def err_target_required_in_redecl : Error< "function declaration is missing 'target' attribute in a multiversioned " "function">; Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -6026,7 +6026,8 @@ return true; } - + case Builtin::BI__builtin_launder: + return evaluatePointer(E->getArg(0), Result); case Builtin::BIstrchr: case Builtin::BIwcschr: case Builtin::BImemchr: Index: lib/CodeGen/CGBuiltin.cpp =================================================================== --- lib/CodeGen/CGBuiltin.cpp +++ lib/CodeGen/CGBuiltin.cpp @@ -1021,6 +1021,49 @@ return Res; } +static bool TypeRequiresBuiltinLaunderImp(const ASTContext &Ctx, QualType Ty, + llvm::DenseSet &Seen) { + if (const auto *Arr = Ctx.getAsArrayType(Ty)) + Ty = Ctx.getBaseElementType(Arr); + + const auto *Record = Ty->getAsCXXRecordDecl(); + if (!Record) + return false; + + // We've already checked this type, or are in the process of checking it. + if (!Seen.insert(Record).second) + return false; + + // FIXME: We either have an incomplete class type, or we have a class template + // whose instantiation has not been forced. Example: + // + // template struct Foo { T value; }; + // Foo *p = nullptr; + // auto *d = __builtin_launder(p); + if (!Record->hasDefinition()) + return true; + + if (Record->isDynamicClass()) + return true; + + // if (!Seen.insert(Record).second) + // return false; + for (FieldDecl *F : Record->fields()) { + if (TypeRequiresBuiltinLaunderImp(Ctx, F->getType(), Seen)) + return true; + } + return false; +} + +/// Determine if the specified type requires laundering by checking if it is a +/// dynamic class type or contains a subobject which is a dynamic class type. +static bool TypeRequiresBuiltinLaunder(CodeGenModule &CGM, QualType Ty) { + if (!CGM.getCodeGenOpts().StrictVTablePointers) + return false; + llvm::DenseSet Seen; + return TypeRequiresBuiltinLaunderImp(CGM.getContext(), Ty, Seen); +} + RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -2030,6 +2073,15 @@ return RValue::get(nullptr); } + case Builtin::BI__builtin_launder: { + const Expr *Arg = E->getArg(0); + QualType ArgTy = Arg->getType()->getPointeeType(); + Value *Ptr = EmitScalarExpr(Arg); + if (TypeRequiresBuiltinLaunder(CGM, ArgTy)) + Ptr = Builder.CreateLaunderInvariantGroup(Ptr); + + return RValue::get(Ptr); + } case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_sub: case Builtin::BI__sync_fetch_and_or: Index: lib/Sema/SemaChecking.cpp =================================================================== --- lib/Sema/SemaChecking.cpp +++ lib/Sema/SemaChecking.cpp @@ -851,6 +851,49 @@ return false; } +static ExprResult SemaBuiltinLaunder(Sema &S, CallExpr *TheCall) { + if (checkArgCount(S, TheCall, 1)) + return ExprError(); + + Expr *OrigArg = TheCall->getArg(0); + // Don't perform LValue conversions since they may strip things like the + // restrict qualifier + ExprResult Arg = S.DefaultFunctionArrayConversion(OrigArg); + if (Arg.isInvalid()) + return ExprError(); + + TheCall->setArg(0, Arg.get()); + QualType ArgTy = Arg.get()->getType(); + TheCall->setType(ArgTy); + + auto DiagSelect = [&]() -> llvm::Optional { + if (!ArgTy->isPointerType()) + return 0; + if (ArgTy->isFunctionPointerType()) + return 1; + if (ArgTy->isVoidPointerType()) + return 2; + return llvm::Optional{}; + }(); + if (DiagSelect.hasValue()) { + S.Diag(TheCall->getLocStart(), diag::err_builtin_launder_invalid_arg) + << DiagSelect.getValue() << TheCall->getSourceRange(); + return ExprError(); + } + + assert(ArgTy->getPointeeType()->isObjectType() && "Unhandled non-object pointer case"); + + InitializedEntity Entity = + InitializedEntity::InitializeParameter(S.Context, ArgTy, false); + Arg = TheCall->getArg(0); + Arg = S.PerformCopyInitialization(Entity, SourceLocation(), Arg); + if (Arg.isInvalid()) + return ExprError(); + TheCall->setArg(0, Arg.get()); + + return TheCall; +} + ExprResult Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, CallExpr *TheCall) { @@ -968,6 +1011,8 @@ if (checkArgCount(*this, TheCall, 1)) return true; TheCall->setType(Context.IntTy); break; + case Builtin::BI__builtin_launder: + return SemaBuiltinLaunder(*this, TheCall); case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_add_1: case Builtin::BI__sync_fetch_and_add_2: Index: test/CodeGen/builtins.c =================================================================== --- test/CodeGen/builtins.c +++ test/CodeGen/builtins.c @@ -132,6 +132,8 @@ R(extract_return_addr, (&N)); P(signbit, (1.0)); + R(launder, (&N)); + return 0; } @@ -396,6 +398,18 @@ return __builtin_readcyclecounter(); } +/// __builtin_launder should be a NOP in C since there are no vtables. +// CHECK-LABEL: define void @test_builtin_launder +void test_builtin_launder(int *p) { + // CHECK: entry + // CHECK-NEXT: %p.addr = alloca i32* + // CHECK-NEXT: %d = alloca i32* + // CHECK-NEXT: store i32* %p, i32** %p.addr, align 8 + // CHECK-NEXT: [[TMP:%.*]] = load i32*, i32** %p.addr + // CHECK-NEXT: store i32* [[TMP]], i32** %d + int *d = __builtin_launder(p); +} + // Behavior of __builtin_os_log differs between platforms, so only test on X86 #ifdef __x86_64__ Index: test/CodeGenCXX/builtin-launder.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/builtin-launder.cpp @@ -0,0 +1,360 @@ +// 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 + +//===----------------------------------------------------------------------===// +// Positive Cases +//===----------------------------------------------------------------------===// + +struct TestVirtualFn { + virtual void foo() {} +}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_fn +extern "C" void test_builtin_launder_virtual_fn(TestVirtualFn *p) { + // CHECK: entry + // CHECK-NEXT: %p.addr = alloca [[TYPE:%.*]], align 8 + // CHECK-NEXT: %d = alloca [[TYPE]] + // CHECK-NEXT: store [[TYPE]] %p, [[TYPE]]* %p.addr + // CHECK-NEXT: [[TMP0:%.*]] = load [[TYPE]], [[TYPE]]* %p.addr + + // CHECK-NONSTRICT-NEXT: store [[TYPE]] [[TMP0]], [[TYPE]]* %d + + // CHECK-STRICT-NEXT: [[TMP1:%.*]] = bitcast [[TYPE]] [[TMP0]] to i8* + // CHECK-STRICT-NEXT: [[TMP2:%.*]] = call i8* @llvm.launder.invariant.group.p0i8(i8* [[TMP1]]) + // CHECK-STRICT-NEXT: [[TMP3:%.*]] = bitcast i8* [[TMP2]] to [[TYPE]] + // CHECK-STRICT-NEXT: store [[TYPE]] [[TMP3]], [[TYPE]]* %d + + // CHECK-NEXT: ret void + TestVirtualFn *d = __builtin_launder(p); +} + +struct TestPolyBase : TestVirtualFn { +}; + +// CHECK-LABEL: define void @test_builtin_launder_poly_base +extern "C" void test_builtin_launder_poly_base(TestPolyBase *p) { + // CHECK-STRICT-NOT: ret void + // CHECK-STRICT: @llvm.launder.invariant.group + + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + + // CHECK: ret void + TestPolyBase *d = __builtin_launder(p); +} + +struct TestBase {}; +struct TestVirtualBase : virtual TestBase {}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_base +extern "C" void test_builtin_launder_virtual_base(TestVirtualBase *p) { + // CHECK-STRICT-NOT: ret void + // CHECK-STRICT: @llvm.launder.invariant.group + + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + + // CHECK: ret void + TestVirtualBase *d = __builtin_launder(p); +} + +//===----------------------------------------------------------------------===// +// Negative Cases +//===----------------------------------------------------------------------===// + +// CHECK-LABEL: define void @test_builtin_launder_ommitted_one +extern "C" void test_builtin_launder_ommitted_one(int *p) { + // CHECK: entry + // CHECK-NEXT: %p.addr = alloca i32* + // CHECK-NEXT: %d = alloca i32* + // CHECK-NEXT: store i32* %p, i32** %p.addr, align 8 + // CHECK-NEXT: [[TMP:%.*]] = load i32*, i32** %p.addr + // CHECK-NEXT: store i32* [[TMP]], i32** %d + // CHECK-NEXT: ret void + int *d = __builtin_launder(p); +} + +struct TestNoInvariant { + int x; +}; + +// CHECK-LABEL: define void @test_builtin_launder_ommitted_two +extern "C" void test_builtin_launder_ommitted_two(TestNoInvariant *p) { + // CHECK: entry + // CHECK-NOT: llvm.launder.invariant.group + // CHECK-NEXT: %p.addr = alloca [[TYPE:%.*]], align 8 + // CHECK-NEXT: %d = alloca [[TYPE]] + // CHECK-NEXT: store [[TYPE]] %p, [[TYPE]]* %p.addr + // CHECK-NEXT: [[TMP:%.*]] = load [[TYPE]], [[TYPE]]* %p.addr + // CHECK-NEXT: store [[TYPE]] [[TMP]], [[TYPE]]* %d + // CHECK-NEXT: ret void + TestNoInvariant *d = __builtin_launder(p); +} + +struct TestVirtualMember { + TestVirtualFn member; +}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_member +extern "C" void test_builtin_launder_virtual_member(TestVirtualMember *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + TestVirtualMember *d = __builtin_launder(p); +} + +struct TestVirtualMemberDepth2 { + TestVirtualMember member; +}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_member_depth_2 +extern "C" void test_builtin_launder_virtual_member_depth_2(TestVirtualMemberDepth2 *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + TestVirtualMemberDepth2 *d = __builtin_launder(p); +} + +struct TestVirtualReferenceMember { + TestVirtualFn &member; +}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_reference_member +extern "C" void test_builtin_launder_virtual_reference_member(TestVirtualReferenceMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestVirtualReferenceMember *d = __builtin_launder(p); +} + +struct TestRecursiveMember { + TestRecursiveMember() : member(*this) {} + TestRecursiveMember &member; +}; + +// CHECK-LABEL: define void @test_builtin_launder_recursive_member +extern "C" void test_builtin_launder_recursive_member(TestRecursiveMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestRecursiveMember *d = __builtin_launder(p); +} + +struct TestVirtualRecursiveMember { + TestVirtualRecursiveMember() : member(*this) {} + TestVirtualRecursiveMember &member; + virtual void foo(); +}; + +// CHECK-LABEL: define void @test_builtin_launder_virtual_recursive_member +extern "C" void test_builtin_launder_virtual_recursive_member(TestVirtualRecursiveMember *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + TestVirtualRecursiveMember *d = __builtin_launder(p); +} + +// CHECK-LABEL: define void @test_builtin_launder_array( +extern "C" void test_builtin_launder_array(TestVirtualFn (&Arr)[5]) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + TestVirtualFn *d = __builtin_launder(Arr); +} + +// CHECK-LABEL: define void @test_builtin_launder_array_nested( +extern "C" void test_builtin_launder_array_nested(TestVirtualFn (&Arr)[5][2]) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + using RetTy = TestVirtualFn(*)[2]; + RetTy d = __builtin_launder(Arr); +} + +// CHECK-LABEL: define void @test_builtin_launder_array_no_invariant( +extern "C" void test_builtin_launder_array_no_invariant(TestNoInvariant (&Arr)[5]) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestNoInvariant *d = __builtin_launder(Arr); +} + +// CHECK-LABEL: define void @test_builtin_launder_array_nested_no_invariant( +extern "C" void test_builtin_launder_array_nested_no_invariant(TestNoInvariant (&Arr)[5][2]) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + using RetTy = TestNoInvariant(*)[2]; + RetTy d = __builtin_launder(Arr); +} + +template +struct WithMember { + Member mem; +}; + +template struct WithMember; + +// CHECK-LABEL: define void @test_builtin_launder_member_array( +extern "C" void test_builtin_launder_member_array(WithMember *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +template struct WithMember; + +// CHECK-LABEL: define void @test_builtin_launder_member_array_nested( +extern "C" void test_builtin_launder_member_array_nested(WithMember *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +template struct WithMember; + +// CHECK-LABEL: define void @test_builtin_launder_member_array_no_invariant( +extern "C" void test_builtin_launder_member_array_no_invariant(WithMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +template struct WithMember; + +// CHECK-LABEL: define void @test_builtin_launder_member_array_nested_no_invariant( +extern "C" void test_builtin_launder_member_array_nested_no_invariant(WithMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +// FIXME: Because WithMember is never instantiated, we have to assume +// it requires laundering. +// CHECK-LABEL: define void @test_builtin_launder_uninstantiated_template( +extern "C" void test_builtin_launder_uninstantiated_template(WithMember *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +struct Incomplete; +// CHECK-LABEL: define void @test_builtin_launder_incomplete( +extern "C" void test_builtin_launder_incomplete(Incomplete *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + Incomplete *d = __builtin_launder(p); +} + +struct LaterComplete; + +// FIXME: Can/Should we dig out the definition for LaterComplete when performing +// CodeGen? + +// CHECK-LABEL: define void @test_builtin_launder_complete_later( +extern "C" void test_builtin_launder_complete_later(LaterComplete *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + LaterComplete *d = __builtin_launder(p); +} +struct LaterComplete {}; + +template +struct WithBase : T {}; + +template struct WithBase; + +// CHECK-LABEL: define void @test_builtin_launder_base_no_invariant( +extern "C" void test_builtin_launder_base_no_invariant(WithBase *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +template struct WithBase; + +// CHECK-LABEL: define void @test_builtin_launder_base( +extern "C" void test_builtin_launder_base(WithBase *p) { + // CHECK: entry + // CHECK-NONSTRICT-NOT: @llvm.launder.invariant.group + // CHECK-STRICT: @llvm.launder.invariant.group + // CHECK: ret void + auto *d = __builtin_launder(p); +} + +/// The test cases in this namespace technically need to be laundered according +/// to the language in the standard (ie they have const or reference subobjects) +/// but LLVM doesn't currently optimize on these cases -- so Clang emits +/// __builtin_launder as a nop. +/// +/// NOTE: Adding optimizations for these cases later is an LTO ABI break. That's +/// probably OK for now -- but is something to keep in mind. +namespace pessimizing_cases { + +struct TestConstMember { + const int x; +}; + +// CHECK-LABEL: define void @test_builtin_launder_const_member +extern "C" void test_builtin_launder_const_member(TestConstMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestConstMember *d = __builtin_launder(p); +} + +struct TestConstSubobject { + TestConstMember x; +}; + +// CHECK-LABEL: define void @test_builtin_launder_const_subobject +extern "C" void test_builtin_launder_const_subobject(TestConstSubobject *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestConstSubobject *d = __builtin_launder(p); +} + +struct TestConstObject { + const struct TestConstMember x; +}; + +// CHECK-LABEL: define void @test_builtin_launder_const_object +extern "C" void test_builtin_launder_const_object(TestConstObject *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestConstObject *d = __builtin_launder(p); +} + +struct TestReferenceMember { + int &x; +}; + +// CHECK-LABEL: define void @test_builtin_launder_reference_member +extern "C" void test_builtin_launder_reference_member(TestReferenceMember *p) { + // CHECK: entry + // CHECK-NOT: @llvm.launder.invariant.group + // CHECK: ret void + TestReferenceMember *d = __builtin_launder(p); +} + +} // namespace pessimizing_cases Index: test/Preprocessor/feature_tests.c =================================================================== --- test/Preprocessor/feature_tests.c +++ test/Preprocessor/feature_tests.c @@ -14,6 +14,7 @@ !__has_builtin(__builtin_convertvector) || \ !__has_builtin(__builtin_trap) || \ !__has_builtin(__c11_atomic_init) || \ + !__has_builtin(__builtin_launder) || \ !__has_feature(attribute_analyzer_noreturn) || \ !__has_feature(attribute_overloadable) #error Clang should have these Index: test/Sema/builtins.c =================================================================== --- test/Sema/builtins.c +++ test/Sema/builtins.c @@ -249,6 +249,24 @@ return buf; } +typedef void (fn_t)(int); + +void test_builtin_launder(char *p, void *vp, const void *cvp, + const volatile int *ip, float *restrict fp, + fn_t *fn) { + __builtin_launder(); // expected-error {{too few arguments to function call, expected 1, have 0}} + __builtin_launder(p, p); // expected-error {{too many arguments to function call, expected 1, have 2}} + int x; + __builtin_launder(x); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} + char *d = __builtin_launder(p); + __builtin_launder(vp); // expected-error {{void pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(cvp); // expected-error {{void pointer argument to '__builtin_launder' is not allowed}} + const volatile int *id = __builtin_launder(ip); + int *id2 = __builtin_launder(ip); // expected-warning {{discards qualifiers}} + float *fd = __builtin_launder(fp); + __builtin_launder(fn); // expected-error {{function pointer argument to '__builtin_launder' is not allowed}} +} + void test21(const int *ptr) { __sync_fetch_and_add(ptr, 1); // expected-error{{address argument to atomic builtin cannot be const-qualified ('const int *' invalid)}} __atomic_fetch_add(ptr, 1, 0); // expected-error {{address argument to atomic operation must be a pointer to non-const type ('const int *' invalid)}} Index: test/SemaCXX/builtins.cpp =================================================================== --- test/SemaCXX/builtins.cpp +++ test/SemaCXX/builtins.cpp @@ -53,3 +53,72 @@ void synchronize_args() { __sync_synchronize(0); // expected-error {{too many arguments}} } + +namespace test_launder { + +struct Dummy {}; + +using FnType = int(char); +using MemFnType = int (Dummy::*)(char); +using ConstMemFnType = int (Dummy::*)() const; + +void foo() {} + +void test_builtin_launder_diags(void *vp, const void *cvp, FnType *fnp, + MemFnType mfp, ConstMemFnType cmfp, int (&Arr)[5]) { + __builtin_launder(vp); // expected-error {{void pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(cvp); // expected-error {{void pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(fnp); // expected-error {{function pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(mfp); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(cmfp); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} + (void)__builtin_launder(&fnp); + __builtin_launder(42); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(nullptr); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} + __builtin_launder(foo); // expected-error {{function pointer argument to '__builtin_launder' is not allowed}} + (void)__builtin_launder(Arr); +} + +void test_builtin_launder(char *p, const volatile int *ip, const float *&fp, + double *__restrict dp) { + int x; + __builtin_launder(x); // expected-error {{non-pointer argument to '__builtin_launder' is not allowed}} +#define TEST_TYPE(Ptr, Type) \ + static_assert(__is_same(decltype(__builtin_launder(Ptr)), Type), "expected same type") + TEST_TYPE(p, char*); + TEST_TYPE(ip, const volatile int*); + TEST_TYPE(fp, const float*); + TEST_TYPE(dp, double *__restrict); +#undef TEST_TYPE + char *d = __builtin_launder(p); + const volatile int *id = __builtin_launder(ip); + int *id2 = __builtin_launder(ip); // expected-error {{cannot initialize a variable of type 'int *' with an rvalue of type 'const volatile int *'}} + const float* fd = __builtin_launder(fp); +} + +template +constexpr Tp *test_constexpr_launder(Tp *tp) { + return __builtin_launder(tp); +} +constexpr int const_int = 42; +constexpr int const_int2 = 101; +constexpr const int *const_ptr = test_constexpr_launder(&const_int); +static_assert(&const_int == const_ptr, ""); +static_assert(const_ptr != test_constexpr_launder(&const_int2), ""); + +void test_non_constexpr() { + constexpr int i = 42; // expected-note {{declared here}} + constexpr const int *ip = __builtin_launder(&i); // expected-error {{constexpr variable 'ip' must be initialized by a constant expression}} + // expected-note@-1 {{pointer to 'i' is not a constant expression}} +} + +constexpr bool test_in_constexpr(const int &i) { + return (__builtin_launder(&i) == &i); +} +static_assert(test_in_constexpr(const_int), ""); +void f() { + constexpr int i = 42; + // FIXME: Should this work? Since `&i` doesn't. + static_assert(test_in_constexpr(i), ""); +} + +} // end namespace test_launder