https://github.com/chaitanyav updated https://github.com/llvm/llvm-project/pull/169869
>From 5ff6b2cfa50a819e56aad7c7c4f73b1a9c38e0df Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Thu, 27 Nov 2025 19:18:31 -0800 Subject: [PATCH 01/10] [Clang] Make __builtin_assume_dereferenceable constexpr Enable constant evaluation of __builtin_assume_dereferenceable. During evaluation, we verify the pointer is valid and the requested bytes are dereferenceable. Resolves:#168335 --- clang/include/clang/Basic/Builtins.td | 2 +- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 34 ++++++++++ clang/lib/AST/ExprConstant.cpp | 29 +++++++++ ...iltin-assume-dereferenceable-constexpr.cpp | 64 +++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index aab2418511399..0c67ce5a840d9 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 6170da63fbcaf..5c14d23e520d0 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2206,6 +2206,37 @@ 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.Stk, *S.Ctx.classify(Call->getArg(1))); + if (ReqSize.getZExtValue() < 1) + return false; + + const Pointer &Ptr = S.Stk.pop<Pointer>(); + if (Ptr.isZero() || !Ptr.isLive() || !Ptr.isBlockPointer() || Ptr.isPastEnd()) + return false; + + const ASTContext &ASTCtx = S.getASTContext(); + const Descriptor *DeclDesc = Ptr.getDeclDesc(); + std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, DeclDesc); + if (!FullSize) + return false; + + unsigned ByteOffset = computePointerOffset(ASTCtx, Ptr); + if (ByteOffset > *FullSize) + return false; + + unsigned RemainingSpace = *FullSize - ByteOffset; + if (RemainingSpace < ReqSize.getZExtValue()) + return false; + + return true; +} + /// Does Ptr point to the last subobject? static bool pointsToLastObject(const Pointer &Ptr) { Pointer P = Ptr; @@ -3997,6 +4028,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 4a04743f7c03e..01fccde944707 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20110,6 +20110,35 @@ 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; + LValue Pointer; + if (!EvaluatePointer(E->getArg(0), Pointer, Info)) + return false; + if (Pointer.Designator.Invalid) + return false; + if (Pointer.isNullPointer()) + return false; + + uint64_t ReqSize = ReqSizeVal.getZExtValue(); + if (ReqSize < 1) + return false; + CharUnits EndOffset; + if (!determineEndOffset(Info, E->getExprLoc(), 0, Pointer, EndOffset)) + return false; + + uint64_t TotalSize = + (EndOffset - Pointer.getLValueOffset()).getQuantity(); + if (TotalSize < ReqSize) { + return false; + } + return true; + } case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); 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..158eb78cbdabc --- /dev/null +++ b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -0,0 +1,64 @@ +// 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 int arr[10] = {}; + +constexpr bool test_constexpr_valid() { + __builtin_assume_dereferenceable(arr, 40); + return true; +} +static_assert(test_constexpr_valid(), ""); + +constexpr bool test_constexpr_partial() { + __builtin_assume_dereferenceable(&arr[5], 20); + return true; +} +static_assert(test_constexpr_partial(), ""); + +constexpr bool test_constexpr_nullptr() { + __builtin_assume_dereferenceable(nullptr, 4); + return true; +} +static_assert(test_constexpr_nullptr(), ""); // expected-error {{not an integral constant expression}} + +constexpr bool test_constexpr_too_large() { + __builtin_assume_dereferenceable(arr, 100); + return true; +} +static_assert(test_constexpr_too_large(), ""); // expected-error {{not an integral constant expression}} + +constexpr int single_var = 42; +constexpr bool test_single_var() { + __builtin_assume_dereferenceable(&single_var, 4); + return true; +} +static_assert(test_single_var(), ""); + +constexpr bool test_exact_boundary() { + __builtin_assume_dereferenceable(&arr[9], 4); + return true; +} +static_assert(test_exact_boundary(), ""); + +constexpr bool test_one_over() { + __builtin_assume_dereferenceable(&arr[9], 5); + return true; +} +static_assert(test_one_over(), ""); // expected-error {{not an integral constant expression}} + +constexpr bool test_zero_size() { + __builtin_assume_dereferenceable(arr, 0); + return true; +} +static_assert(test_zero_size(), ""); // expected-error {{not an integral constant expression}} + +struct S { + int x; + int y; +}; +constexpr S s = {1, 2}; +constexpr bool test_struct_member() { + __builtin_assume_dereferenceable(&s.x, 4); + return true; +} +static_assert(test_struct_member(), ""); >From 5505a87429bc3d646544cb0a4f7db92908da7dc3 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Fri, 28 Nov 2025 17:03:08 -0800 Subject: [PATCH 02/10] * Emit diagnostics when validation fails: null pointer, one-past-the-end, out-of-bounds access. * Enhance test suite to cover more scenarios * Return true when Size is zero. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 25 ++++++-- clang/lib/AST/ExprConstant.cpp | 18 ++++-- ...iltin-assume-dereferenceable-constexpr.cpp | 64 +++++++++++++------ 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 5c14d23e520d0..d696d623b001e 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2213,12 +2213,22 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, assert(Call->getNumArgs() == 2); APSInt ReqSize = popToAPSInt(S.Stk, *S.Ctx.classify(Call->getArg(1))); - if (ReqSize.getZExtValue() < 1) - return false; - const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (Ptr.isZero() || !Ptr.isLive() || !Ptr.isBlockPointer() || Ptr.isPastEnd()) + + 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.isLive() || !Ptr.isBlockPointer()) + 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(); @@ -2230,9 +2240,12 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, if (ByteOffset > *FullSize) return false; - unsigned RemainingSpace = *FullSize - ByteOffset; - if (RemainingSpace < ReqSize.getZExtValue()) + 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; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 01fccde944707..8b0c3a8934e2e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20117,24 +20117,32 @@ class VoidExprEvaluator 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)) return false; if (Pointer.Designator.Invalid) return false; - if (Pointer.isNullPointer()) + 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(); - if (ReqSize < 1) - return false; CharUnits EndOffset; if (!determineEndOffset(Info, E->getExprLoc(), 0, Pointer, EndOffset)) return false; - uint64_t TotalSize = + uint64_t AvailSize = (EndOffset - Pointer.getLValueOffset()).getQuantity(); - if (TotalSize < ReqSize) { + if (AvailSize < ReqSize) { + Info.FFDiag(E, diag::note_constexpr_access_past_end) << AK_Read; return false; } return true; diff --git a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp index 158eb78cbdabc..3819ae3b9c2f0 100644 --- a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -1,64 +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 int arr[10] = {}; - 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() { - __builtin_assume_dereferenceable(nullptr, 4); +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}} +static_assert(test_constexpr_nullptr(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} -constexpr bool test_constexpr_too_large() { - __builtin_assume_dereferenceable(arr, 100); +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}} +static_assert(test_constexpr_too_large(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} -constexpr int single_var = 42; 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() { - __builtin_assume_dereferenceable(&arr[9], 5); +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}} +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(), ""); // expected-error {{not an integral constant expression}} +static_assert(test_zero_size(), ""); -struct S { - int x; - int y; -}; -constexpr S s = {1, 2}; 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}} >From 532e8a8ff48a306445d2317e4887c906666c6434 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Sat, 29 Nov 2025 13:42:48 -0800 Subject: [PATCH 03/10] Allow casts like (char*)&b + 1 to passthrough for non-constexpr const initialization --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 9 ++++++-- clang/lib/AST/ExprConstant.cpp | 5 ++++- ...iltin-assume-dereferenceable-constexpr.cpp | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index d696d623b001e..e1ae012db959d 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2212,7 +2212,7 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, const CallExpr *Call) { assert(Call->getNumArgs() == 2); - APSInt ReqSize = popToAPSInt(S.Stk, *S.Ctx.classify(Call->getArg(1))); + APSInt ReqSize = popToAPSInt(S, Call->getArg(1)); const Pointer &Ptr = S.Stk.pop<Pointer>(); if (ReqSize.isZero()) @@ -2222,7 +2222,12 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, << AK_Read << S.Current->getRange(OpPC); return false; } - if (!Ptr.isLive() || !Ptr.isBlockPointer()) + 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) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 8b0c3a8934e2e..d2eed22b80837 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20121,8 +20121,11 @@ class VoidExprEvaluator return true; LValue Pointer; - if (!EvaluatePointer(E->getArg(0), Pointer, Info)) + 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()) { diff --git a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp index 3819ae3b9c2f0..6416a9a340195 100644 --- a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -90,3 +90,24 @@ 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}} + +int b = 10; +const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); +int a = f; + +int c[10] = {}; +const int g = (__builtin_assume_dereferenceable((unsigned char*)c + 5, 35), 42); +int d = g; + +long long ll = 100; +const int h = (__builtin_assume_dereferenceable((void*)&ll, 8), 99); +int e = h; + +struct Foo { int x; int y; int z; }; +Foo foo = {1, 2, 3}; +const int i = (__builtin_assume_dereferenceable((short*)&foo + 2, 8), 77); +int j = i; + +double darr[10] = {}; +const int k = (__builtin_assume_dereferenceable((int*)darr, 40), 55); +int l = k; >From d50b7f863ed01481e69683e412adc1924b80a58d Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Sat, 29 Nov 2025 23:06:27 -0800 Subject: [PATCH 04/10] Add codegen tests to verify constexpr passthrough for non-constexpr const initialization --- ...iltin-assume-dereferenceable-constexpr.cpp | 50 +++++++++++++++++++ ...iltin-assume-dereferenceable-constexpr.cpp | 25 +--------- 2 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp 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..4747d4c35eedb --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -0,0 +1,50 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -emit-llvm -o - %s | FileCheck %s + +constexpr int b = 10; +int test_char_cast() { + const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); + return f; +} + +constexpr long long ll = 100; +int test_void_cast() { + const int h = (__builtin_assume_dereferenceable((void*)&ll, 8), 99); + return h; +} + +constexpr int gb = 10; +const int gf = (__builtin_assume_dereferenceable((char*)&gb + 1, 3), 12); +int ga = gf; + +// CHECK-LABEL: test_char_castv +// CHECK: [[F:%.*]] = alloca i32, align 4 +// CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @_ZL1b, i64 1), i64 3) ] +// CHECK-NEXT: store i32 12, ptr [[F]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[F]], align 4 +// CHECK-NEXT: ret i32 [[TMP0]] +// +// +// CHECK-LABEL: test_void_castv +// CHECK: [[H:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 99, ptr [[H]], align 4 +// CHECK-NEXT: ret i32 99 +// +// +// CHECK-LABEL: __cxx_global_var_init +// CHECK: [[TMP0:%.*]] = load i32, ptr @_ZL2gf, align 4 +// CHECK-NEXT: store i32 [[TMP0]], ptr @ga, align 4 +// CHECK-NEXT: ret void +// +// +// CHECK-LABEL: __cxx_global_var_init.1 +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @_ZL2gb, i64 1), i64 3) ] +// CHECK-NEXT: store i32 12, ptr @_ZL2gf, align 4 +// CHECK-NEXT: ret void +// +// +// CHECK-LABEL: _GLOBAL__sub_I_builtin_assume_dereferenceable_constexpr.cpp +// CHECK: call void @__cxx_global_var_init.1() +// CHECK-NEXT: call void @__cxx_global_var_init() +// CHECK-NEXT: ret void +// diff --git a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp index 6416a9a340195..8a59f95b6e9dd 100644 --- a/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/SemaCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -58,8 +58,8 @@ static_assert(test_zero_size(), ""); constexpr bool test_struct_member() { struct S { - int x; - int y; + int x; + int y; }; constexpr S s = {1, 2}; __builtin_assume_dereferenceable(&s.x, 4); @@ -90,24 +90,3 @@ 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}} - -int b = 10; -const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); -int a = f; - -int c[10] = {}; -const int g = (__builtin_assume_dereferenceable((unsigned char*)c + 5, 35), 42); -int d = g; - -long long ll = 100; -const int h = (__builtin_assume_dereferenceable((void*)&ll, 8), 99); -int e = h; - -struct Foo { int x; int y; int z; }; -Foo foo = {1, 2, 3}; -const int i = (__builtin_assume_dereferenceable((short*)&foo + 2, 8), 77); -int j = i; - -double darr[10] = {}; -const int k = (__builtin_assume_dereferenceable((int*)darr, 40), 55); -int l = k; >From f52d1fa3d3e0710f0fe3c040f774f3bb01a750d3 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Sun, 30 Nov 2025 14:06:36 -0800 Subject: [PATCH 05/10] Update CodeGen tests for constexpr and global initialization scenarios --- ...iltin-assume-dereferenceable-constexpr.cpp | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp index 4747d4c35eedb..88e562e31ed26 100644 --- a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -1,50 +1,56 @@ -// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs // RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -emit-llvm -o - %s | FileCheck %s -constexpr int b = 10; -int test_char_cast() { - const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); - return f; +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; -constexpr long long ll = 100; -int test_void_cast() { - const int h = (__builtin_assume_dereferenceable((void*)&ll, 8), 99); - return h; +void test_integral_ptr() { + __builtin_assume_dereferenceable((int*)0x1234, 4); } -constexpr int gb = 10; -const int gf = (__builtin_assume_dereferenceable((char*)&gb + 1, 3), 12); -int ga = gf; +void test_nullptr() { + __builtin_assume_dereferenceable(nullptr, 0); +} -// CHECK-LABEL: test_char_castv -// CHECK: [[F:%.*]] = alloca i32, align 4 -// CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @_ZL1b, i64 1), i64 3) ] -// CHECK-NEXT: store i32 12, ptr [[F]], align 4 -// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[F]], align 4 -// CHECK-NEXT: ret i32 [[TMP0]] -// -// -// CHECK-LABEL: test_void_castv -// CHECK: [[H:%.*]] = alloca i32, align 4 -// CHECK-NEXT: store i32 99, ptr [[H]], align 4 -// CHECK-NEXT: ret i32 99 -// +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-LABEL: __cxx_global_var_init -// CHECK: [[TMP0:%.*]] = load i32, ptr @_ZL2gf, align 4 -// CHECK-NEXT: store i32 [[TMP0]], ptr @ga, align 4 -// CHECK-NEXT: ret void +// CHECK: @{{_Z[0-9]+}}test_integral_ptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] // +// CHECK: @{{_Z[0-9]+}}test_nullptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] // -// CHECK-LABEL: __cxx_global_var_init.1 -// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @_ZL2gb, i64 1), i64 3) ] -// CHECK-NEXT: store i32 12, ptr @_ZL2gf, align 4 -// CHECK-NEXT: ret void +// 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-LABEL: _GLOBAL__sub_I_builtin_assume_dereferenceable_constexpr.cpp -// CHECK: call void @__cxx_global_var_init.1() -// CHECK-NEXT: call void @__cxx_global_var_init() -// CHECK-NEXT: ret void +// 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 >From 7f30a0e565954fd46179342d60674cd235f240d1 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Mon, 1 Dec 2025 08:55:53 -0800 Subject: [PATCH 06/10] Add RUN line for new interpreter and remove invalid test case --- .../builtin-assume-dereferenceable-constexpr.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp index 88e562e31ed26..5f7942a72a2d7 100644 --- a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -1,4 +1,5 @@ // 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); @@ -17,10 +18,6 @@ constexpr int fully_constexpr() { constexpr int i = fully_constexpr(); int use_i = i; -void test_integral_ptr() { - __builtin_assume_dereferenceable((int*)0x1234, 4); -} - void test_nullptr() { __builtin_assume_dereferenceable(nullptr, 0); } @@ -36,9 +33,6 @@ void test_function_ptr() { // CHECK: @use_i = global i32 100 // -// CHECK: @{{_Z[0-9]+}}test_integral_ptrv -// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] -// // CHECK: @{{_Z[0-9]+}}test_nullptrv // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] // >From a265de20d6184e29c3941430fa8136cc71039f6d Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 9 Dec 2025 15:19:43 -0800 Subject: [PATCH 07/10] Reject integral pointers in bytecode interpreter to match the old interpreter --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 8 ++++++-- .../builtin-assume-dereferenceable-constexpr.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index e1ae012db959d..8087e0af45a6f 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2223,8 +2223,12 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, return false; } if (!Ptr.isBlockPointer()) { - if (Ptr.isIntegralPointer()) - return true; + if (Ptr.isIntegralPointer()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast) + << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret + << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC); + return false; + } return false; } if (!Ptr.isLive()) diff --git a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp index 5f7942a72a2d7..01731933a3475 100644 --- a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -18,6 +18,9 @@ constexpr int fully_constexpr() { constexpr int i = fully_constexpr(); int use_i = i; +const int j = (__builtin_assume_dereferenceable((int*)0x1234, 4), 200); +int use_j = j; + void test_nullptr() { __builtin_assume_dereferenceable(nullptr, 0); } @@ -31,7 +34,12 @@ void test_function_ptr() { __builtin_assume_dereferenceable((void*)&test_zero_size, 8); } +void test_integral_ptr() { + __builtin_assume_dereferenceable((int*)0x1234, 4); +} + // CHECK: @use_i = global i32 100 +// CHECK: @use_j = global i32 0 // // CHECK: @{{_Z[0-9]+}}test_nullptrv // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] @@ -42,6 +50,9 @@ void test_function_ptr() { // 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: @{{_Z[0-9]+}}test_integral_ptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] +// // CHECK: __cxx_global_var_init // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @b, i64 1), i64 3) ] // >From af73929095df02f58493281b318f8c2c70e3a827 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 10 Dec 2025 06:20:40 -0800 Subject: [PATCH 08/10] Do not emit diagnostic when its a integral pointer --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 8087e0af45a6f..a3fe94f20462d 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2223,12 +2223,8 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, return false; } if (!Ptr.isBlockPointer()) { - if (Ptr.isIntegralPointer()) { - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_cast) - << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret - << S.getLangOpts().CPlusPlus << S.Current->getRange(OpPC); + if (Ptr.isIntegralPointer()) return false; - } return false; } if (!Ptr.isLive()) >From 029d339134f3a3ea99ab37602b4212bba4c72fb1 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 10 Dec 2025 07:31:12 -0800 Subject: [PATCH 09/10] Remove the check for IntegralPointer since it is covered in BlockPointer check --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index a3fe94f20462d..c441619c360f0 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2222,11 +2222,8 @@ static bool interp__builtin_assume_dereferenceable(InterpState &S, CodePtr OpPC, << AK_Read << S.Current->getRange(OpPC); return false; } - if (!Ptr.isBlockPointer()) { - if (Ptr.isIntegralPointer()) - return false; + if (!Ptr.isBlockPointer()) return false; - } if (!Ptr.isLive()) return false; if (Ptr.isPastEnd()) { >From 8928be8237455c0e5ff3fec7b51e3e95b732c135 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 16 Dec 2025 09:15:52 -0800 Subject: [PATCH 10/10] Address code review comments * Move CHECK's to the place where the code is generated * Remove redundant if EvaluateBuiltinConstantP block --- clang/lib/AST/ExprConstant.cpp | 5 +-- ...iltin-assume-dereferenceable-constexpr.cpp | 36 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index d2eed22b80837..8b0c3a8934e2e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20121,11 +20121,8 @@ class VoidExprEvaluator return true; LValue Pointer; - if (!EvaluatePointer(E->getArg(0), Pointer, Info)) { - if (EvaluateBuiltinConstantP(Info, E->getArg(0))) - return true; + if (!EvaluatePointer(E->getArg(0), Pointer, Info)) return false; - } if (Pointer.Designator.Invalid) return false; if (Pointer.isNullPointer()) { diff --git a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp index 01731933a3475..11fa2a5279dcb 100644 --- a/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp +++ b/clang/test/CodeGenCXX/builtin-assume-dereferenceable-constexpr.cpp @@ -1,6 +1,8 @@ // 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 +// Global variables with __builtin_assume_dereferenceable in initializers. +// These generate __cxx_global_var_init functions (checked at end of file). int b = 10; const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); int use_f = f; @@ -21,41 +23,37 @@ int use_i = i; const int j = (__builtin_assume_dereferenceable((int*)0x1234, 4), 200); int use_j = j; +// CHECK-LABEL: @{{_Z[0-9]+}}test_nullptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] void test_nullptr() { __builtin_assume_dereferenceable(nullptr, 0); } +// CHECK-LABEL: @{{_Z[0-9]+}}test_zero_sizev +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %{{.*}}, i64 0) ] void test_zero_size() { int x = 10; __builtin_assume_dereferenceable(&x, 0); } +// CHECK-LABEL: @{{_Z[0-9]+}}test_function_ptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr @{{_Z[0-9]+}}test_zero_sizev, i64 8) ] void test_function_ptr() { __builtin_assume_dereferenceable((void*)&test_zero_size, 8); } +// CHECK-LABEL: @{{_Z[0-9]+}}test_integral_ptrv +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] void test_integral_ptr() { __builtin_assume_dereferenceable((int*)0x1234, 4); } -// CHECK: @use_i = global i32 100 -// CHECK: @use_j = global i32 0 -// -// 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: @{{_Z[0-9]+}}test_integral_ptrv -// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] -// -// CHECK: __cxx_global_var_init +// Global variable initialization checks for f, h, j above. // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @b, i64 1), i64 3) ] -// -// CHECK: __cxx_global_var_init +// CHECK: store i32 12, ptr @{{_ZL[0-9]+}}f, align 4 + // 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 +// CHECK: store i32 42, ptr @{{_ZL[0-9]+}}h, align 4 + +// CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] +// CHECK: store i32 200, ptr @{{_ZL[0-9]+}}j, align 4 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
