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 @@ -9332,6 +9332,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 @@ -5895,7 +5895,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 @@ -1882,6 +1882,17 @@ return RValue::get(nullptr); } + case Builtin::BI__builtin_launder: { + const Expr *Arg = E->getArg(0); + QualType ArgTy = Arg->getType()->getPointeeType(); + Value *Ptr = EmitScalarExpr(Arg); + const auto *Record = ArgTy->getAsCXXRecordDecl(); + if (CGM.getCodeGenOpts().StrictVTablePointers && Record && + Record->isDynamicClass()) + Ptr = Builder.CreateInvariantGroupBarrier(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 @@ -850,6 +850,41 @@ return false; } +static ExprResult SemaBuiltinLaunder(Sema &S, CallExpr *TheCall) { + if (checkArgCount(S, TheCall, 1)) + return ExprError(); + + QualType ArgTy = TheCall->getArg(0)->getType(); + TheCall->setType(ArgTy); + + int DiagSelect = [&]() { + if (!ArgTy->isPointerType()) + return 0; + if (ArgTy->isFunctionPointerType()) + return 1; + if (ArgTy->isVoidPointerType()) + return 2; + return -1; + }(); + if (DiagSelect != -1) { + S.Diag(TheCall->getLocStart(), diag::err_builtin_launder_invalid_arg) + << DiagSelect << TheCall->getSourceRange(); + return ExprError(); + } + + assert(ArgTy->getPointeeType()->isObjectType() && "Unhandled non-object pointer case"); + + InitializedEntity Entity = + InitializedEntity::InitializeParameter(S.Context, ArgTy, false); + ExprResult 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) { @@ -967,6 +1002,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,147 @@ +// 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.invariant.group.barrier.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.invariant.group.barrier + + // CHECK-NONSTRICT-NOT: @llvm.invariant.group.barrier + + // 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.invariant.group.barrier + + // CHECK-NONSTRICT-NOT: @llvm.invariant.group.barrier + + // 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-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); +} + +/// 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. +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.invariant.group.barrier + // 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.invariant.group.barrier + // 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.invariant.group.barrier + // 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.invariant.group.barrier + // 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 @@ -248,3 +248,21 @@ 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}} +} Index: test/SemaCXX/builtins.cpp =================================================================== --- test/SemaCXX/builtins.cpp +++ test/SemaCXX/builtins.cpp @@ -53,3 +53,71 @@ 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) { + __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 {{non-pointer argument to '__builtin_launder' is not allowed}} +} + +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