https://github.com/pvelesko created 
https://github.com/llvm/llvm-project/pull/218642

`__builtin___clear_cache` crashes the frontend whenever the pointers passed to 
it are not in the target default address space.

```c
typedef char __attribute__((address_space(1))) as1_char;
void f(as1_char *begin, as1_char *end) {
  __builtin___clear_cache(begin, end);
}
```

```
$ clang -cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - t.c
clang: llvm/lib/IR/Instructions.cpp:781: void llvm::CallInst::init(FunctionType 
*, Value *, ArrayRef<Value *>, ArrayRef<OperandBundleDef>, const Twine &): 
Assertion `(i >= FTy->getNumParams() || FTy->getParamType(i) == 
Args[i]->getType()) && "Calling a function with a bad signature!"' failed.
```

The same assertion fires for every OpenCL translation unit that passes a 
pointer outside the target default address space, so `-cc1 -triple 
spirv64-unknown-unknown -x cl` dies on `__builtin___clear_cache(global_ptr, 
global_ptr)`. There is no diagnostic, only the crash backtrace.

Sema deliberately gives a builtin like this a declaration in the address space 
of the call site:

```
/// If a builtin function has a pointer argument with no explicit address
/// space, then it should be able to accept a pointer to any address
/// space as input.  In order to do this, we need to replace the
/// standard builtin declaration with one that uses the same address space
/// as the call.
```

(`rewriteBuiltinFunctionDecl`, `clang/lib/Sema/SemaExpr.cpp`)

CodeGen did not follow. It materialised the intrinsic with `CGM.DefaultPtrTy` 
while the emitted arguments carry the rewritten address spaces, so 
`CallInst::init` asserts on the signature mismatch. Since 3f2e24726a49 the 
intrinsic is

```
def int_clear_cache : Intrinsic<[], [llvm_anyptr_ty, LLVMMatchType<0>],
                                [], "llvm.clear_cache">;
```

so overloading it on the address space that is actually passed in is all that 
is needed. That is what the neighbouring `__builtin_prefetch` case already does 
with `CGM.getIntrinsic(Intrinsic::prefetch, Address->getType())`.

`LLVMMatchType<0>` forces both operands into one address space, while the Sema 
rewrite is per argument, so a call can reach CodeGen with a begin pointer and 
an end pointer in different address spaces. The two pointers delimit a single 
range, so the end pointer is cast into the address space of the begin pointer, 
and the `clear_cache_mixed` cases pin that down.

Address space 0 codegen is unchanged, which the untouched `main` check lines in 
`clang/test/CodeGen/clear_cache.c` show.

The first commit adds the tests, which crash on current `main`, and the second 
commit is the fix. The new tests need no registered target beyond the host, so 
they also run in builds without SPIR-V.

Testing: `ninja check-clang` on a `LLVM_TARGETS_TO_BUILD=X86` build of 
`32226ca72c8b`, 52544 discovered, 45299 passed, 7219 unsupported, 21 expectedly 
failed, 2 failed. The two failures are 
`clang/test/CodeGen/AArch64/abi-classify-arg-types.c` and 
`clang/test/CodeGen/AArch64/abi-classify-arg-types.cpp`, which still expect 
`nofree` where e6a74b56e4fe now emits `nofreeobj`. They fail identically 
without this change.

One note for the release branches: 21.x and 22.x assert the same way, including 
for HIP compiled to `spirv64`, but they still declare the intrinsic as 
`[llvm_ptr_ty, llvm_ptr_ty]`, so they need a different fix and this patch does 
not apply there as is.

This completes the stated intent of 3f2e24726a49, "[CHERI] Allow 
@llvm.clear_cache to accept pointers in address spaces other than 0. 
(#189283)". That commit made the intrinsic overloaded in Intrinsics.td, but 
CGBuiltin kept using `CGM.DefaultPtrTy`, so the Clang half never followed.

Two things deliberately left out of scope. The ClangIR path is untouched: 
`CIRGenBuiltin.cpp` bitcasts both pointers to `voidTy` with no address space 
handling, and the DirectToLLVM lowering still emits the unsuffixed 
`llvm.clear_cache` name. And `llvm/docs/LangRef.md` still documents `declare 
void @llvm.clear_cache(ptr, ptr)`, stale since the same commit. Both are worth 
follow ups.


>From 035548c577fd3b66495351613c2d62722b807d52 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Tue, 25 Aug 2026 11:17:41 +0300
Subject: [PATCH 1/2] [clang][CodeGen][test] Add __builtin___clear_cache
 address space coverage

Sema rewrites the declaration of a builtin that has an unqualified
pointer parameter so that the parameter takes the address space of the
argument at the call site.  Cover that for __builtin___clear_cache, in C
with __attribute__((address_space(N))) pointers and in OpenCL with
global, local and generic pointers, including a call whose two pointers
are in different address spaces.

Both tests currently crash the frontend with

  Assertion `(i >= FTy->getNumParams() || FTy->getParamType(i) ==
  Args[i]->getType()) && "Calling a function with a bad signature!"'
  failed.

because CGBuiltin declares @llvm.clear_cache in the target default
address space.
---
 clang/test/CodeGen/clear_cache.c        | 41 +++++++++++++++++++++++++
 clang/test/CodeGenOpenCL/clear_cache.cl | 37 ++++++++++++++++++++++
 2 files changed, 78 insertions(+)
 create mode 100644 clang/test/CodeGenOpenCL/clear_cache.cl

diff --git a/clang/test/CodeGen/clear_cache.c b/clang/test/CodeGen/clear_cache.c
index d07ed2f5fcf97..b265ecedef3f2 100644
--- a/clang/test/CodeGen/clear_cache.c
+++ b/clang/test/CodeGen/clear_cache.c
@@ -17,3 +17,44 @@ int main(void) {
   __builtin___clear_cache(buffer, buffer+32);
   return 0;
 }
+
+// The declaration of __builtin___clear_cache is rewritten to take the address
+// space of each pointer it is called with, so @llvm.clear_cache has to be
+// declared in the address space that is actually passed in rather than in the
+// target default one.
+
+typedef char __attribute__((address_space(1))) as1_char;
+typedef char __attribute__((address_space(2))) as2_char;
+
+// CHECK-LABEL: @clear_cache_as1(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[BEGIN_ADDR:%.*]] = alloca ptr addrspace(1), align 8
+// CHECK-NEXT:    [[END_ADDR:%.*]] = alloca ptr addrspace(1), align 8
+// CHECK-NEXT:    store ptr addrspace(1) [[BEGIN:%.*]], ptr [[BEGIN_ADDR]], 
align 8
+// CHECK-NEXT:    store ptr addrspace(1) [[END:%.*]], ptr [[END_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr addrspace(1), ptr [[BEGIN_ADDR]], 
align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr addrspace(1), ptr [[END_ADDR]], 
align 8
+// CHECK-NEXT:    call void @llvm.clear_cache.p1(ptr addrspace(1) [[TMP0]], 
ptr addrspace(1) [[TMP1]])
+// CHECK-NEXT:    ret void
+//
+void clear_cache_as1(as1_char *begin, as1_char *end) {
+  __builtin___clear_cache(begin, end);
+}
+
+// The two pointers delimit one range, so a call that mixes address spaces gets
+// the end pointer cast into the address space of the begin pointer.
+// CHECK-LABEL: @clear_cache_mixed(
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[BEGIN_ADDR:%.*]] = alloca ptr addrspace(1), align 8
+// CHECK-NEXT:    [[END_ADDR:%.*]] = alloca ptr addrspace(2), align 8
+// CHECK-NEXT:    store ptr addrspace(1) [[BEGIN:%.*]], ptr [[BEGIN_ADDR]], 
align 8
+// CHECK-NEXT:    store ptr addrspace(2) [[END:%.*]], ptr [[END_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr addrspace(1), ptr [[BEGIN_ADDR]], 
align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr addrspace(2), ptr [[END_ADDR]], 
align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = addrspacecast ptr addrspace(2) [[TMP1]] to 
ptr addrspace(1)
+// CHECK-NEXT:    call void @llvm.clear_cache.p1(ptr addrspace(1) [[TMP0]], 
ptr addrspace(1) [[TMP2]])
+// CHECK-NEXT:    ret void
+//
+void clear_cache_mixed(as1_char *begin, as2_char *end) {
+  __builtin___clear_cache(begin, end);
+}
diff --git a/clang/test/CodeGenOpenCL/clear_cache.cl 
b/clang/test/CodeGenOpenCL/clear_cache.cl
new file mode 100644
index 0000000000000..4cb26c76e3a8f
--- /dev/null
+++ b/clang/test/CodeGenOpenCL/clear_cache.cl
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -triple spirv64-unknown-unknown -cl-std=CL2.0 
-disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -triple spirv32-unknown-unknown -cl-std=CL2.0 
-disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
+
+// The declaration of __builtin___clear_cache is rewritten to take the address
+// space of each pointer it is called with, and in OpenCL that is almost never
+// the target default address space, so @llvm.clear_cache has to be declared in
+// the address space that is actually passed in.
+
+// CHECK-LABEL: define spir_func void @clear_cache_global(
+// CHECK:         call void @llvm.clear_cache.p1(ptr addrspace(1) %{{.*}}, ptr 
addrspace(1) %{{.*}})
+// CHECK:       declare void @llvm.clear_cache.p1(ptr addrspace(1), ptr 
addrspace(1))
+void clear_cache_global(global char *begin, global char *end) {
+  __builtin___clear_cache(begin, end);
+}
+
+// CHECK-LABEL: define spir_func void @clear_cache_local(
+// CHECK:         call void @llvm.clear_cache.p3(ptr addrspace(3) %{{.*}}, ptr 
addrspace(3) %{{.*}})
+// CHECK:       declare void @llvm.clear_cache.p3(ptr addrspace(3), ptr 
addrspace(3))
+void clear_cache_local(local char *begin, local char *end) {
+  __builtin___clear_cache(begin, end);
+}
+
+// CHECK-LABEL: define spir_func void @clear_cache_generic(
+// CHECK:         call void @llvm.clear_cache.p4(ptr addrspace(4) %{{.*}}, ptr 
addrspace(4) %{{.*}})
+// CHECK:       declare void @llvm.clear_cache.p4(ptr addrspace(4), ptr 
addrspace(4))
+void clear_cache_generic(generic char *begin, generic char *end) {
+  __builtin___clear_cache(begin, end);
+}
+
+// The two pointers delimit one range, so a call that mixes address spaces gets
+// the end pointer cast into the address space of the begin pointer.
+// CHECK-LABEL: define spir_func void @clear_cache_mixed(
+// CHECK:         [[CAST:%.*]] = addrspacecast ptr addrspace(3) %{{.*}} to ptr 
addrspace(1)
+// CHECK:         call void @llvm.clear_cache.p1(ptr addrspace(1) %{{.*}}, ptr 
addrspace(1) [[CAST]])
+void clear_cache_mixed(global char *begin, local char *end) {
+  __builtin___clear_cache(begin, end);
+}

>From 8b68c31e92d610e2f866cf7f8a7ec650fc672942 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Tue, 25 Aug 2026 11:27:18 +0300
Subject: [PATCH 2/2] [clang][CodeGen] Overload llvm.clear_cache on the
 argument address space

Sema replaces the declaration of a builtin that has an unqualified
pointer parameter with one that uses the address space of the argument
at the call site, so the arguments emitted for __builtin___clear_cache
can be in any address space.  CGBuiltin declared @llvm.clear_cache in
the target default address space regardless, which made CallInst::init
assert on the signature mismatch in every C translation unit that passes
__attribute__((address_space(N))) pointers and in every OpenCL
translation unit that passes anything but a private pointer.

Overload the intrinsic on the address space that is actually passed in,
which is what the neighbouring __builtin_prefetch case already does.
The two pointers delimit a single range and llvm.clear_cache takes
LLVMMatchType<0> for its second operand, so the end pointer is brought
into the address space of the begin pointer when the rewritten
declaration gave them different ones.

Address space 0 codegen is unchanged.
---
 clang/lib/CodeGen/CGBuiltin.cpp | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 07379056f2f0a..ee7f0333a30f1 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4118,7 +4118,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl 
GD, unsigned BuiltinID,
   case Builtin::BI__builtin___clear_cache: {
     Value *Begin = EmitScalarExpr(E->getArg(0));
     Value *End = EmitScalarExpr(E->getArg(1));
-    Function *F = CGM.getIntrinsic(Intrinsic::clear_cache, {CGM.DefaultPtrTy});
+    // The declaration of __builtin___clear_cache is rewritten to take the
+    // address spaces of the pointers it is called with, which need not be the
+    // target's default address space, so overload the intrinsic on the address
+    // space that is actually passed in. The two pointers delimit a single
+    // range, so bring the end pointer into the address space of the begin
+    // pointer if the rewritten declaration gave them different ones.
+    llvm::Type *PtrTy = Begin->getType();
+    End = Builder.CreatePointerBitCastOrAddrSpaceCast(End, PtrTy);
+    Function *F = CGM.getIntrinsic(Intrinsic::clear_cache, {PtrTy});
     return RValue::get(Builder.CreateCall(F, {Begin, End}));
   }
   case Builtin::BI__builtin_trap:

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to