[clang] [llvm] [Clang] Add __builtin_assume_dereferenceable to encode deref assumption. (PR #121789)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Wed Jan 8 05:15:31 PST 2025


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/121789

>From 596d1346eb806e4c276042e50cca7514d3335f54 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 6 Jan 2025 13:39:55 +0000
Subject: [PATCH 1/3] [Clang] Add __builtin_assume_dereferenceable to encode
 deref assumption.

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;
      }
    }
---
 clang/docs/LanguageExtensions.rst             | 35 ++++++++++++++++
 clang/include/clang/Basic/Builtins.td         |  6 +++
 clang/lib/CodeGen/CGBuiltin.cpp               | 10 +++++
 .../CodeGen/builtin-assume-dereferenceable.c  | 34 +++++++++++++++
 .../Sema/builtin-assume-dereferenceable.c     | 41 +++++++++++++++++++
 llvm/include/llvm/IR/IRBuilder.h              |  5 +++
 llvm/lib/IR/IRBuilder.cpp                     | 10 +++++
 7 files changed, 141 insertions(+)
 create mode 100644 clang/test/CodeGen/builtin-assume-dereferenceable.c
 create mode 100644 clang/test/Sema/builtin-assume-dereferenceable.c

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index e020710c7aa4f5..1a96647ae765e2 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -2761,6 +2761,41 @@ etc.).
 
 Query for this feature with ``__has_builtin(__builtin_assume_separate_storage)``.
 
+``__builtin_assume_dereferenceable``
+-------------------------------------
+
+``__builtin_assume_derefernceable`` is used to provide the optimizer with the
+knowledge that the pointer argument P is dereferenceable up to the specified
+number of bytes.
+
+**Syntax**:
+
+.. code-block:: c++
+
+    __builtin_assume_dereferenceable(const void *, size_t)
+
+**Example of Use**:
+
+.. code-block:: c++
+
+  int foo(int *x, int y) {
+      __builtin_assume_dereferenceable(x, 4);
+      int z = 0;
+      if (y == 1) {
+        // The optimizer may execute the load of x unconditionally.
+        z = *x;
+        }
+      return z;
+  }
+
+**Description**:
+
+The arguments to this function provide a start pointer ``P`` and a size ``S``.
+``P`` must be non-null and ``S`` at least 1. The optimizer may assume that
+``S`` bytes are dereferenceable starting at ``P``.
+
+Query for this feature with ``__has_builtin(__builtin_assume_dereferenceable)``.
+
 
 ``__builtin_offsetof``
 ----------------------
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 468c16050e2bf0..e9d8c6f621afa9 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -839,6 +839,12 @@ def BuiltinAssumeAligned : Builtin {
   let Prototype = "void*(void const*, size_t, ...)";
 }
 
+def BuiltinAssumeDereferenceable : Builtin {
+  let Spellings = ["__builtin_assume_dereferenceable"];
+  let Attributes = [NoThrow, Const, Constexpr];
+  let Prototype = "void(void const*, _Constant size_t)";
+}
+
 def BuiltinFree : Builtin {
   let Spellings = ["__builtin_free"];
   let Attributes = [FunctionWithBuiltinPrefix, NoThrow];
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index dcea32969fb990..03862b89e05950 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3726,6 +3726,16 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
                             AlignmentCI, OffsetValue);
     return RValue::get(PtrValue);
   }
+  case Builtin::BI__builtin_assume_dereferenceable: {
+    const Expr *Ptr = E->getArg(0);
+    Value *PtrValue = EmitScalarExpr(Ptr);
+    Value *SizeValue = EmitScalarExpr(E->getArg(1));
+    if (SizeValue->getType() != IntPtrTy)
+      SizeValue =
+          Builder.CreateIntCast(SizeValue, IntPtrTy, false, "casted.size");
+    Builder.CreateDereferenceableAssumption(PtrValue, SizeValue);
+    return RValue::get(nullptr);
+  }
   case Builtin::BI__assume:
   case Builtin::BI__builtin_assume: {
     if (E->getArg(0)->HasSideEffects(getContext()))
diff --git a/clang/test/CodeGen/builtin-assume-dereferenceable.c b/clang/test/CodeGen/builtin-assume-dereferenceable.c
new file mode 100644
index 00000000000000..cadffd4a84c264
--- /dev/null
+++ b/clang/test/CodeGen/builtin-assume-dereferenceable.c
@@ -0,0 +1,34 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
+
+// CHECK-LABEL: @test1(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 10) ]
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
+// CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT:    ret i32 [[TMP2]]
+//
+int test1(int *a) {
+  __builtin_assume_dereferenceable(a, 10);
+  return a[0];
+}
+
+// CHECK-LABEL: @test2(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 32) ]
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
+// CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+// CHECK-NEXT:    ret i32 [[TMP2]]
+//
+int test2(int *a) {
+  __builtin_assume_dereferenceable(a, 32ull);
+  return a[0];
+}
diff --git a/clang/test/Sema/builtin-assume-dereferenceable.c b/clang/test/Sema/builtin-assume-dereferenceable.c
new file mode 100644
index 00000000000000..07b23022043cd7
--- /dev/null
+++ b/clang/test/Sema/builtin-assume-dereferenceable.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -Wno-strict-prototypes -triple x86_64-linux -verify %s
+
+int test1(int *a) {
+  __builtin_assume_dereferenceable(a, 32);
+  return a[0];
+}
+
+int test2(int *a) {
+  __builtin_assume_dereferenceable(a, 32ull);
+  return a[0];
+}
+
+int test3(int *a) {
+  __builtin_assume_dereferenceable(a, 32u);
+  return a[0];
+}
+
+int test4(int *a, unsigned size) {
+  a = __builtin_assume_dereferenceable(a, size); // expected-error {{argument to '__builtin_assume_dereferenceable' must be a constant integer}}
+  return a[0];
+}
+
+int test5(int *a, unsigned long long size) {
+  a = __builtin_assume_dereferenceable(a, size); // expected-error {{argument to '__builtin_assume_dereferenceable' must be a constant integer}}
+  return a[0];
+}
+
+int test6(float a) {
+  __builtin_assume_dereferenceable(a, 2); // expected-error {{passing 'float' to parameter of incompatible type 'const void *'}}
+  return 0;;
+}
+
+int test7(int *a) {
+  __builtin_assume_dereferenceable(a, 32, 1); // expected-error {{too many arguments to function call, expected 2, have 3}}
+  return a[0];
+}
+
+int test8(int *a) {
+  __builtin_assume_dereferenceable(a); // expected-error {{too few arguments to function call, expected 2, have 1}}
+  return a[0];
+}
diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h
index b73309175f20d1..4da303d28e3d7b 100644
--- a/llvm/include/llvm/IR/IRBuilder.h
+++ b/llvm/include/llvm/IR/IRBuilder.h
@@ -2678,6 +2678,11 @@ class IRBuilderBase {
   CallInst *CreateAlignmentAssumption(const DataLayout &DL, Value *PtrValue,
                                       Value *Alignment,
                                       Value *OffsetValue = nullptr);
+
+  /// Create an assume intrinsic call that represents an dereferencable
+  /// assumption on the provided pointer.
+  ///
+  CallInst *CreateDereferenceableAssumption(Value *PtrValue, Value *SizeValue);
 };
 
 /// This provides a uniform API for creating instructions and inserting
diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp
index 27b499e42a4e4c..87636a33ee044b 100644
--- a/llvm/lib/IR/IRBuilder.cpp
+++ b/llvm/lib/IR/IRBuilder.cpp
@@ -1274,6 +1274,16 @@ CallInst *IRBuilderBase::CreateAlignmentAssumption(const DataLayout &DL,
   return CreateAlignmentAssumptionHelper(DL, PtrValue, Alignment, OffsetValue);
 }
 
+CallInst *IRBuilderBase::CreateDereferenceableAssumption(Value *PtrValue,
+                                                         Value *SizeValue) {
+  assert(isa<PointerType>(PtrValue->getType()) &&
+         "trying to create an deferenceable assumption on a non-pointer?");
+  SmallVector<Value *, 4> Vals({PtrValue, SizeValue});
+  OperandBundleDefT<Value *> DereferenceableOpB("dereferenceable", Vals);
+  return CreateAssumption(ConstantInt::getTrue(getContext()),
+                          {DereferenceableOpB});
+}
+
 IRBuilderDefaultInserter::~IRBuilderDefaultInserter() = default;
 IRBuilderCallbackInserter::~IRBuilderCallbackInserter() = default;
 IRBuilderFolder::~IRBuilderFolder() = default;

>From 0711638af02e7680d35ac7647e96e5778e921320 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 8 Jan 2025 12:13:27 +0000
Subject: [PATCH 2/3] !fixup address latest comments, thanks!

---
 clang/include/clang/Basic/Builtins.td          |  2 +-
 clang/lib/CodeGen/CGBuiltin.cpp                |  4 +++-
 .../CodeGen/builtin-assume-dereferenceable.c   |  2 ++
 .../builtin-assume-dereferenceable.cpp}        | 18 ++++++++++++++++--
 llvm/include/llvm/IR/IRBuilder.h               |  5 ++++-
 llvm/lib/IR/IRBuilder.cpp                      |  8 ++++++++
 6 files changed, 34 insertions(+), 5 deletions(-)
 rename clang/test/{Sema/builtin-assume-dereferenceable.c => SemaCXX/builtin-assume-dereferenceable.cpp} (57%)

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index e9d8c6f621afa9..9f95249c221b01 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -841,7 +841,7 @@ def BuiltinAssumeAligned : Builtin {
 
 def BuiltinAssumeDereferenceable : Builtin {
   let Spellings = ["__builtin_assume_dereferenceable"];
-  let Attributes = [NoThrow, Const, Constexpr];
+  let Attributes = [NoThrow, Const];
   let Prototype = "void(void const*, _Constant size_t)";
 }
 
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 03862b89e05950..7ed3795e90f23c 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3728,12 +3728,14 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   }
   case Builtin::BI__builtin_assume_dereferenceable: {
     const Expr *Ptr = E->getArg(0);
+    const Expr *Size= E->getArg(1);
     Value *PtrValue = EmitScalarExpr(Ptr);
-    Value *SizeValue = EmitScalarExpr(E->getArg(1));
+    Value *SizeValue = EmitScalarExpr(Size);
     if (SizeValue->getType() != IntPtrTy)
       SizeValue =
           Builder.CreateIntCast(SizeValue, IntPtrTy, false, "casted.size");
     Builder.CreateDereferenceableAssumption(PtrValue, SizeValue);
+    Builder.CreateNonNullAssumption(PtrValue);
     return RValue::get(nullptr);
   }
   case Builtin::BI__assume:
diff --git a/clang/test/CodeGen/builtin-assume-dereferenceable.c b/clang/test/CodeGen/builtin-assume-dereferenceable.c
index cadffd4a84c264..bf632f6e101820 100644
--- a/clang/test/CodeGen/builtin-assume-dereferenceable.c
+++ b/clang/test/CodeGen/builtin-assume-dereferenceable.c
@@ -7,6 +7,7 @@
 // CHECK-NEXT:    store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 10) ]
+// CHECK-NEXT:    call void @llvm.assume(i1 true) [ "nonnull"(ptr [[TMP0]]) ]
 // CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
 // CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
@@ -23,6 +24,7 @@ int test1(int *a) {
 // CHECK-NEXT:    store ptr [[A:%.*]], ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0]], i64 32) ]
+// CHECK-NEXT:    call void @llvm.assume(i1 true) [ "nonnull"(ptr [[TMP0]]) ]
 // CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
 // CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i64 0
 // CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
diff --git a/clang/test/Sema/builtin-assume-dereferenceable.c b/clang/test/SemaCXX/builtin-assume-dereferenceable.cpp
similarity index 57%
rename from clang/test/Sema/builtin-assume-dereferenceable.c
rename to clang/test/SemaCXX/builtin-assume-dereferenceable.cpp
index 07b23022043cd7..b79b7c059567e1 100644
--- a/clang/test/Sema/builtin-assume-dereferenceable.c
+++ b/clang/test/SemaCXX/builtin-assume-dereferenceable.cpp
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -Wno-strict-prototypes -triple x86_64-linux -verify %s
+// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s
+// RUN: %clang_cc1 -DSIZE_T_64 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s -fexperimental-new-constant-interpreter
+
 
 int test1(int *a) {
   __builtin_assume_dereferenceable(a, 32);
@@ -26,7 +28,7 @@ int test5(int *a, unsigned long long size) {
 }
 
 int test6(float a) {
-  __builtin_assume_dereferenceable(a, 2); // expected-error {{passing 'float' to parameter of incompatible type 'const void *'}}
+  __builtin_assume_dereferenceable(a, 2); // expected-error {{cannot initialize a parameter of type 'const void *' with an lvalue of type 'float'}}
   return 0;;
 }
 
@@ -39,3 +41,15 @@ int test8(int *a) {
   __builtin_assume_dereferenceable(a); // expected-error {{too few arguments to function call, expected 2, have 1}}
   return a[0];
 }
+
+int test9(int *a) {
+  a[0] = __builtin_assume_dereferenceable(a, 32); // expected-error {{assigning to 'int' from incompatible type 'void'}}
+  return a[0];
+}
+
+constexpr int *p = 0;
+constexpr void *l = __builtin_assume_dereferenceable(p, 4); // expected-error {{cannot initialize a variable of type 'void *const' with an rvalue of type 'void'}}
+
+void *foo() {
+  return l;
+}
diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h
index 4da303d28e3d7b..18fe37113dc0b5 100644
--- a/llvm/include/llvm/IR/IRBuilder.h
+++ b/llvm/include/llvm/IR/IRBuilder.h
@@ -2681,8 +2681,11 @@ class IRBuilderBase {
 
   /// Create an assume intrinsic call that represents an dereferencable
   /// assumption on the provided pointer.
-  ///
   CallInst *CreateDereferenceableAssumption(Value *PtrValue, Value *SizeValue);
+
+  /// Create an assume intrinsic call that represents a nonnull assumption
+  /// on the provided pointer.
+  CallInst *CreateNonNullAssumption(Value *PtrValue);
 };
 
 /// This provides a uniform API for creating instructions and inserting
diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp
index 87636a33ee044b..4735e9ab5025e9 100644
--- a/llvm/lib/IR/IRBuilder.cpp
+++ b/llvm/lib/IR/IRBuilder.cpp
@@ -1284,6 +1284,14 @@ CallInst *IRBuilderBase::CreateDereferenceableAssumption(Value *PtrValue,
                           {DereferenceableOpB});
 }
 
+CallInst *IRBuilderBase::CreateNonNullAssumption(Value *PtrValue) {
+  assert(isa<PointerType>(PtrValue->getType()) &&
+         "trying to create an nonnull assumption on a non-pointer?");
+  SmallVector<Value *, 4> Vals({PtrValue});
+  OperandBundleDefT<Value *> NonNullOpB("nonnull", Vals);
+  return CreateAssumption(ConstantInt::getTrue(getContext()), {NonNullOpB});
+}
+
 IRBuilderDefaultInserter::~IRBuilderDefaultInserter() = default;
 IRBuilderCallbackInserter::~IRBuilderCallbackInserter() = default;
 IRBuilderFolder::~IRBuilderFolder() = default;

>From 148add91010e0459099d969cdd666b24bba43cd3 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 8 Jan 2025 13:15:07 +0000
Subject: [PATCH 3/3] !fixup fix formatting

---
 clang/lib/CodeGen/CGBuiltin.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 7ed3795e90f23c..cb59d191c08333 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3728,7 +3728,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   }
   case Builtin::BI__builtin_assume_dereferenceable: {
     const Expr *Ptr = E->getArg(0);
-    const Expr *Size= E->getArg(1);
+    const Expr *Size = E->getArg(1);
     Value *PtrValue = EmitScalarExpr(Ptr);
     Value *SizeValue = EmitScalarExpr(Size);
     if (SizeValue->getType() != IntPtrTy)



More information about the llvm-commits mailing list