[SPIR-V] Erase dead functions in shader modules (#209672)

The LLVM offload test suite is current failing layout keyword
orientation tests. While initially appearing to be a layout problem in
the SPIR-V legalizer caused by matrix vector sizes exceeding 4, the root
cause is actually persistent dead function definitions in the SPIR-V
backend.

This behavior is standard for OpenCL, which relies on a linker to clean
up unused definitions; however, since shaders lack a linker and require
full inlining, we must handle this in a special shader only pass.

This change adds SPIRVFinalizeShaderLinkage (a shader-only analogue of
DXILFinalizeLinkage): internalize non-entry, non-exported helpers and
erase the dead ones. Globals are left alone since unreferenced externals
become interface OpVariables.

Fixes #201712

Assisted with Claude Opus 4.8
This commit is contained in:
Farzon Lotfi 2026-07-21 18:10:52 -04:00 committed by GitHub
parent db4d75e13d
commit aace063fda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 166 additions and 4 deletions

View file

@ -46,6 +46,7 @@ add_llvm_target(SPIRVCodeGen
SPIRVRegisterBankInfo.cpp
SPIRVRegisterInfo.cpp
SPIRVRegularizer.cpp
SPIRVFinalizeShaderLinkage.cpp
SPIRVSubtarget.cpp
SPIRVTargetMachine.cpp
SPIRVTargetTransformInfo.cpp

View file

@ -27,6 +27,7 @@ ModulePass *createSPIRVPushConstantAccessLegacyPass(SPIRVTargetMachine *TM);
FunctionPass *createSPIRVMergeRegionExitTargetsPass();
ModulePass *createSPIRVLegalizeImplicitBindingPass();
ModulePass *createSPIRVLegalizeZeroSizeArraysPass(const SPIRVTargetMachine &TM);
ModulePass *createSPIRVFinalizeShaderLinkagePass(const SPIRVTargetMachine &TM);
FunctionPass *createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM);
FunctionPass *createSPIRVRegularizerPass();
FunctionPass *createSPIRVPreLegalizerCombiner();
@ -57,6 +58,7 @@ void initializeSPIRVPrepareFunctionsLegacyPass(PassRegistry &);
void initializeSPIRVPrepareGlobalsLegacyPass(PassRegistry &);
void initializeSPIRVLegalizeImplicitBindingLegacyPass(PassRegistry &);
void initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PassRegistry &);
void initializeSPIRVFinalizeShaderLinkageLegacyPass(PassRegistry &);
void initializeSPIRVCtorDtorLoweringLegacyPass(PassRegistry &);
} // namespace llvm

View file

@ -0,0 +1,94 @@
//===- SPIRVFinalizeShaderLinkage.cpp - Finalize shader linkage ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Shader-only analogue of DXILFinalizeLinkage: internalizes non-entry,
// non-exported HLSL helper functions and erases the resulting dead ones.
//
//===----------------------------------------------------------------------===//
#include "SPIRVFinalizeShaderLinkage.h"
#include "SPIRV.h"
#include "SPIRVSubtarget.h"
#include "SPIRVTargetMachine.h"
#include "SPIRVUtils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#define DEBUG_TYPE "spirv-finalize-shader-linkage"
using namespace llvm;
namespace {
bool finalizeShaderLinkage(const SPIRVTargetMachine &TM, Module &M) {
// This pass only applies to shader targets because shaders don't have
// linkers.
if (!TM.getSubtargetImpl()->isShader())
return false;
bool Changed = false;
for (Function &F : M) {
if (F.isIntrinsic() || F.isDeclaration() || isEntryPoint(F))
continue;
if (F.hasExternalLinkage() && !F.hasHiddenVisibility())
continue;
if (!F.hasLocalLinkage()) {
F.setLinkage(GlobalValue::InternalLinkage);
Changed = true;
}
}
// Erase dead helpers, iterating to a fixpoint for helper-calls-helper chains.
bool LocalChange = true;
while (LocalChange) {
LocalChange = false;
for (Function &F : make_early_inc_range(M))
if (F.isDefTriviallyDead()) {
F.eraseFromParent();
LocalChange = Changed = true;
}
}
return Changed;
}
class SPIRVFinalizeShaderLinkageLegacy : public ModulePass {
public:
static char ID;
SPIRVFinalizeShaderLinkageLegacy(const SPIRVTargetMachine &TM)
: ModulePass(ID), TM(TM) {}
StringRef getPassName() const override {
return "SPIRV Finalize Shader Linkage";
}
bool runOnModule(Module &M) override { return finalizeShaderLinkage(TM, M); }
private:
const SPIRVTargetMachine &TM;
};
} // namespace
PreservedAnalyses SPIRVFinalizeShaderLinkage::run(Module &M,
ModuleAnalysisManager &AM) {
return finalizeShaderLinkage(TM, M) ? PreservedAnalyses::none()
: PreservedAnalyses::all();
}
char SPIRVFinalizeShaderLinkageLegacy::ID = 0;
INITIALIZE_PASS(SPIRVFinalizeShaderLinkageLegacy,
"spirv-finalize-shader-linkage",
"Finalize SPIR-V shader linkage", false, false)
ModulePass *
llvm::createSPIRVFinalizeShaderLinkagePass(const SPIRVTargetMachine &TM) {
return new SPIRVFinalizeShaderLinkageLegacy(TM);
}

View file

@ -0,0 +1,34 @@
//===- SPIRVFinalizeShaderLinkage.h - Finalize shader linkage --*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// Shader-only analogue of DXILFinalizeLinkage: internalizes non-entry,
/// non-exported HLSL helper functions and erases the resulting dead ones.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVFINALIZESHADERLINKAGE_H
#define LLVM_LIB_TARGET_SPIRV_SPIRVFINALIZESHADERLINKAGE_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class SPIRVTargetMachine;
class SPIRVFinalizeShaderLinkage
: public OptionalPassInfoMixin<SPIRVFinalizeShaderLinkage> {
const SPIRVTargetMachine &TM;
public:
SPIRVFinalizeShaderLinkage(const SPIRVTargetMachine &TM) : TM(TM) {}
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVFINALIZESHADERLINKAGE_H

View file

@ -23,6 +23,7 @@ MODULE_PASS("spirv-lower-ctor-dtor", SPIRVCtorDtorLoweringPass())
MODULE_PASS("spirv-prepare-functions", SPIRVPrepareFunctions(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-prepare-globals", SPIRVPrepareGlobals())
MODULE_PASS("spirv-pushconstant-access", SPIRVPushConstantAccess(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-finalize-shader-linkage", SPIRVFinalizeShaderLinkage(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-emit-intrinsics", SPIRVEmitIntrinsicsPass(*static_cast<const SPIRVTargetMachine *>(this)))
#undef MODULE_PASS

View file

@ -16,6 +16,7 @@
#include "SPIRVCBufferAccess.h"
#include "SPIRVCtorDtorLowering.h"
#include "SPIRVEmitIntrinsics.h"
#include "SPIRVFinalizeShaderLinkage.h"
#include "SPIRVGlobalRegistry.h"
#include "SPIRVLegalizeImplicitBinding.h"
#include "SPIRVLegalizePointerCast.h"
@ -75,6 +76,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
initializeSPIRVPrepareGlobalsLegacyPass(PR);
initializeSPIRVLegalizeImplicitBindingLegacyPass(PR);
initializeSPIRVCtorDtorLoweringLegacyPass(PR);
initializeSPIRVFinalizeShaderLinkageLegacyPass(PR);
}
static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
@ -186,10 +188,13 @@ void SPIRVPassConfig::addIRPasses() {
TargetPassConfig::addIRPasses();
// Variadic function calls aren't supported in shader code.
// This needs to come before SPIRVPrepareFunctions because this
// may introduce intrinsic calls.
if (!TM.getSubtargetImpl()->isShader()) {
if (TM.getSubtargetImpl()->isShader()) {
if (getOptLevel() != CodeGenOptLevel::None)
addPass(createSPIRVFinalizeShaderLinkagePass(TM));
} else {
// Variadic function calls aren't supported in shader code.
// This needs to come before SPIRVPrepareFunctions because this
// may introduce intrinsic calls.
addPass(createExpandVariadicsPass(ExpandVariadicsMode::Lowering));
}

View file

@ -0,0 +1,25 @@
; RUN: opt -mtriple=spirv-unknown-vulkan1.3-compute -passes=spirv-finalize-shader-linkage -S %s | FileCheck %s --check-prefix=OPT
; RUN: llc -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -mtriple=spirv-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
; The pass erases the dead helper and keeps the entry point.
; OPT-NOT: @dead_matrix_helper
; OPT: define void @main()
; CHECK-DAG: OpEntryPoint GLCompute %[[#entry:]] "main"
; The dead helper (and its illegal wide-vector parameter) must not be emitted.
; CHECK-NOT: OpFunctionParameter
; CHECK-NOT: dead_matrix_helper
define hidden spir_func <6 x float> @dead_matrix_helper(<6 x float> %m) #1 {
%r = fadd <6 x float> %m, %m
ret <6 x float> %r
}
define void @main() #0 {
entry:
ret void
}
attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
attributes #1 = { alwaysinline }