[Clang] Add __builtin_launder
Summary: This patch adds `__builtin_launder`, which is required to implement `std::launder`. Additionally GCC provides `__builtin_launder`, so thing brings Clang in-line with GCC. I'm not exactly sure what magic `__builtin_launder` requires, but based on previous discussions this patch applies a `@llvm.invariant.group.barrier`. As noted in previous discussions, this may not be enough to correctly handle vtables. Reviewers: rnk, majnemer, rsmith Reviewed By: rsmith Subscribers: kristina, Romain-Geissler-1A, erichkeane, amharc, jroelofs, cfe-commits, Prazek Differential Revision: https://reviews.llvm.org/D40218 llvm-svn: 349195
This commit is contained in:
parent
8f06d10291
commit
261875054e
|
@ -498,6 +498,7 @@ BUILTIN(__builtin_snprintf, "ic*zcC*.", "nFp:2:")
|
|||
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!
|
||||
|
|
|
@ -9475,4 +9475,8 @@ 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<
|
||||
"%select{non-pointer|function pointer|void pointer}0 argument to "
|
||||
"'__builtin_launder' is not allowed">;
|
||||
|
||||
} // end of sema component.
|
||||
|
|
|
@ -6112,7 +6112,8 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Builtin::BI__builtin_launder:
|
||||
return evaluatePointer(E->getArg(0), Result);
|
||||
case Builtin::BIstrchr:
|
||||
case Builtin::BIwcschr:
|
||||
case Builtin::BImemchr:
|
||||
|
|
|
@ -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 @@ static llvm::Value *dumpRecord(CodeGenFunction &CGF, QualType RType,
|
|||
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));
|
||||
|
@ -2474,6 +2511,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
|
|||
|
||||
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:
|
||||
|
|
|
@ -880,6 +880,66 @@ static bool SemaOpenCLBuiltinToAddr(Sema &S, unsigned BuiltinID,
|
|||
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
|
||||
|
@ -1042,6 +1102,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
|
|||
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:
|
||||
|
|
|
@ -132,6 +132,8 @@ int main() {
|
|||
R(extract_return_addr, (&N));
|
||||
P(signbit, (1.0));
|
||||
|
||||
R(launder, (&N));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -396,6 +398,15 @@ long long test_builtin_readcyclecounter() {
|
|||
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__
|
||||
|
||||
|
|
|
@ -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 <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
|
|
@ -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
|
||||
|
|
|
@ -258,6 +258,24 @@ char * Test20(char *p, const char *in, unsigned n)
|
|||
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)}}
|
||||
|
|
|
@ -53,3 +53,95 @@ extern "C" int vfprintf(FILE *__restrict, const char *__restrict,
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue