Skip to content

Commit 50d10b5

Browse files
authored
[Clang] Add __builtin_assume_dereferenceable to encode deref assumption. (#121789)
This patch adds a new __builtin_assume_dereferenceable to encode dereferenceability of a pointer using llvm.assume with an operand bundle. For now the builtin only accepts constant sizes, I am planning to drop this restriction in a follow-up change. This can be used to better optimize cases where a pointer is known to be dereferenceable, e.g. unconditionally loading from p2 when vectorizing the loop. int *get_ptr(); void foo(int* src, int x) { int *p2 = get_ptr(); __builtin_assume_aligned(p2, 4); __builtin_assume_dereferenceable(p2, 4000); for (unsigned I = 0; I != 1000; ++I) { int x = src[I]; if (x == 0) x = p2[I]; src[I] = x; } } PR: #121789
1 parent 0b49001 commit 50d10b5

File tree

7 files changed

+161
-0
lines changed

7 files changed

+161
-0
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2866,6 +2866,47 @@ etc.).
28662866
28672867
Query for this feature with ``__has_builtin(__builtin_assume_separate_storage)``.
28682868
2869+
``__builtin_assume_dereferenceable``
2870+
-------------------------------------
2871+
2872+
``__builtin_assume_dereferenceable`` is used to provide the optimizer with the
2873+
knowledge that the pointer argument P is dereferenceable up to at least the
2874+
specified number of bytes.
2875+
2876+
**Syntax**:
2877+
2878+
.. code-block:: c++
2879+
2880+
__builtin_assume_dereferenceable(const void *, size_t)
2881+
2882+
**Example of Use**:
2883+
2884+
.. code-block:: c++
2885+
2886+
int foo(int *x, int y) {
2887+
__builtin_assume_dereferenceable(x, sizeof(int));
2888+
int z = 0;
2889+
if (y == 1) {
2890+
// The optimizer may execute the load of x unconditionally due to
2891+
// __builtin_assume_dereferenceable guaranteeing sizeof(int) bytes can
2892+
// be loaded speculatively without trapping.
2893+
z = *x;
2894+
}
2895+
return z;
2896+
}
2897+
2898+
**Description**:
2899+
2900+
The arguments to this function provide a start pointer ``P`` and a size ``S``.
2901+
``S`` must be at least 1 and a constant. The optimizer may assume that ``S``
2902+
bytes are dereferenceable starting at ``P``. Note that this does not necessarily
2903+
imply that ``P`` is non-null as ``nullptr`` can be dereferenced in some cases.
2904+
The assumption also does not imply that ``P`` is not dereferenceable past ``S``
2905+
bytes.
2906+
2907+
2908+
Query for this feature with ``__has_builtin(__builtin_assume_dereferenceable)``.
2909+
28692910
28702911
``__builtin_offsetof``
28712912
----------------------

clang/include/clang/Basic/Builtins.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,12 @@ def BuiltinAssumeAligned : Builtin {
839839
let Prototype = "void*(void const*, size_t, ...)";
840840
}
841841

842+
def BuiltinAssumeDereferenceable : Builtin {
843+
let Spellings = ["__builtin_assume_dereferenceable"];
844+
let Attributes = [NoThrow, Const];
845+
let Prototype = "void(void const*, _Constant size_t)";
846+
}
847+
842848
def BuiltinFree : Builtin {
843849
let Spellings = ["__builtin_free"];
844850
let Attributes = [FunctionWithBuiltinPrefix, NoThrow];

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3839,6 +3839,17 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
38393839
AlignmentCI, OffsetValue);
38403840
return RValue::get(PtrValue);
38413841
}
3842+
case Builtin::BI__builtin_assume_dereferenceable: {
3843+
const Expr *Ptr = E->getArg(0);
3844+
const Expr *Size = E->getArg(1);
3845+
Value *PtrValue = EmitScalarExpr(Ptr);
3846+
Value *SizeValue = EmitScalarExpr(Size);
3847+
if (SizeValue->getType() != IntPtrTy)
3848+
SizeValue =
3849+
Builder.CreateIntCast(SizeValue, IntPtrTy, false, "casted.size");
3850+
Builder.CreateDereferenceableAssumption(PtrValue, SizeValue);
3851+
return RValue::get(nullptr);
3852+
}
38423853
case Builtin::BI__assume:
38433854
case Builtin::BI__builtin_assume: {
38443855
if (E->getArg(0)->HasSideEffects(getContext()))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
2+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
3+
4+
// CHECK-LABEL: @test1(
5+
// CHECK-NEXT: entry:
6+
// CHECK-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
7+
// CHECK-NEXT: store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
8+
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
9+
// CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 10) ]
10+
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
11+
// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
12+
// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
13+
// CHECK-NEXT: ret i32 [[TMP2]]
14+
//
15+
int test1(int *a) {
16+
__builtin_assume_dereferenceable(a, 10);
17+
return a[0];
18+
}
19+
20+
// CHECK-LABEL: @test2(
21+
// CHECK-NEXT: entry:
22+
// CHECK-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
23+
// CHECK-NEXT: store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
24+
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
25+
// CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 32) ]
26+
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
27+
// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
28+
// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
29+
// CHECK-NEXT: ret i32 [[TMP2]]
30+
//
31+
int test2(int *a) {
32+
__builtin_assume_dereferenceable(a, 32ull);
33+
return a[0];
34+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s
2+
// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s -fexperimental-new-constant-interpreter
3+
4+
5+
int test1(int *a) {
6+
__builtin_assume_dereferenceable(a, 32);
7+
return a[0];
8+
}
9+
10+
int test2(int *a) {
11+
__builtin_assume_dereferenceable(a, 32ull);
12+
return a[0];
13+
}
14+
15+
int test3(int *a) {
16+
__builtin_assume_dereferenceable(a, 32u);
17+
return a[0];
18+
}
19+
20+
int test4(int *a, unsigned size) {
21+
a = __builtin_assume_dereferenceable(a, size); // expected-error {{argument to '__builtin_assume_dereferenceable' must be a constant integer}}
22+
return a[0];
23+
}
24+
25+
int test5(int *a, unsigned long long size) {
26+
a = __builtin_assume_dereferenceable(a, size); // expected-error {{argument to '__builtin_assume_dereferenceable' must be a constant integer}}
27+
return a[0];
28+
}
29+
30+
int test6(float a) {
31+
__builtin_assume_dereferenceable(a, 2); // expected-error {{cannot initialize a parameter of type 'const void *' with an lvalue of type 'float'}}
32+
return 0;;
33+
}
34+
35+
int test7(int *a) {
36+
__builtin_assume_dereferenceable(a, 32, 1); // expected-error {{too many arguments to function call, expected 2, have 3}}
37+
return a[0];
38+
}
39+
40+
int test8(int *a) {
41+
__builtin_assume_dereferenceable(a); // expected-error {{too few arguments to function call, expected 2, have 1}}
42+
return a[0];
43+
}
44+
45+
int test9(int *a) {
46+
a[0] = __builtin_assume_dereferenceable(a, 32); // expected-error {{assigning to 'int' from incompatible type 'void'}}
47+
return a[0];
48+
}
49+
50+
constexpr int *p = 0;
51+
constexpr void *l = __builtin_assume_dereferenceable(p, 4); // expected-error {{cannot initialize a variable of type 'void *const' with an rvalue of type 'void'}}
52+
53+
void *foo() {
54+
return l;
55+
}

llvm/include/llvm/IR/IRBuilder.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,6 +2684,10 @@ class IRBuilderBase {
26842684
CallInst *CreateAlignmentAssumption(const DataLayout &DL, Value *PtrValue,
26852685
Value *Alignment,
26862686
Value *OffsetValue = nullptr);
2687+
2688+
/// Create an assume intrinsic call that represents an dereferencable
2689+
/// assumption on the provided pointer.
2690+
CallInst *CreateDereferenceableAssumption(Value *PtrValue, Value *SizeValue);
26872691
};
26882692

26892693
/// This provides a uniform API for creating instructions and inserting

llvm/lib/IR/IRBuilder.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,16 @@ CallInst *IRBuilderBase::CreateAlignmentAssumption(const DataLayout &DL,
12841284
return CreateAlignmentAssumptionHelper(DL, PtrValue, Alignment, OffsetValue);
12851285
}
12861286

1287+
CallInst *IRBuilderBase::CreateDereferenceableAssumption(Value *PtrValue,
1288+
Value *SizeValue) {
1289+
assert(isa<PointerType>(PtrValue->getType()) &&
1290+
"trying to create an deferenceable assumption on a non-pointer?");
1291+
SmallVector<Value *, 4> Vals({PtrValue, SizeValue});
1292+
OperandBundleDefT<Value *> DereferenceableOpB("dereferenceable", Vals);
1293+
return CreateAssumption(ConstantInt::getTrue(getContext()),
1294+
{DereferenceableOpB});
1295+
}
1296+
12871297
IRBuilderDefaultInserter::~IRBuilderDefaultInserter() = default;
12881298
IRBuilderCallbackInserter::~IRBuilderCallbackInserter() = default;
12891299
IRBuilderFolder::~IRBuilderFolder() = default;

0 commit comments

Comments
 (0)