[llvm] [SPIRV] Fix global emission for modules with no functions (PR #183833)
Nick Sarnie via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 2 07:08:46 PST 2026
https://github.com/sarnex updated https://github.com/llvm/llvm-project/pull/183833
>From 8938c8fcbea930fca74c793ca0faf24980d07035 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Fri, 27 Feb 2026 12:54:55 -0800
Subject: [PATCH 1/2] [SPIRV] Fix global emission for modules with no functions
Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 9 +--------
llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp | 5 +++++
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 2 +-
.../Target/SPIRV/SPIRVPrepareFunctions.cpp | 11 +++++++++++
llvm/lib/Target/SPIRV/SPIRVUtils.cpp | 18 ++++++++++++++++++
llvm/lib/Target/SPIRV/SPIRVUtils.h | 1 +
.../CodeGen/SPIRV/global-var-no-functions.ll | 19 +++++++++++++++++++
.../SPIRV/legalize-zero-size-arrays-global.ll | 4 ++--
.../SPIRV/legalize-zero-size-arrays-nested.ll | 4 ++--
.../SPIRV/legalize-zero-size-arrays-struct.ll | 4 ++--
.../SPIRV/legalize-zero-size-arrays-undef.ll | 4 ++--
.../fun-with-aggregate-arg-in-const-init.ll | 9 ++++++---
12 files changed, 70 insertions(+), 20 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 1c8774c59f065..04add49846350 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2814,15 +2814,8 @@ bool SPIRVEmitIntrinsics::processFunctionPointers(Module &M) {
if (Worklist.empty())
return false;
- std::string ServiceFunName = SPIRV_BACKEND_SERVICE_FUN_NAME;
- if (!getVacantFunctionName(M, ServiceFunName))
- report_fatal_error(
- "cannot allocate a name for the internal service function");
LLVMContext &Ctx = M.getContext();
- Function *SF =
- Function::Create(FunctionType::get(Type::getVoidTy(Ctx), {}, false),
- GlobalValue::PrivateLinkage, ServiceFunName, M);
- SF->addFnAttr(SPIRV_BACKEND_SERVICE_FUN_NAME, "");
+ Function *SF = getOrCreateBackendServiceFunction(M);
BasicBlock *BB = BasicBlock::Create(Ctx, "entry", SF);
IRBuilder<> IRB(BB);
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 86659c623ebf2..4b94f67d5a7b0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -2668,6 +2668,11 @@ static void addMBBNames(const Module &M, const SPIRVInstrInfo &TII,
MachineFunction *MF = MMI->getMachineFunction(F);
if (!MF)
continue;
+ if (MF->getFunction()
+ .getFnAttribute(SPIRV_BACKEND_SERVICE_FUN_NAME)
+ .isValid())
+ continue;
+
MachineRegisterInfo &MRI = MF->getRegInfo();
for (auto &MBB : *MF) {
if (!MBB.hasName() || MBB.empty())
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 3849a8b223b36..aead1ce735c49 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -995,7 +995,7 @@ static void processBlockAddr(MachineFunction &MF, SPIRVGlobalRegistry *GR,
static bool isImplicitFallthrough(MachineBasicBlock &MBB) {
if (MBB.empty())
- return true;
+ return MBB.getNextNode() != nullptr;
// Branching SPIR-V intrinsics are not detected by this generic method.
// Thus, we can only trust negative result.
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 54f8b58114f7f..219dd2b7f0ca0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -666,6 +666,17 @@ bool SPIRVPrepareFunctions::runOnModule(Module &M) {
.getMutableSubtargetImpl()
->resolveEnvFromModule(M);
+ if (M.functions().empty()) {
+ // If there are no functions, insert a service
+ // function so that the global/constant tracking intrinsics
+ // will be created. Without these intrinsics the generated SPIR-V
+ // will be empty. The service function itself is not emitted.
+ Function *SF = getOrCreateBackendServiceFunction(M);
+ BasicBlock *BB = BasicBlock::Create(M.getContext(), "entry", SF);
+ IRBuilder<> IRB(BB);
+ IRB.CreateRetVoid();
+ }
+
bool Changed = false;
for (Function &F : M) {
Changed |= substituteIntrinsicCalls(&F);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 33be4511a827b..7858f7f932a46 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -1210,4 +1210,22 @@ getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV) {
return SPIRV::LinkageType::Export;
}
+Function *getOrCreateBackendServiceFunction(Module &M) {
+ std::string ServiceFunName = SPIRV_BACKEND_SERVICE_FUN_NAME;
+ if (!getVacantFunctionName(M, ServiceFunName))
+ report_fatal_error(
+ "cannot allocate a name for the internal service function");
+ if (Function *SF = M.getFunction(ServiceFunName)) {
+ if (SF->getInstructionCount() > 0)
+ report_fatal_error(
+ "Unexpected combination of global variables and function pointers");
+ return SF;
+ }
+ Function *SF = Function::Create(
+ FunctionType::get(Type::getVoidTy(M.getContext()), {}, false),
+ GlobalValue::PrivateLinkage, ServiceFunName, M);
+ SF->addFnAttr(SPIRV_BACKEND_SERVICE_FUN_NAME, "");
+ return SF;
+}
+
} // namespace llvm
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 3aec2beb94ac6..8a152cd58d517 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -587,5 +587,6 @@ getFirstValidInstructionInsertPoint(MachineBasicBlock &BB);
std::optional<SPIRV::LinkageType::LinkageType>
getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV);
+Function *getOrCreateBackendServiceFunction(Module &M);
} // namespace llvm
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H
diff --git a/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll b/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
new file mode 100644
index 0000000000000..9d34fa1f36e8a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
@@ -0,0 +1,19 @@
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown < %s -o - -filetype=asm | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown < %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: OpCapability Kernel
+; CHECK: OpCapability Addresses
+; CHECK: OpCapability Linkage
+; CHECK: %[[#Import:]] = OpExtInstImport "OpenCL.std"
+; CHECK: OpMemoryModel Physical64 OpenCL
+; CHECK: OpSource OpenCL_CPP 100000
+; CHECK: OpName %[[#Global:]] "global_var"
+; CHECK: OpDecorate %[[#Global]] LinkageAttributes "global_var" Export
+; CHECK: %[[#Int32Ty:]] = OpTypeInt 32 0
+; CHECK: %[[#Int32PtrTy:]] = OpTypePointer CrossWorkgroup %[[#Int32Ty]]
+; CHECK: %[[#Initializer:]] = OpConstantNull %[[#Int32Ty]]
+; CHECK: OpVariable %[[#Int32PtrTy:]] CrossWorkgroup %[[#Initializer]]
+
+; Verify we emit global definitions even if there are no functions.
+
+ at global_var = addrspace(1) global i32 zeroinitializer
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
index 00438d33b8c2a..829a5a508f242 100644
--- a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
@@ -3,6 +3,6 @@
; Test that a global variable with zero-size array is transformed to ptr type.
- at global_zero_array = global [0 x i32] zeroinitializer
+ at global_zero_array = addrspace(1) global [0 x i32] zeroinitializer
-; CHECK: @global_zero_array = global ptr addrspace(4) null
+; CHECK: @global_zero_array = addrspace(1) global ptr addrspace(4) null
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll
index 6a12d7ba09231..fcc437187f30a 100644
--- a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll
@@ -3,6 +3,6 @@
; Test that nested zero-size arrays are legalized to a pointer.
- at nested_zero_array = global [2 x [0 x i32]] zeroinitializer
+ at nested_zero_array = addrspace(1) global [2 x [0 x i32]] zeroinitializer
-; CHECK: @nested_zero_array = global ptr addrspace(4) null
+; CHECK: @nested_zero_array = addrspace(1) global ptr addrspace(4) null
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll
index f7b93cdbc8b74..d340417cbfdc8 100644
--- a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll
@@ -5,7 +5,7 @@
%struct.with_zero = type { i32, [0 x i32], i32 }
- at global_struct = global %struct.with_zero zeroinitializer
+ at global_struct = addrspace(1) global %struct.with_zero zeroinitializer
; CHECK: %struct.with_zero.legalized = type { i32, ptr addrspace(4), i32 }
-; CHECK: @global_struct = global %struct.with_zero.legalized zeroinitializer
+; CHECK: @global_struct = addrspace(1) global %struct.with_zero.legalized zeroinitializer
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-undef.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-undef.ll
index cdc3b304fdc7c..17945d1f72847 100644
--- a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-undef.ll
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-undef.ll
@@ -3,6 +3,6 @@
; Test that poision initializers are legalized correctly to a pointer.
- at nested_zero_array = global [2 x [0 x i32]] poison
+ at nested_zero_array = addrspace(1) global [2 x [0 x i32]] poison
-; CHECK: @nested_zero_array = global ptr addrspace(4) poison
+; CHECK: @nested_zero_array = addrspace(1) global ptr addrspace(4) poison
diff --git a/llvm/test/CodeGen/SPIRV/pointers/fun-with-aggregate-arg-in-const-init.ll b/llvm/test/CodeGen/SPIRV/pointers/fun-with-aggregate-arg-in-const-init.ll
index ec3fd41f7de9e..7d30fde76db7f 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/fun-with-aggregate-arg-in-const-init.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/fun-with-aggregate-arg-in-const-init.ll
@@ -6,6 +6,9 @@
; CHECK-DAG: OpExtension "SPV_INTEL_function_pointers"
; CHECK-DAG: OpName %[[#fArray:]] "array"
; CHECK-DAG: OpName %[[#fStruct:]] "struct"
+; CHECK-DAG: OpName %[[#f0:]] "f0"
+; CHECK-DAG: OpName %[[#f1:]] "f1"
+; CHECK-DAG: OpName %[[#f2:]] "f2"
; CHECK-DAG: %[[#Int8Ty:]] = OpTypeInt 8 0
; CHECK: %[[#GlobalInt8PtrTy:]] = OpTypePointer CrossWorkgroup %[[#Int8Ty]]
@@ -30,9 +33,9 @@
; CHECK: %[[#I64Const2:]] = OpConstant %[[#Int64Ty]] 2
; CHECK: %[[#I64Const1:]] = OpConstant %[[#Int64Ty]] 1
; CHECK: %[[#I64Const0:]] = OpConstantNull %[[#Int64Ty]]
-; CHECK: %[[#f0Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %28
-; CHECK: %[[#f1Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %32
-; CHECK: %[[#f2Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %36
+; CHECK: %[[#f0Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %[[#f0]]
+; CHECK: %[[#f1Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %[[#f1]]
+; CHECK: %[[#f2Pfn:]] = OpConstantFunctionPointerINTEL %[[#IntelFnPtrTy]] %[[#f2]]
; CHECK: %[[#f0Cast:]] = OpSpecConstantOp %[[#FnPtrTy]] Bitcast %[[#f0Pfn]]
; CHECK: %[[#f1Cast:]] = OpSpecConstantOp %[[#FnPtrTy]] Bitcast %[[#f1Pfn]]
; CHECK: %[[#f2Cast:]] = OpSpecConstantOp %[[#FnPtrTy]] Bitcast %[[#f2Pfn]]
>From 6acd1263f02bed4eae63dd99aafb67969989c6fc Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Mon, 2 Mar 2026 07:07:28 -0800
Subject: [PATCH 2/2] simplify test and test on vulkan
Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
llvm/test/CodeGen/SPIRV/global-var-no-functions.ll | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll b/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
index 9d34fa1f36e8a..b9c6027635685 100644
--- a/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
+++ b/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
@@ -1,17 +1,14 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown < %s -o - -filetype=asm | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown < %s -o - -filetype=obj | spirv-val %}
-; CHECK: OpCapability Kernel
-; CHECK: OpCapability Addresses
-; CHECK: OpCapability Linkage
-; CHECK: %[[#Import:]] = OpExtInstImport "OpenCL.std"
-; CHECK: OpMemoryModel Physical64 OpenCL
-; CHECK: OpSource OpenCL_CPP 100000
+; RUN: llc -O0 -mtriple=spirv64-vulkan-unknown < %s -o - -filetype=asm | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-vulkan-unknown < %s -o - -filetype=obj | spirv-val %}
+
; CHECK: OpName %[[#Global:]] "global_var"
; CHECK: OpDecorate %[[#Global]] LinkageAttributes "global_var" Export
; CHECK: %[[#Int32Ty:]] = OpTypeInt 32 0
; CHECK: %[[#Int32PtrTy:]] = OpTypePointer CrossWorkgroup %[[#Int32Ty]]
-; CHECK: %[[#Initializer:]] = OpConstantNull %[[#Int32Ty]]
+; CHECK: %[[#Initializer:]] = OpConstant{{.*}} %[[#Int32Ty]]
; CHECK: OpVariable %[[#Int32PtrTy:]] CrossWorkgroup %[[#Initializer]]
; Verify we emit global definitions even if there are no functions.
More information about the llvm-commits
mailing list