Index: include/clang/Basic/Builtins.def =================================================================== --- include/clang/Basic/Builtins.def +++ include/clang/Basic/Builtins.def @@ -498,6 +498,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 @@ -9475,4 +9475,8 @@ def warn_noderef_to_dereferenceable_pointer : Warning< "casting to dereferenceable pointer removes 'noderef' attribute">, InGroup; +def err_builtin_launder_invalid_arg : Error< + "%select{non-pointer|function pointer|void pointer}0 argument to " + "'__builtin_launder' is not allowed">; + } // end of sema component. Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -6112,7 +6112,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 @@ -25,6 +25,7 @@ #include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CGFunctionInfo.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/DataLayout.h" @@ -1409,6 +1410,42 @@ return Res; } +static bool +TypeRequiresBuiltinLaunderImp(const ASTContext &Ctx, QualType Ty, + llvm::SmallPtrSetImpl &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; + + assert(Record->hasDefinition() && + "Incomplete types should already be diagnosed"); + + if (Record->isDynamicClass()) + return true; + + 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::SmallPtrSet Seen; + return TypeRequiresBuiltinLaunderImp(CGM.getContext(), Ty, Seen); +} + RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) { llvm::Value *Src = EmitScalarExpr(E->getArg(0)); llvm::Value *ShiftAmt = EmitScalarExpr(E->getArg(1)); @@ -2474,6 +2511,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 @@ -880,6 +880,66 @@ return false; } +static ExprResult SemaBuiltinLaunder(Sema &S, CallExpr *TheCall) { + if (checkArgCount(S, TheCall, 1)) + return ExprError(); + + // Compute __builtin_launder's parameter type from the argument. + // The parameter type is: + // * The type of the argument if it's not an array or function type, + // Otherwise, + // * The decayed argument type. + QualType ParamTy = [&]() { + QualType ArgTy = TheCall->getArg(0)->getType(); + if (const ArrayType *Ty = ArgTy->getAsArrayTypeUnsafe()) + return S.Context.getPointerType(Ty->getElementType()); + if (ArgTy->isFunctionType()) { + return S.Context.getPointerType(ArgTy); + } + return ArgTy; + }(); + + TheCall->setType(ParamTy); + + auto DiagSelect = [&]() -> llvm::Optional { + if (!ParamTy->isPointerType()) + return 0; + if (ParamTy->isFunctionPointerType()) + return 1; + if (ParamTy->isVoidPointerType()) + return 2; + return llvm::Optional{}; + }(); + if (DiagSelect.hasValue()) { + S.Diag(TheCall->getBeginLoc(), diag::err_builtin_launder_invalid_arg) + << DiagSelect.getValue() << TheCall->getSourceRange(); + return ExprError(); + } + + // 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 (S.RequireCompleteType(TheCall->getBeginLoc(), ParamTy->getPointeeType(), + diag::err_incomplete_type)) + return ExprError(); + + assert(ParamTy->getPointeeType()->isObjectType() && + "Unhandled non-object pointer case"); + + InitializedEntity Entity = + InitializedEntity::InitializeParameter(S.Context, ParamTy, false); + ExprResult Arg = + S.PerformCopyInitialization(Entity, SourceLocation(), TheCall->getArg(0)); + if (Arg.isInvalid()) + return ExprError(); + TheCall->setArg(0, Arg.get()); + + return TheCall; +} + // Emit an error and return true if the current architecture is not in the list // of supported architectures. static bool @@ -1042,6 +1102,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,15 @@ 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: [[TMP:%.*]] = load i32*, + // CHECK-NOT: @llvm.launder + // CHECK: store i32* [[TMP]], + 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 =================================================================== --- test/CodeGenCXX/builtin-launder.cpp +++ test/CodeGenCXX/builtin-launder.cpp @@ -0,0 +1,321 @@ +// 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: 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); +} + +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 @@ -258,6 +258,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,95 @@ void synchronize_args() { __sync_synchronize(0); // expected-error {{too many arguments}} } + +namespace test_launder { +#define TEST_TYPE(Ptr, Type) \ + static_assert(__is_same(decltype(__builtin_launder(Ptr)), Type), "expected same type") + +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}} + + TEST_TYPE(p, char*); + TEST_TYPE(ip, const volatile int*); + TEST_TYPE(fp, const float*); + TEST_TYPE(dp, double *__restrict); + + 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); +} + +void test_launder_return_type(const int (&ArrayRef)[101], int (&MArrRef)[42][13], + void (**&FuncPtrRef)()) { + TEST_TYPE(ArrayRef, const int *); + TEST_TYPE(MArrRef, int(*)[13]); + TEST_TYPE(FuncPtrRef, void (**)()); +} + +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; + static_assert(test_in_constexpr(i), ""); +} + +struct Incomplete; // expected-note {{forward declaration}} +struct IncompleteMember { + Incomplete &i; +}; +void test_incomplete(Incomplete *i, IncompleteMember *im) { + // expected-error@+1 {{incomplete type 'test_launder::Incomplete' where a complete type is required}} + __builtin_launder(i); + __builtin_launder(&i); // OK + __builtin_launder(im); // OK +} + +void test_noexcept(int *i) { + static_assert(noexcept(__builtin_launder(i)), ""); +} +#undef TEST_TYPE +} // end namespace test_launder