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 <[email protected]> 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 <[email protected]> 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(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
