[clang] [CIR][SPIR-V] Emit non-generic null pointers via generic null cast (PR #228559)
Arseniy Obolenskiy via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 6 00:01:43 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/228559
>From 33f1e50a78020ddd948afe954eaf9842057f7e33 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 2 Oct 2026 21:30:41 +0200
Subject: [PATCH 1/2] [CIR][SPIR-V] Emit non-generic null pointers via generic
null cast
Match classic codegen so targets with nonzero null in non-generic address spaces can lower null correctly.
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 5 ++
clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 10 ++-
clang/lib/CIR/CodeGen/CIRGenModule.h | 4 +
clang/lib/CIR/CodeGen/TargetInfo.cpp | 7 ++
clang/lib/CIR/CodeGen/TargetInfo.h | 5 ++
clang/lib/CIR/CodeGen/Targets/SPIRV.cpp | 31 +++++++
clang/test/CIR/CodeGenOpenCL/null-pointer.cl | 93 ++++++++++++++++++++
7 files changed, 151 insertions(+), 4 deletions(-)
create mode 100644 clang/test/CIR/CodeGenOpenCL/null-pointer.cl
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 5ca19bab96119..d749c85462064 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -1597,6 +1597,11 @@ mlir::Value CIRGenModule::emitNullConstant(QualType t, mlir::Location loc) {
return builder.getConstant(loc, emitNullConstantAttr(t));
}
+mlir::Value CIRGenModule::getNullPointer(cir::PointerType ptrTy, QualType qt,
+ mlir::Location loc) {
+ return getTargetCIRGenInfo().getNullPointer(*this, ptrTy, qt, loc);
+}
+
mlir::TypedAttr CIRGenModule::emitNullConstantAttr(QualType t) {
if (t->getAs<PointerType>())
return builder.getConstNullPtrAttr(getTypes().convertTypeForMem(t));
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 6cae1200e6e23..ec13233dd217e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -2645,8 +2645,9 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) {
// eliminate the useless instructions emitted during translating E.
if (result.HasSideEffects)
Visit(subExpr);
- return cgf.cgm.emitNullConstant(destTy,
- cgf.getLoc(subExpr->getExprLoc()));
+ return cgf.cgm.getNullPointer(
+ mlir::cast<cir::PointerType>(convertType(destTy)), destTy,
+ cgf.getLoc(subExpr->getExprLoc()));
}
return cgf.performAddrSpaceCast(Visit(subExpr), convertType(destTy));
}
@@ -2716,8 +2717,9 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) {
// Note that DestTy is used as the MLIR type instead of a custom
// nullptr type.
- mlir::Type ty = cgf.convertType(destTy);
- return builder.getNullPtr(ty, cgf.getLoc(subExpr->getExprLoc()));
+ auto ty = mlir::cast<cir::PointerType>(cgf.convertType(destTy));
+ return cgf.cgm.getNullPointer(ty, destTy,
+ cgf.getLoc(subExpr->getExprLoc()));
}
case CK_NullToMemberPointer: {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index efe0e5525dc1d..d17dcb472f307 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -731,6 +731,10 @@ class CIRGenModule : public CIRGenTypeCache {
mlir::TypedAttr emitNullConstantAttr(QualType t);
+ /// Get target specific null pointer.
+ mlir::Value getNullPointer(cir::PointerType ptrTy, QualType qt,
+ mlir::Location loc);
+
/// Return a null constant appropriate for zero-initializing a base class with
/// the given type. This is usually, but not always, an LLVM null constant.
mlir::TypedAttr emitNullConstantForBase(const CXXRecordDecl *record);
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.cpp b/clang/lib/CIR/CodeGen/TargetInfo.cpp
index cede4941b4473..7a41cbeecc7e7 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.cpp
+++ b/clang/lib/CIR/CodeGen/TargetInfo.cpp
@@ -214,6 +214,13 @@ cir::CallingConv TargetCIRGenInfo::getDeviceKernelCallingConv() const {
return cir::CallingConv::C;
}
+mlir::Value TargetCIRGenInfo::getNullPointer(CIRGenModule &cgm,
+ cir::PointerType ptrTy,
+ QualType qt,
+ mlir::Location loc) const {
+ return cgm.getBuilder().getNullPtr(ptrTy, loc);
+}
+
clang::LangAS
TargetCIRGenInfo::getGlobalVarAddressSpace(CIRGenModule &cgm,
const clang::VarDecl *d) const {
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h
index 5f380026e025d..5313a27c5e2f9 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.h
+++ b/clang/lib/CIR/CodeGen/TargetInfo.h
@@ -80,6 +80,11 @@ class TargetCIRGenInfo {
cir::LangAddressSpace::Default);
}
+ /// Get the CIR value of a null pointer of type \p ptrTy, where \p qt is the
+ /// source pointer type.
+ virtual mlir::Value getNullPointer(CIRGenModule &cgm, cir::PointerType ptrTy,
+ QualType qt, mlir::Location loc) const;
+
virtual mlir::Type getCUDADeviceBuiltinSurfaceDeviceType() const {
return nullptr;
}
diff --git a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
index 4d19f122799e3..8129f0ddb752e 100644
--- a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
+++ b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
@@ -54,10 +54,41 @@ class CommonSPIRTargetCIRGenInfo : public TargetCIRGenInfo {
ft = getABIInfo().cgt.getASTContext().adjustFunctionType(
ft, ft->getExtInfo().withCallingConv(CC_DeviceKernel));
}
+
+ mlir::Value getNullPointer(CIRGenModule &cgm, cir::PointerType ptrTy,
+ QualType qt, mlir::Location loc) const override;
};
} // namespace
+// The bit pattern of null in non-generic AS is unspecified for SPIR(-V), so
+// materialize it via an address space cast from null in generic AS.
+mlir::Value
+CommonSPIRTargetCIRGenInfo::getNullPointer(CIRGenModule &cgm,
+ cir::PointerType ptrTy, QualType qt,
+ mlir::Location loc) const {
+ LangAS as = qt->getUnqualifiedDesugaredType()->isNullPtrType()
+ ? LangAS::Default
+ : qt->getPointeeType().getAddressSpace();
+ unsigned asAsInt = static_cast<unsigned>(as);
+ unsigned firstTargetASAsInt =
+ static_cast<unsigned>(LangAS::FirstTargetAddressSpace);
+ unsigned codeSectionINTELAS = firstTargetASAsInt + 9;
+ // As per SPV_INTEL_function_pointers, it is illegal to addrspacecast
+ // function pointers to/from the generic AS.
+ bool isFunctionPtrAS =
+ cgm.getTriple().isSPIRV() && asAsInt == codeSectionINTELAS;
+ CIRGenBuilderTy &builder = cgm.getBuilder();
+ if (as == LangAS::Default || as == LangAS::opencl_generic ||
+ as == LangAS::opencl_constant || isFunctionPtrAS)
+ return builder.getNullPtr(ptrTy, loc);
+
+ cir::PointerType genericPtrTy =
+ builder.getPointerTo(ptrTy.getPointee(), LangAS::opencl_generic);
+ return builder.createAddrSpaceCast(loc, builder.getNullPtr(genericPtrTy, loc),
+ ptrTy);
+}
+
std::unique_ptr<TargetCIRGenInfo>
clang::CIRGen::createCommonSPIRTargetCIRGenInfo(CIRGenTypes &cgt) {
return std::make_unique<CommonSPIRTargetCIRGenInfo>(cgt);
diff --git a/clang/test/CIR/CodeGenOpenCL/null-pointer.cl b/clang/test/CIR/CodeGenOpenCL/null-pointer.cl
new file mode 100644
index 0000000000000..ed2769727e1b8
--- /dev/null
+++ b/clang/test/CIR/CodeGenOpenCL/null-pointer.cl
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -triple spirv64-unknown-unknown -cl-std=CL2.0 -fclangir \
+// RUN: -emit-cir -mmlir --mlir-print-ir-before=cir-target-lowering \
+// RUN: %s -o %t.cir 2> %t.pre.cir
+// RUN: FileCheck %s --check-prefix=CIR --input-file=%t.pre.cir
+// RUN: %clang_cc1 -triple spirv64-unknown-unknown -cl-std=CL2.0 -fclangir \
+// RUN: -emit-llvm -O0 %s -o %t-cir.ll
+// RUN: FileCheck %s --check-prefix=LLVM --input-file=%t-cir.ll
+// RUN: %clang_cc1 -triple spirv64-unknown-unknown -cl-std=CL2.0 \
+// RUN: -emit-llvm -O0 %s -o %t.ll
+// RUN: FileCheck %s --check-prefix=LLVM --input-file=%t.ll
+
+#define NULL ((void *)0)
+
+void test_storage(private char **arg_private, local char **arg_local,
+ global char **arg_global, constant char **arg_constant,
+ generic char **arg_generic) {
+ *arg_private = 0;
+ *arg_local = 0;
+ *arg_global = 0;
+ *arg_constant = 0;
+ *arg_generic = 0;
+}
+
+// CIR-LABEL: cir.func {{.*}}@test_storage
+// CIR: %[[N:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i, lang_address_space(offload_generic)>
+// CIR: cir.cast address_space %[[N]] : !cir.ptr<!s8i, lang_address_space(offload_generic)> -> !cir.ptr<!s8i, lang_address_space(offload_private)>
+// CIR: %[[N:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i, lang_address_space(offload_generic)>
+// CIR: cir.cast address_space %[[N]] : !cir.ptr<!s8i, lang_address_space(offload_generic)> -> !cir.ptr<!s8i, lang_address_space(offload_local)>
+// CIR: %[[N:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i, lang_address_space(offload_generic)>
+// CIR: cir.cast address_space %[[N]] : !cir.ptr<!s8i, lang_address_space(offload_generic)> -> !cir.ptr<!s8i, lang_address_space(offload_global)>
+// CIR: cir.const #cir.ptr<null> : !cir.ptr<!s8i, lang_address_space(offload_constant)>
+// CIR-NOT: cir.cast address_space
+// CIR: cir.const #cir.ptr<null> : !cir.ptr<!s8i, lang_address_space(offload_generic)>
+// CIR-NOT: cir.cast address_space
+// CIR: cir.return
+
+// LLVM-LABEL: define {{.*}}void @test_storage
+// LLVM: store ptr addrspacecast (ptr addrspace(4) null to ptr), ptr addrspace(4) %{{.*}}
+// LLVM: store ptr addrspace(3) addrspacecast (ptr addrspace(4) null to ptr addrspace(3)), ptr addrspace(4) %{{.*}}
+// LLVM: store ptr addrspace(1) addrspacecast (ptr addrspace(4) null to ptr addrspace(1)), ptr addrspace(4) %{{.*}}
+// LLVM: store ptr addrspace(2) null, ptr addrspace(4) %{{.*}}
+// LLVM: store ptr addrspace(4) null, ptr addrspace(4) %{{.*}}
+
+void test_init(void) {
+ private char *p1 = 0;
+ local char *p2 = (local char *)0;
+ global char *p3 = NULL;
+ constant char *p4 = 0;
+ generic char *p5 = NULL;
+}
+
+// LLVM-LABEL: define {{.*}}void @test_init
+// LLVM: store ptr addrspacecast (ptr addrspace(4) null to ptr), ptr %{{.*}}
+// LLVM: store ptr addrspace(3) addrspacecast (ptr addrspace(4) null to ptr addrspace(3)), ptr %{{.*}}
+// LLVM: store ptr addrspace(1) addrspacecast (ptr addrspace(4) null to ptr addrspace(1)), ptr %{{.*}}
+// LLVM: store ptr addrspace(2) null, ptr %{{.*}}
+// LLVM: store ptr addrspace(4) null, ptr %{{.*}}
+
+private int *test_return_private(void) { return 0; }
+// LLVM-LABEL: define {{.*}}ptr @test_return_private
+// LLVM: ptr addrspacecast (ptr addrspace(4) null to ptr)
+
+local int *test_return_local(void) { return (local int *)0; }
+// LLVM-LABEL: define {{.*}}ptr addrspace(3) @test_return_local
+// LLVM: ptr addrspace(3) addrspacecast (ptr addrspace(4) null to ptr addrspace(3))
+
+global int *test_return_global(void) { return NULL; }
+// LLVM-LABEL: define {{.*}}ptr addrspace(1) @test_return_global
+// LLVM: ptr addrspace(1) addrspacecast (ptr addrspace(4) null to ptr addrspace(1))
+
+constant int *test_return_constant(void) { return 0; }
+// LLVM-LABEL: define {{.*}}ptr addrspace(2) @test_return_constant
+// LLVM: ptr addrspace(2) null
+
+generic int *test_return_generic(void) { return 0; }
+// LLVM-LABEL: define {{.*}}ptr addrspace(4) @test_return_generic
+// LLVM: ptr addrspace(4) null
+
+local int *test_return_as_conversion(void) {
+ return (local int *)(generic int *)(global int *)0;
+}
+// CIR-LABEL: cir.func {{.*}}@test_return_as_conversion
+// CIR: %[[N:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s32i, lang_address_space(offload_generic)>
+// CIR: cir.cast address_space %[[N]] : !cir.ptr<!s32i, lang_address_space(offload_generic)> -> !cir.ptr<!s32i, lang_address_space(offload_local)>
+
+// LLVM-LABEL: define {{.*}}ptr addrspace(3) @test_return_as_conversion
+// LLVM: ptr addrspace(3) addrspacecast (ptr addrspace(4) null to ptr addrspace(3))
+
+// SPV_INTEL_function_pointers forbids casting function pointers to generic.
+typedef __attribute__((address_space(9))) void *FnPtrTy;
+FnPtrTy test_return_fnptr(void) { return 0; }
+// LLVM-LABEL: define {{.*}}ptr addrspace(9) @test_return_fnptr
+// LLVM: ptr addrspace(9) null
>From 7c3a55a51a40ffebb6431796b3295aa4abb4b993 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 5 Oct 2026 19:15:31 +0200
Subject: [PATCH 2/2] comments
---
clang/include/clang/CodeGenUtils/CodeGenUtils.h | 5 +++++
clang/lib/CIR/CodeGen/Targets/SPIRV.cpp | 15 ++-------------
clang/lib/CodeGen/Targets/SPIR.cpp | 15 ++-------------
clang/lib/CodeGenUtils/CodeGenUtils.cpp | 15 +++++++++++++++
4 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/clang/include/clang/CodeGenUtils/CodeGenUtils.h b/clang/include/clang/CodeGenUtils/CodeGenUtils.h
index b2cc416330eb3..8d77264e2b4ef 100644
--- a/clang/include/clang/CodeGenUtils/CodeGenUtils.h
+++ b/clang/include/clang/CodeGenUtils/CodeGenUtils.h
@@ -54,6 +54,11 @@ LangAS getGlobalConstantAddressSpace(const LangOptions &LangOpts,
bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit);
+/// Return true if a SPIR(-V) null pointer of type \p QT must be materialized
+/// as an addrspacecast from a generic null pointer, as the null bit pattern in
+/// non-generic address spaces is unspecified.
+bool spirNullPointerNeedsGenericCast(QualType QT, const llvm::Triple &Triple);
+
/// Check that a call to a target-specific builtin has the required target
/// features enabled in the caller, emitting an error diagnostic if not.
/// \p caller is the FunctionDecl of the enclosing function (may be null).
diff --git a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
index 8129f0ddb752e..064aee64aaa4e 100644
--- a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
+++ b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp
@@ -16,6 +16,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CodeGenUtils/CodeGenUtils.h"
using namespace clang;
using namespace clang::CIRGen;
@@ -67,20 +68,8 @@ mlir::Value
CommonSPIRTargetCIRGenInfo::getNullPointer(CIRGenModule &cgm,
cir::PointerType ptrTy, QualType qt,
mlir::Location loc) const {
- LangAS as = qt->getUnqualifiedDesugaredType()->isNullPtrType()
- ? LangAS::Default
- : qt->getPointeeType().getAddressSpace();
- unsigned asAsInt = static_cast<unsigned>(as);
- unsigned firstTargetASAsInt =
- static_cast<unsigned>(LangAS::FirstTargetAddressSpace);
- unsigned codeSectionINTELAS = firstTargetASAsInt + 9;
- // As per SPV_INTEL_function_pointers, it is illegal to addrspacecast
- // function pointers to/from the generic AS.
- bool isFunctionPtrAS =
- cgm.getTriple().isSPIRV() && asAsInt == codeSectionINTELAS;
CIRGenBuilderTy &builder = cgm.getBuilder();
- if (as == LangAS::Default || as == LangAS::opencl_generic ||
- as == LangAS::opencl_constant || isFunctionPtrAS)
+ if (!CodeGenUtils::spirNullPointerNeedsGenericCast(qt, cgm.getTriple()))
return builder.getNullPtr(ptrTy, loc);
cir::PointerType genericPtrTy =
diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp
index 4a7c84ec26b2b..244dd10d1f7ca 100644
--- a/clang/lib/CodeGen/Targets/SPIR.cpp
+++ b/clang/lib/CodeGen/Targets/SPIR.cpp
@@ -11,6 +11,7 @@
#include "TargetInfo.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Basic/LangOptions.h"
+#include "clang/CodeGenUtils/CodeGenUtils.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/LLVMContext.h"
@@ -491,19 +492,7 @@ llvm::Constant *
CommonSPIRTargetCodeGenInfo::getNullPointer(const CodeGen::CodeGenModule &CGM,
llvm::PointerType *PT,
QualType QT) const {
- LangAS AS = QT->getUnqualifiedDesugaredType()->isNullPtrType()
- ? LangAS::Default
- : QT->getPointeeType().getAddressSpace();
- unsigned ASAsInt = static_cast<unsigned>(AS);
- unsigned FirstTargetASAsInt =
- static_cast<unsigned>(LangAS::FirstTargetAddressSpace);
- unsigned CodeSectionINTELAS = FirstTargetASAsInt + 9;
- // As per SPV_INTEL_function_pointers, it is illegal to addrspacecast
- // function pointers to/from the generic AS.
- bool IsFunctionPtrAS =
- CGM.getTriple().isSPIRV() && ASAsInt == CodeSectionINTELAS;
- if (AS == LangAS::Default || AS == LangAS::opencl_generic ||
- AS == LangAS::opencl_constant || IsFunctionPtrAS)
+ if (!CodeGenUtils::spirNullPointerNeedsGenericCast(QT, CGM.getTriple()))
return llvm::ConstantPointerNull::get(PT);
auto &Ctx = CGM.getContext();
diff --git a/clang/lib/CodeGenUtils/CodeGenUtils.cpp b/clang/lib/CodeGenUtils/CodeGenUtils.cpp
index 11afe31ae89b0..fc1ceb006ec05 100644
--- a/clang/lib/CodeGenUtils/CodeGenUtils.cpp
+++ b/clang/lib/CodeGenUtils/CodeGenUtils.cpp
@@ -134,6 +134,21 @@ LangAS getGlobalConstantAddressSpace(const LangOptions &LangOpts,
return LangAS::Default;
}
+bool spirNullPointerNeedsGenericCast(QualType QT, const llvm::Triple &Triple) {
+ // LLVM address space of the SPIR-V CodeSectionINTEL storage class.
+ constexpr unsigned SPIRVCodeSectionINTELAddrSpace = 9;
+ LangAS AS = QT->getUnqualifiedDesugaredType()->isNullPtrType()
+ ? LangAS::Default
+ : QT->getPointeeType().getAddressSpace();
+ if (AS == LangAS::Default || AS == LangAS::opencl_generic ||
+ AS == LangAS::opencl_constant)
+ return false;
+ // As per SPV_INTEL_function_pointers, it is illegal to addrspacecast
+ // function pointers to/from the generic AS.
+ return !(Triple.isSPIRV() && isTargetAddressSpace(AS) &&
+ toTargetAddressSpace(AS) == SPIRVCodeSectionINTELAddrSpace);
+}
+
bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit) {
const Type *BaseType = BaseInit->getBaseClass();
return BaseType->castAsCXXRecordDecl()->isDynamicClass();
More information about the cfe-commits
mailing list