[clang] [Clang] Make __builtin_assume_dereferenceable constexpr (PR #169869)
NagaChaitanya Vellanki via cfe-commits
cfe-commits at lists.llvm.org
Tue Dec 16 09:19:12 PST 2025
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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
More information about the cfe-commits
mailing list