llvm-project/llvm/lib/Target/SPIRV/SPIRVPushConstantAccess.cpp
Nathan Gauër 8cfda79105
[HLSL][SPIR-V] Implement vk::push_constant (#166793)
Implements initial support for vk::push_constant.
As is, this allows handling simple push constants, but has one
main issue: layout can be incorrect (See #168401). The layout
issue being not only push-constant related, it's ignored for this PR.

The frontend part of the implementation is straightforward:
 - adding a new attribute
 - when targeting vulkan/spirv, we process it
 - global variables with this attribute gets a new AS:
   hlsl_push_constant

The IR has nothing specific, only some RO globals in this new AS.

On the SPIR-V side, we not convert this AS into a PushConstant storage
class. But this creates some issues: the variables in this storage class
must have a specific set of decoration to define their layout.

Current infra to create the SPIR-V types lacks the context required to
make this decision: no indication on the AS or context around the type
being created. Refactoring this would be a heavy task as it would
require getting this information in every place using the GR for type
creation.

Instead, we do something similar to CBuffers:
 - find all globals with this address space, and change their type to
   a target-specific type.
 - insert a new intrinsic in place of every reference to this global
   variable.

This allow the backend to handle both layout variables loads and type
lowering independently.

Type lowering has nothing specific: when we encounter a target extension
type with spirv.PushConstant, we lower this to the correct SPIR-V type
with the proper offset & block decorations.

As for the intrinsic, it's mostly a no-op, but required since we have
this target-specific type.

Note: this implementation prevents the static declaration of multiple
push constants in a single shader module. The actual specification is
more relaxed: there can be only one **used** push constant block per
entrypoint. To correctly implement this, we'd require to keep some
additional state to determine the list of statically used resources per
entrypoint. This shall be addressed as a follow-up (see #170310)
2025-12-18 11:01:11 +01:00

108 lines
3.7 KiB
C++

//===- SPIRVPushConstantAccess.cpp - Translate CBuffer Loads ----*- 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
//
//===----------------------------------------------------------------------===//
//
// This pass changes the types of all the globals in the PushConstant
// address space into a target extension type, and makes all references
// to this global go though a custom SPIR-V intrinsic.
//
// This allows the backend to properly lower the push constant struct type
// to a fully laid out type, and generate the proper OpAccessChain.
//
//===----------------------------------------------------------------------===//
#include "SPIRVPushConstantAccess.h"
#include "SPIRV.h"
#include "SPIRVSubtarget.h"
#include "SPIRVTargetMachine.h"
#include "SPIRVUtils.h"
#include "llvm/Frontend/HLSL/CBuffer.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/IntrinsicsSPIRV.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ReplaceConstant.h"
#define DEBUG_TYPE "spirv-pushconstant-access"
using namespace llvm;
static bool replacePushConstantAccesses(Module &M, SPIRVGlobalRegistry *GR) {
SmallVector<GlobalVariable *> PushConstants;
for (GlobalVariable &GV : M.globals()) {
if (GV.getAddressSpace() !=
storageClassToAddressSpace(SPIRV::StorageClass::PushConstant))
continue;
GV.removeDeadConstantUsers();
PushConstants.push_back(&GV);
}
for (GlobalVariable *GV : PushConstants) {
Type *PCType = llvm::TargetExtType::get(
M.getContext(), "spirv.PushConstant", {GV->getValueType()});
GlobalVariable *NewGV = new GlobalVariable(
M, PCType, GV->isConstant(), GV->getLinkage(),
/* initializer= */ nullptr, GV->getName(),
/* InsertBefore= */ GV, GV->getThreadLocalMode(), GV->getAddressSpace(),
GV->isExternallyInitialized());
SmallVector<User *, 4> Users(GV->user_begin(), GV->user_end());
for (User *U : Users) {
Instruction *I = cast<Instruction>(U);
IRBuilder<> Builder(I);
Value *GetPointerCall = Builder.CreateIntrinsic(
NewGV->getType(), Intrinsic::spv_pushconstant_getpointer, {NewGV});
GR->buildAssignPtr(Builder, GV->getValueType(), GetPointerCall);
for (unsigned N = 0; N < I->getNumOperands(); ++N) {
if (I->getOperand(N) == GV)
I->setOperand(N, GetPointerCall);
}
}
GV->eraseFromParent();
}
return true;
}
PreservedAnalyses SPIRVPushConstantAccess::run(Module &M,
ModuleAnalysisManager &AM) {
const SPIRVSubtarget *ST = TM.getSubtargetImpl();
SPIRVGlobalRegistry *GR = ST->getSPIRVGlobalRegistry();
return replacePushConstantAccesses(M, GR) ? PreservedAnalyses::none()
: PreservedAnalyses::all();
}
namespace {
class SPIRVPushConstantAccessLegacy : public ModulePass {
SPIRVTargetMachine *TM = nullptr;
public:
bool runOnModule(Module &M) override {
const SPIRVSubtarget *ST = TM->getSubtargetImpl();
SPIRVGlobalRegistry *GR = ST->getSPIRVGlobalRegistry();
return replacePushConstantAccesses(M, GR);
}
StringRef getPassName() const override {
return "SPIRV push constant Access";
}
SPIRVPushConstantAccessLegacy(SPIRVTargetMachine *TM)
: ModulePass(ID), TM(TM) {}
static char ID; // Pass identification.
};
char SPIRVPushConstantAccessLegacy::ID = 0;
} // end anonymous namespace
INITIALIZE_PASS(SPIRVPushConstantAccessLegacy, DEBUG_TYPE,
"SPIRV push constant Access", false, false)
ModulePass *
llvm::createSPIRVPushConstantAccessLegacyPass(SPIRVTargetMachine *TM) {
return new SPIRVPushConstantAccessLegacy(TM);
}