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