[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