[llvm] e5a6a0f - [SPIRV] Fix global emission for modules with no functions (#183833)

via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 4 06:58:50 PST 2026


Author: Nick Sarnie
Date: 2026-03-04T14:58:45Z
New Revision: e5a6a0f10856b14ea0956b40305632fbbfea6f16

URL: https://github.com/llvm/llvm-project/commit/e5a6a0f10856b14ea0956b40305632fbbfea6f16
DIFF: https://github.com/llvm/llvm-project/commit/e5a6a0f10856b14ea0956b40305632fbbfea6f16.diff

LOG: [SPIRV] Fix global emission for modules with no functions (#183833)

Right now we have a problem where if you have a LLVM module with globals
but no functions, a completely empty SPIR-V module is emitted.

This is because global emission is dependent on tracking intrinsic
functions being emitted in functions.

As a simple fix, just insert a service function, which the backend is
already set up to not actually emit, if there are no real functions.

The current use case of the service function is for function pointers. I
don't think it's possible that we need to both generate a service
function for function pointers and for globals with no functions, so I
just added an error (not an assert) just in case if we do need it for
both cases.

Probably we should rework global handling in the future to work without
these workarounds, but this is a pretty fundamental issue so let's work
around it with this simple change for now.

This change exposed an existing bug:
  We consider basic blocks with no successors as fall-through

Also, fix some existing tests. The symptom was:
We previously emitted an empty module, but not that we don't, we hit a
`spirv-val` error about invalid Function StorageClass for globals
because no `addrspace` was specified. Set the `addrspace` to `1`
(`CrossWorkgroup`) in those tests.

Closes: https://github.com/llvm/llvm-project/issues/182899

---------

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>

Added: 
    llvm/test/CodeGen/SPIRV/global-var-no-functions.ll

Modified: 
    llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
    llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
    llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
    llvm/lib/Target/SPIRV/SPIRVUtils.cpp
    llvm/lib/Target/SPIRV/SPIRVUtils.h
    llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
    llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll
    llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll
    llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-undef.ll

Removed: 
    


################################################################################
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/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..b9c6027635685
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/global-var-no-functions.ll
@@ -0,0 +1,16 @@
+; 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 %}
+
+; 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:]] = OpConstant{{.*}} %[[#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


        


More information about the llvm-commits mailing list