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)
108 lines
3.7 KiB
C++
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);
|
|
}
|