diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 6d6104a3ddb8d..226ad1cc03078 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -859,7 +859,7 @@ def BuiltinAssumeAligned : Builtin { def BuiltinAssumeDereferenceable : Builtin { let Spellings = ["__builtin_assume_dereferenceable"]; - let Attributes = [NoThrow, Const]; + let Attributes = [NoThrow, Const, Constexpr]; let Prototype = "void(void const*, size_t)"; } diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 971fce541bb88..837f9dabef621 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2174,6 +2174,55 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx, return Result; } +/// __builtin_assume_dereferenceable(Ptr, Size) +static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, + const InterpFrame *Frame, + const CallExpr *Call) { + assert(Call->getNumArgs() == 2); + + APSInt ReqSize = popToAPSInt(S, Call->getArg(1)); + const Pointer &Ptr = S.Stk.pop(); + + if (ReqSize.isZero()) + return true; + if (Ptr.isZero()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_null) + << AK_Read << S.Current->getRange(OpPC); + return false; + } + if (!Ptr.isBlockPointer()) { + if (Ptr.isIntegralPointer()) + return true; + return false; + } + if (!Ptr.isLive()) + return false; + if (Ptr.isPastEnd()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end) + << AK_Read << S.Current->getRange(OpPC); + return false; + } + + const ASTContext &ASTCtx = S.getASTContext(); + const Descriptor *DeclDesc = Ptr.getDeclDesc(); + std::optional FullSize = computeFullDescSize(ASTCtx, DeclDesc); + if (!FullSize) + return false; + + unsigned ByteOffset = computePointerOffset(ASTCtx, Ptr); + if (ByteOffset > *FullSize) + return false; + + unsigned AvailSize = *FullSize - ByteOffset; + if (AvailSize < ReqSize.getZExtValue()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end) + << AK_Read << S.Current->getRange(OpPC); + return false; + } + + return true; +} + /// Does Ptr point to the last subobject? static bool pointsToLastObject(const Pointer &Ptr) { Pointer P = Ptr; @@ -3729,6 +3778,9 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, case Builtin::BI__assume: return interp__builtin_assume(S, OpPC, Frame, Call); + case Builtin::BI__builtin_assume_dereferenceable: + return interp__builtin_assume_dereferenceable(S, OpPC, Frame, Call); + case Builtin::BI__builtin_strcmp: case Builtin::BIstrcmp: case Builtin::BI__builtin_strncmp: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index e5af4cb049ba9..ba376ba774511 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -19687,6 +19687,46 @@ class VoidExprEvaluator // The argument is not evaluated! return true; + case Builtin::BI__builtin_assume_dereferenceable: { + assert(E->getType()->isVoidType()); + assert(E->getNumArgs() == 2); + + APSInt ReqSizeVal; + if (!::EvaluateInteger(E->getArg(1), ReqSizeVal, Info)) + return false; + if (ReqSizeVal.isZero()) + return true; + + LValue Pointer; + if (!EvaluatePointer(E->getArg(0), Pointer, Info)) { + if (EvaluateBuiltinConstantP(Info, E->getArg(0))) + return true; + return false; + } + if (Pointer.Designator.Invalid) + return false; + if (Pointer.isNullPointer()) { + Info.FFDiag(E, diag::note_constexpr_access_null) << AK_Read; + return false; + } + if (Pointer.Designator.isOnePastTheEnd()) { + Info.FFDiag(E, diag::note_constexpr_access_past_end) << AK_Read; + return false; + } + + uint64_t ReqSize = ReqSizeVal.getZExtValue(); + CharUnits EndOffset; + if (!determineEndOffset(Info, E->getExprLoc(), 0, Pointer, EndOffset)) + return false; + + uint64_t AvailSize = + (EndOffset - Pointer.getLValueOffset()).getQuantity(); + if (AvailSize < ReqSize) { + Info.FFDiag(E, diag::note_constexpr_access_past_end) << AK_Read; + return false; + } + return true; + } case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); diff --git a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp new file mode 100644 index 0000000000000..5f7942a72a2d7 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck %s + +int b = 10; +const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); +int use_f = f; + +constexpr int g = 20; +const int h = (__builtin_assume_dereferenceable((char*)&g + 1, 2), 42); +int use_h = h; + +constexpr char arr[] = "hello"; +constexpr const char* ptr = arr + 1; +constexpr int fully_constexpr() { + __builtin_assume_dereferenceable(ptr, 2); + return 100; +} +constexpr int i = fully_constexpr(); +int use_i = i; + +void test_nullptr() { + __builtin_assume_dereferenceable(nullptr, 0); +} + +void test_zero_size() { + int x = 10; + __builtin_assume_dereferenceable(&x, 0); +} + +void test_function_ptr() { + __builtin_assume_dereferenceable((void*)&test_zero_size, 8); +} + +// CHECK: @use_i = global i32 100 +// +// CHECK: @{{_Z[0-9]+}}test_nullptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] +// +// CHECK: @{{_Z[0-9]+}}test_zero_sizev +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr {{%.*}}, i64 0) ] +// +// CHECK: @{{_Z[0-9]+}}test_function_ptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr @{{_Z[0-9]+}}test_zero_sizev, i64 8) ] +// +// CHECK: __cxx_global_var_init +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @b, i64 1), i64 3) ] +// +// CHECK: __cxx_global_var_init +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @{{_ZL[0-9]+}}g, i64 1), i64 2) ] +// CHECK: store i32 42, ptr @{{_ZL[0-9]+}}h diff --git a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp new file mode 100644 index 0000000000000..8a59f95b6e9dd --- /dev/null +++ b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -0,0 +1,92 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 -triple x86_64-unknown-unknown %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 -triple x86_64-unknown-unknown %s -fexperimental-new-constant-interpreter + +constexpr bool test_constexpr_valid() { + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(arr, 40); + return true; +} +static_assert(test_constexpr_valid(), ""); + +constexpr bool test_constexpr_partial() { + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(&arr[5], 20); + return true; +} +static_assert(test_constexpr_partial(), ""); + +constexpr bool test_constexpr_nullptr() { // expected-error {{constexpr function never produces a constant expression}} + __builtin_assume_dereferenceable(nullptr, 4); // expected-note 2{{read of dereferenced null pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_constexpr_nullptr(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} + +constexpr bool test_constexpr_too_large() { // expected-error {{constexpr function never produces a constant expression}} + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(arr, 100); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_constexpr_too_large(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} + +constexpr bool test_single_var() { + constexpr int single_var = 42; + __builtin_assume_dereferenceable(&single_var, 4); + return true; +} +static_assert(test_single_var(), ""); + +constexpr bool test_exact_boundary() { + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(&arr[9], 4); + return true; +} +static_assert(test_exact_boundary(), ""); + +constexpr bool test_one_over() { // expected-error {{constexpr function never produces a constant expression}} + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(&arr[9], 5); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_one_over(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} + +constexpr bool test_zero_size() { + constexpr int arr[10] = {}; + __builtin_assume_dereferenceable(arr, 0); + return true; +} +static_assert(test_zero_size(), ""); + +constexpr bool test_struct_member() { + struct S { + int x; + int y; + }; + constexpr S s = {1, 2}; + __builtin_assume_dereferenceable(&s.x, 4); + return true; +} +static_assert(test_struct_member(), ""); + +constexpr bool test_range_valid() { + constexpr int range_data[5] = {1, 2, 3, 4, 5}; + __builtin_assume_dereferenceable(range_data, 5 * sizeof(int)); + return range_data[0] == 1; +} +static_assert(test_range_valid(), ""); + +constexpr bool test_range_invalid() { // expected-error {{constexpr function never produces a constant expression}} + constexpr int range_data[5] = {1, 2, 3, 4, 5}; + __builtin_assume_dereferenceable(range_data, 6 * sizeof(int)); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_range_invalid(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} + +constexpr int arr1[10] = {}; +constexpr int valid = (__builtin_assume_dereferenceable(arr1, 40), 12); + +constexpr int invalid = (__builtin_assume_dereferenceable((int*)123, 4), 12); // expected-error {{constexpr variable 'invalid' must be initialized by a constant expression}} expected-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} + +constexpr int arr2[5] = {1, 2, 3, 4, 5}; +constexpr int too_large = (__builtin_assume_dereferenceable(arr2, 6 * sizeof(int)), 12); // expected-error {{constexpr variable 'too_large' must be initialized by a constant expression}} expected-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} + +constexpr int null = (__builtin_assume_dereferenceable(nullptr, 4), 12); // expected-error {{constexpr variable 'null' must be initialized by a constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}}