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!
def warn_noderef_to_dereferenceable_pointer : Warning<
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;
+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.
return true;
}
-
+ case Builtin::BI__builtin_launder:
+ return evaluatePointer(E->getArg(0), Result);
case Builtin::BIstrchr:
case Builtin::BIwcschr:
case Builtin::BImemchr:
#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"
return Res;
}
+static bool
+TypeRequiresBuiltinLaunderImp(const ASTContext &Ctx, QualType Ty,
+ llvm::SmallPtrSetImpl<const Decl *> &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<const Decl *, 16> 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));
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:
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<unsigned> {
+ if (!ParamTy->isPointerType())
+ return 0;
+ if (ParamTy->isFunctionPointerType())
+ return 1;
+ if (ParamTy->isVoidPointerType())
+ return 2;
+ return llvm::Optional<unsigned>{};
+ }();
+ 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 <class T> struct Foo { T value; };
+ // Foo<int> *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
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:
R(extract_return_addr, (&N));
P(signbit, (1.0));
+ R(launder, (&N));
+
return 0;
}
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__
--- /dev/null
+// 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 <class Member>
+struct WithMember {
+ Member mem;
+};
+
+template struct WithMember<TestVirtualFn[5]>;
+
+// CHECK-LABEL: define void @test_builtin_launder_member_array(
+extern "C" void test_builtin_launder_member_array(WithMember<TestVirtualFn[5]> *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<TestVirtualFn[5][2]>;
+
+// CHECK-LABEL: define void @test_builtin_launder_member_array_nested(
+extern "C" void test_builtin_launder_member_array_nested(WithMember<TestVirtualFn[5][2]> *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<TestNoInvariant[5]>;
+
+// CHECK-LABEL: define void @test_builtin_launder_member_array_no_invariant(
+extern "C" void test_builtin_launder_member_array_no_invariant(WithMember<TestNoInvariant[5]> *p) {
+ // CHECK: entry
+ // CHECK-NOT: @llvm.launder.invariant.group
+ // CHECK: ret void
+ auto *d = __builtin_launder(p);
+}
+
+template struct WithMember<TestNoInvariant[5][2]>;
+
+// CHECK-LABEL: define void @test_builtin_launder_member_array_nested_no_invariant(
+extern "C" void test_builtin_launder_member_array_nested_no_invariant(WithMember<TestNoInvariant[5][2]> *p) {
+ // CHECK: entry
+ // CHECK-NOT: @llvm.launder.invariant.group
+ // CHECK: ret void
+ auto *d = __builtin_launder(p);
+}
+
+template <class T>
+struct WithBase : T {};
+
+template struct WithBase<TestNoInvariant>;
+
+// CHECK-LABEL: define void @test_builtin_launder_base_no_invariant(
+extern "C" void test_builtin_launder_base_no_invariant(WithBase<TestNoInvariant> *p) {
+ // CHECK: entry
+ // CHECK-NOT: @llvm.launder.invariant.group
+ // CHECK: ret void
+ auto *d = __builtin_launder(p);
+}
+
+template struct WithBase<TestVirtualFn>;
+
+// CHECK-LABEL: define void @test_builtin_launder_base(
+extern "C" void test_builtin_launder_base(WithBase<TestVirtualFn> *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
!__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
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)}}
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 <class Tp>
+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