| //===-- SPIRVAuxDataHandler.cpp - NonSemantic.AuxData emitter -*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SPIRVAuxDataHandler.h" |
| #include "MCTargetDesc/SPIRVMCTargetDesc.h" |
| #include "SPIRVSubtarget.h" |
| #include "SPIRVUtils.h" |
| #include "llvm/CodeGen/AsmPrinter.h" |
| #include "llvm/IR/Attributes.h" |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/GlobalObject.h" |
| #include "llvm/IR/GlobalVariable.h" |
| #include "llvm/IR/LLVMContext.h" |
| #include "llvm/IR/Metadata.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/MC/MCInst.h" |
| #include "llvm/MC/MCStreamer.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/ErrorHandling.h" |
| |
| using namespace llvm; |
| |
| static cl::opt<bool> SPVPreserveAuxData( |
| "spirv-preserve-auxdata", |
| cl::desc("Preserve LLVM attributes and metadata as " |
| "NonSemantic.AuxData ExtInst annotations (requires " |
| "SPV_KHR_non_semantic_info)"), |
| cl::Optional, cl::Hidden, cl::init(false)); |
| |
| namespace { |
| enum AuxDataLinkageType : uint32_t { |
| AvailableExternally = 0, |
| }; |
| |
| constexpr unsigned NonSemanticAuxDataSet = |
| static_cast<unsigned>(SPIRV::InstructionSet::NonSemantic_AuxData); |
| |
| AttributeSet getGOAttrs(const GlobalObject *GO) { |
| if (const auto *F = dyn_cast<Function>(GO)) |
| return F->getAttributes().getFnAttrs(); |
| return cast<GlobalVariable>(GO)->getAttributes(); |
| } |
| } // namespace |
| |
| static bool wasAvailableExternally(const GlobalObject *GO) { |
| if (const auto *F = dyn_cast<Function>(GO)) |
| return F->hasFnAttribute(SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR); |
| return cast<GlobalVariable>(GO)->getAttributes().hasAttribute( |
| SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR); |
| } |
| |
| SPIRVAuxDataHandler::SPIRVAuxDataHandler(AsmPrinter &AP, const Module &M) |
| : Asm(AP), Mod(M) { |
| for (const GlobalObject &GO : M.global_objects()) |
| if (wasAvailableExternally(&GO)) |
| LinkagePreservedGOs.push_back(&GO); |
| } |
| |
| bool SPIRVAuxDataHandler::hasWork() const { return SPVPreserveAuxData; } |
| |
| void SPIRVAuxDataHandler::prepareModuleOutput(const SPIRVSubtarget &ST, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| if (!hasWork()) |
| return; |
| if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info)) { |
| if (SPVPreserveAuxData) |
| report_fatal_error("-spirv-preserve-auxdata requires the " |
| "SPV_KHR_non_semantic_info extension to be enabled."); |
| return; |
| } |
| MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info); |
| if (!MAI.ExtInstSetMap.count(NonSemanticAuxDataSet)) |
| MAI.ExtInstSetMap[NonSemanticAuxDataSet] = MAI.getNextIDRegister(); |
| } |
| |
| MCRegister |
| SPIRVAuxDataHandler::getOrEmitString(StringRef S, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| auto [It, Inserted] = StringRegs.try_emplace(S); |
| if (!Inserted) |
| return It->second; |
| MCRegister Reg = MAI.getNextIDRegister(); |
| It->second = Reg; |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpString); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| addStringImm(S, Inst); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| void SPIRVAuxDataHandler::collectAttributesFor(const GlobalObject *GO, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| AuxDataOpcode Opcode = isa<Function>(GO) ? FunctionAttributeOpcode |
| : GlobalVariableAttributeOpcode; |
| for (const Attribute &A : getGOAttrs(GO)) { |
| if (A.isStringAttribute() && |
| A.getKindAsString() == SPIRV_WAS_AVAILABLE_EXTERNALLY_ATTR) |
| continue; |
| ExtInstRecord Rec; |
| Rec.Opcode = Opcode; |
| Rec.Target = GO; |
| if (A.isStringAttribute()) { |
| Rec.Operands.push_back({getOrEmitString(A.getKindAsString(), MAI)}); |
| StringRef Val = A.getValueAsString(); |
| if (!Val.empty()) |
| Rec.Operands.push_back({getOrEmitString(Val, MAI)}); |
| } else { |
| Rec.Operands.push_back( |
| {getOrEmitString(StringPool.save(A.getAsString()), MAI)}); |
| } |
| PendingRecords.push_back(std::move(Rec)); |
| } |
| } |
| |
| void SPIRVAuxDataHandler::collectMetadataFor(const GlobalObject *GO, |
| ArrayRef<StringRef> MDNames, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| SmallVector<std::pair<unsigned, MDNode *>> AllMD; |
| GO->getAllMetadata(AllMD); |
| if (AllMD.empty()) |
| return; |
| AuxDataOpcode Opcode = |
| isa<Function>(GO) ? FunctionMetadataOpcode : GlobalVariableMetadataOpcode; |
| // MDString operands become OpStrings; ValueAsMetadata constants (e.g. |
| // !{i32 5}) become OpConstants emitted at section 10. Any other operand |
| // kind would need full value translation, so skip the whole node. |
| auto CollectOperands = |
| [&](MDNode *MD) -> std::optional<SmallVector<Operand, 4>> { |
| SmallVector<Operand, 4> Out; |
| for (const MDOperand &MdOp : MD->operands()) { |
| Metadata *Md = MdOp.get(); |
| if (auto *MDStr = dyn_cast_or_null<MDString>(Md)) { |
| Out.push_back({getOrEmitString(MDStr->getString(), MAI)}); |
| } else if (auto *VAM = dyn_cast_or_null<ValueAsMetadata>(Md)) { |
| auto *C = dyn_cast<Constant>(VAM->getValue()); |
| if (!C || !(isa<ConstantInt>(C) || isa<ConstantFP>(C))) |
| return std::nullopt; |
| Out.push_back({MCRegister(), C}); |
| } else { |
| return std::nullopt; |
| } |
| } |
| return Out; |
| }; |
| for (const auto &MD : AllMD) { |
| if (MD.first == LLVMContext::MD_dbg) |
| continue; |
| StringRef MDName = MDNames[MD.first]; |
| if (MDName == "spirv.Decorations" || MDName == "spirv.ParameterDecorations") |
| continue; |
| auto Operands = CollectOperands(MD.second); |
| if (!Operands) |
| continue; |
| ExtInstRecord Rec; |
| Rec.Opcode = Opcode; |
| Rec.Target = GO; |
| Rec.Operands.push_back({getOrEmitString(MDName, MAI)}); |
| Rec.Operands.append(Operands->begin(), Operands->end()); |
| PendingRecords.push_back(std::move(Rec)); |
| } |
| } |
| |
| void SPIRVAuxDataHandler::emitAuxDataStrings(SPIRV::ModuleAnalysisInfo &MAI) { |
| if (!SPVPreserveAuxData) |
| return; |
| if (!MAI.getExtInstSetReg(NonSemanticAuxDataSet).isValid()) |
| return; |
| SmallVector<StringRef> MDNames; |
| Mod.getContext().getMDKindNames(MDNames); |
| for (const GlobalObject &GO : Mod.global_objects()) { |
| if (GO.isDeclaration()) |
| continue; |
| collectAttributesFor(&GO, MAI); |
| collectMetadataFor(&GO, MDNames, MAI); |
| } |
| } |
| |
| void SPIRVAuxDataHandler::emitAuxData(SPIRV::ModuleAnalysisInfo &MAI) { |
| MCRegister ExtSetReg = MAI.getExtInstSetReg(NonSemanticAuxDataSet); |
| if (!ExtSetReg.isValid()) |
| return; |
| |
| MCRegister VoidTypeReg = findOrEmitOpTypeVoid(MAI); |
| |
| for (const ExtInstRecord &Rec : PendingRecords) { |
| MCRegister TargetReg = MAI.getGlobalObjReg(Rec.Target); |
| if (!TargetReg.isValid()) |
| continue; |
| SmallVector<MCRegister, 5> Operands; |
| Operands.push_back(TargetReg); |
| for (const Operand &Op : Rec.Operands) |
| Operands.push_back(Op.Const ? emitConstant(Op.Const, MAI) : Op.Reg); |
| emitAuxDataExtInst(Rec.Opcode, VoidTypeReg, ExtSetReg, Operands, MAI); |
| } |
| |
| if (LinkagePreservedGOs.empty()) |
| return; |
| |
| MCRegister UInt32TypeReg = findOrEmitOpTypeUInt32(MAI); |
| MCRegister AEConstReg; |
| for (const GlobalObject *GO : LinkagePreservedGOs) { |
| MCRegister TargetReg = MAI.getGlobalObjReg(GO); |
| if (!TargetReg.isValid()) |
| continue; |
| if (!AEConstReg.isValid()) |
| AEConstReg = |
| emitOpConstantUInt32(AvailableExternally, UInt32TypeReg, MAI); |
| emitAuxDataExtInst(LinkageOpcode, VoidTypeReg, ExtSetReg, |
| {TargetReg, AEConstReg}, MAI); |
| } |
| } |
| |
| void SPIRVAuxDataHandler::emitAuxDataExtInst(AuxDataOpcode Opcode, |
| MCRegister VoidTypeReg, |
| MCRegister ExtSetReg, |
| ArrayRef<MCRegister> Operands, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpExtInst); |
| Inst.addOperand(MCOperand::createReg(MAI.getNextIDRegister())); |
| Inst.addOperand(MCOperand::createReg(VoidTypeReg)); |
| Inst.addOperand(MCOperand::createReg(ExtSetReg)); |
| Inst.addOperand(MCOperand::createImm(Opcode)); |
| for (MCRegister R : Operands) |
| Inst.addOperand(MCOperand::createReg(R)); |
| emitMCInst(Inst); |
| } |
| |
| void SPIRVAuxDataHandler::emitMCInst(MCInst &Inst) { |
| Asm.OutStreamer->emitInstruction(Inst, Asm.getSubtargetInfo()); |
| } |
| |
| MCRegister |
| SPIRVAuxDataHandler::findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI) { |
| for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) |
| if (MI->getOpcode() == SPIRV::OpTypeVoid) |
| return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg()); |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpTypeVoid); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister |
| SPIRVAuxDataHandler::findOrEmitOpTypeInt(unsigned BitWidth, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| // SPIR-V OpTypeInt: <width>, <signedness>. Signedness 0 = unsigned, 1 = |
| // signed; we always emit unsigned. |
| constexpr int64_t UnsignedSignedness = 0; |
| for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) |
| if (MI->getOpcode() == SPIRV::OpTypeInt && |
| MI->getOperand(1).getImm() == static_cast<int64_t>(BitWidth) && |
| MI->getOperand(2).getImm() == UnsignedSignedness) |
| return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg()); |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpTypeInt); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createImm(BitWidth)); |
| Inst.addOperand(MCOperand::createImm(UnsignedSignedness)); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister |
| SPIRVAuxDataHandler::findOrEmitOpTypeUInt32(SPIRV::ModuleAnalysisInfo &MAI) { |
| return findOrEmitOpTypeInt(32, MAI); |
| } |
| |
| MCRegister |
| SPIRVAuxDataHandler::findOrEmitOpTypeFloat(unsigned BitWidth, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) |
| if (MI->getOpcode() == SPIRV::OpTypeFloat && |
| MI->getOperand(1).getImm() == static_cast<int64_t>(BitWidth)) |
| return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg()); |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpTypeFloat); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createImm(BitWidth)); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVAuxDataHandler::emitConstant(const Constant *C, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| auto [It, Inserted] = ConstantRegs.try_emplace(C); |
| if (!Inserted) |
| return It->second; |
| |
| APInt Bits; |
| unsigned Opcode; |
| MCRegister TypeReg; |
| if (const auto *CI = dyn_cast<ConstantInt>(C)) { |
| Bits = CI->getValue(); |
| Opcode = SPIRV::OpConstantI; |
| TypeReg = findOrEmitOpTypeInt(Bits.getBitWidth(), MAI); |
| } else { |
| const auto *CF = cast<ConstantFP>(C); |
| Bits = CF->getValueAPF().bitcastToAPInt(); |
| Opcode = SPIRV::OpConstantF; |
| TypeReg = findOrEmitOpTypeFloat(Bits.getBitWidth(), MAI); |
| } |
| |
| MCRegister Reg = MAI.getNextIDRegister(); |
| It->second = Reg; |
| MCInst Inst; |
| Inst.setOpcode(Opcode); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createReg(TypeReg)); |
| // SPIR-V encodes the literal as ceil(width/32) little-endian 32-bit words. |
| unsigned NumWords = std::max(1u, (Bits.getBitWidth() + 31) / 32); |
| for (unsigned I = 0; I < NumWords; ++I) |
| Inst.addOperand(MCOperand::createImm(Bits.extractBitsAsZExtValue( |
| std::min(32u, Bits.getBitWidth() - I * 32), I * 32))); |
| // The asm printer needs this hint to render an f16 literal correctly. |
| if (Opcode == SPIRV::OpConstantF && Bits.getBitWidth() == 16) |
| Inst.setFlags(SPIRV::INST_PRINTER_WIDTH16); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVAuxDataHandler::emitOpConstantUInt32( |
| uint32_t Value, MCRegister UInt32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) { |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpConstantI); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createReg(UInt32TypeReg)); |
| Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Value))); |
| emitMCInst(Inst); |
| return Reg; |
| } |