| //===-- SPIRVInstPrinter.cpp - Output SPIR-V MCInsts as ASM -----*- 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 class prints a SPIR-V MCInst to a .s file. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "SPIRVInstPrinter.h" | |
| #include "SPIRV.h" | |
| #include "SPIRVBaseInfo.h" | |
| #include "llvm/CodeGen/Register.h" | |
| #include "llvm/MC/MCAsmInfo.h" | |
| #include "llvm/MC/MCExpr.h" | |
| #include "llvm/MC/MCInst.h" | |
| #include "llvm/MC/MCInstrInfo.h" | |
| #include "llvm/MC/MCSymbol.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/FormattedStream.h" | |
| using namespace llvm; | |
| using namespace llvm::SPIRV; | |
| #define DEBUG_TYPE "asm-printer" | |
| // Include the auto-generated portion of the assembly writer. | |
| #include "SPIRVGenAsmWriter.inc" | |
| void SPIRVInstPrinter::printRemainingVariableOps(const MCInst *MI, | |
| unsigned StartIndex, | |
| raw_ostream &O, | |
| bool SkipFirstSpace, | |
| bool SkipImmediates) { | |
| const unsigned NumOps = MI->getNumOperands(); | |
| for (unsigned i = StartIndex; i < NumOps; ++i) { | |
| if (!SkipImmediates || !MI->getOperand(i).isImm()) { | |
| if (!SkipFirstSpace || i != StartIndex) | |
| O << ' '; | |
| printOperand(MI, i, O); | |
| } | |
| } | |
| } | |
| void SPIRVInstPrinter::printOpConstantVarOps(const MCInst *MI, | |
| unsigned StartIndex, | |
| raw_ostream &O) { | |
| O << ' '; | |
| if (MI->getNumOperands() - StartIndex == 2) { // Handle 64 bit literals. | |
| uint64_t Imm = MI->getOperand(StartIndex).getImm(); | |
| Imm |= (MI->getOperand(StartIndex + 1).getImm() << 32); | |
| O << Imm; | |
| } else { | |
| printRemainingVariableOps(MI, StartIndex, O, true, false); | |
| } | |
| } | |
| void SPIRVInstPrinter::recordOpExtInstImport(const MCInst *MI) { | |
| Register Reg = MI->getOperand(0).getReg(); | |
| auto Name = getSPIRVStringOperand(*MI, 1); | |
| auto Set = getExtInstSetFromString(Name); | |
| ExtInstSetIDs.insert({Reg, Set}); | |
| } | |
| void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address, | |
| StringRef Annot, const MCSubtargetInfo &STI, | |
| raw_ostream &OS) { | |
| const unsigned OpCode = MI->getOpcode(); | |
| printInstruction(MI, Address, OS); | |
| if (OpCode == SPIRV::OpDecorate) { | |
| printOpDecorate(MI, OS); | |
| } else if (OpCode == SPIRV::OpExtInstImport) { | |
| recordOpExtInstImport(MI); | |
| } else if (OpCode == SPIRV::OpExtInst) { | |
| printOpExtInst(MI, OS); | |
| } else { | |
| // Print any extra operands for variadic instructions. | |
| const MCInstrDesc &MCDesc = MII.get(OpCode); | |
| if (MCDesc.isVariadic()) { | |
| const unsigned NumFixedOps = MCDesc.getNumOperands(); | |
| const unsigned LastFixedIndex = NumFixedOps - 1; | |
| const int FirstVariableIndex = NumFixedOps; | |
| if (NumFixedOps > 0 && MCDesc.operands()[LastFixedIndex].OperandType == | |
| MCOI::OPERAND_UNKNOWN) { | |
| // For instructions where a custom type (not reg or immediate) comes as | |
| // the last operand before the variable_ops. This is usually a StringImm | |
| // operand, but there are a few other cases. | |
| switch (OpCode) { | |
| case SPIRV::OpTypeImage: | |
| OS << ' '; | |
| printSymbolicOperand<OperandCategory::AccessQualifierOperand>( | |
| MI, FirstVariableIndex, OS); | |
| break; | |
| case SPIRV::OpVariable: | |
| OS << ' '; | |
| printOperand(MI, FirstVariableIndex, OS); | |
| break; | |
| case SPIRV::OpEntryPoint: { | |
| // Print the interface ID operands, skipping the name's string | |
| // literal. | |
| printRemainingVariableOps(MI, NumFixedOps, OS, false, true); | |
| break; | |
| } | |
| case SPIRV::OpExecutionMode: | |
| case SPIRV::OpExecutionModeId: | |
| case SPIRV::OpLoopMerge: { | |
| // Print any literals after the OPERAND_UNKNOWN argument normally. | |
| printRemainingVariableOps(MI, NumFixedOps, OS); | |
| break; | |
| } | |
| default: | |
| break; // printStringImm has already been handled. | |
| } | |
| } else { | |
| // For instructions with no fixed ops or a reg/immediate as the final | |
| // fixed operand, we can usually print the rest with "printOperand", but | |
| // check for a few cases with custom types first. | |
| switch (OpCode) { | |
| case SPIRV::OpLoad: | |
| case SPIRV::OpStore: | |
| OS << ' '; | |
| printSymbolicOperand<OperandCategory::MemoryOperandOperand>( | |
| MI, FirstVariableIndex, OS); | |
| printRemainingVariableOps(MI, FirstVariableIndex + 1, OS); | |
| break; | |
| case SPIRV::OpImageSampleImplicitLod: | |
| case SPIRV::OpImageSampleDrefImplicitLod: | |
| case SPIRV::OpImageSampleProjImplicitLod: | |
| case SPIRV::OpImageSampleProjDrefImplicitLod: | |
| case SPIRV::OpImageFetch: | |
| case SPIRV::OpImageGather: | |
| case SPIRV::OpImageDrefGather: | |
| case SPIRV::OpImageRead: | |
| case SPIRV::OpImageWrite: | |
| case SPIRV::OpImageSparseSampleImplicitLod: | |
| case SPIRV::OpImageSparseSampleDrefImplicitLod: | |
| case SPIRV::OpImageSparseSampleProjImplicitLod: | |
| case SPIRV::OpImageSparseSampleProjDrefImplicitLod: | |
| case SPIRV::OpImageSparseFetch: | |
| case SPIRV::OpImageSparseGather: | |
| case SPIRV::OpImageSparseDrefGather: | |
| case SPIRV::OpImageSparseRead: | |
| case SPIRV::OpImageSampleFootprintNV: | |
| OS << ' '; | |
| printSymbolicOperand<OperandCategory::ImageOperandOperand>( | |
| MI, FirstVariableIndex, OS); | |
| printRemainingVariableOps(MI, NumFixedOps + 1, OS); | |
| break; | |
| case SPIRV::OpCopyMemory: | |
| case SPIRV::OpCopyMemorySized: { | |
| const unsigned NumOps = MI->getNumOperands(); | |
| for (unsigned i = NumFixedOps; i < NumOps; ++i) { | |
| OS << ' '; | |
| printSymbolicOperand<OperandCategory::MemoryOperandOperand>(MI, i, | |
| OS); | |
| if (MI->getOperand(i).getImm() & MemoryOperand::Aligned) { | |
| assert(i + 1 < NumOps && "Missing alignment operand"); | |
| OS << ' '; | |
| printOperand(MI, i + 1, OS); | |
| i += 1; | |
| } | |
| } | |
| break; | |
| } | |
| case SPIRV::OpConstantI: | |
| case SPIRV::OpConstantF: | |
| printOpConstantVarOps(MI, NumFixedOps, OS); | |
| break; | |
| default: | |
| printRemainingVariableOps(MI, NumFixedOps, OS); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| printAnnotation(OS, Annot); | |
| } | |
| void SPIRVInstPrinter::printOpExtInst(const MCInst *MI, raw_ostream &O) { | |
| // The fixed operands have already been printed, so just need to decide what | |
| // type of ExtInst operands to print based on the instruction set and number. | |
| const MCInstrDesc &MCDesc = MII.get(MI->getOpcode()); | |
| unsigned NumFixedOps = MCDesc.getNumOperands(); | |
| const auto NumOps = MI->getNumOperands(); | |
| if (NumOps == NumFixedOps) | |
| return; | |
| O << ' '; | |
| // TODO: implement special printing for OpenCLExtInst::vstor*. | |
| printRemainingVariableOps(MI, NumFixedOps, O, true); | |
| } | |
| void SPIRVInstPrinter::printOpDecorate(const MCInst *MI, raw_ostream &O) { | |
| // The fixed operands have already been printed, so just need to decide what | |
| // type of decoration operands to print based on the Decoration type. | |
| const MCInstrDesc &MCDesc = MII.get(MI->getOpcode()); | |
| unsigned NumFixedOps = MCDesc.getNumOperands(); | |
| if (NumFixedOps != MI->getNumOperands()) { | |
| auto DecOp = MI->getOperand(NumFixedOps - 1); | |
| auto Dec = static_cast<Decoration::Decoration>(DecOp.getImm()); | |
| O << ' '; | |
| switch (Dec) { | |
| case Decoration::BuiltIn: | |
| printSymbolicOperand<OperandCategory::BuiltInOperand>(MI, NumFixedOps, O); | |
| break; | |
| case Decoration::UniformId: | |
| printSymbolicOperand<OperandCategory::ScopeOperand>(MI, NumFixedOps, O); | |
| break; | |
| case Decoration::FuncParamAttr: | |
| printSymbolicOperand<OperandCategory::FunctionParameterAttributeOperand>( | |
| MI, NumFixedOps, O); | |
| break; | |
| case Decoration::FPRoundingMode: | |
| printSymbolicOperand<OperandCategory::FPRoundingModeOperand>( | |
| MI, NumFixedOps, O); | |
| break; | |
| case Decoration::FPFastMathMode: | |
| printSymbolicOperand<OperandCategory::FPFastMathModeOperand>( | |
| MI, NumFixedOps, O); | |
| break; | |
| case Decoration::LinkageAttributes: | |
| case Decoration::UserSemantic: | |
| printStringImm(MI, NumFixedOps, O); | |
| break; | |
| default: | |
| printRemainingVariableOps(MI, NumFixedOps, O, true); | |
| break; | |
| } | |
| } | |
| } | |
| static void printExpr(const MCExpr *Expr, raw_ostream &O) { | |
| #ifndef NDEBUG | |
| const MCSymbolRefExpr *SRE; | |
| if (const MCBinaryExpr *BE = dyn_cast<MCBinaryExpr>(Expr)) | |
| SRE = cast<MCSymbolRefExpr>(BE->getLHS()); | |
| else | |
| SRE = cast<MCSymbolRefExpr>(Expr); | |
| MCSymbolRefExpr::VariantKind Kind = SRE->getKind(); | |
| assert(Kind == MCSymbolRefExpr::VK_None); | |
| #endif | |
| O << *Expr; | |
| } | |
| void SPIRVInstPrinter::printOperand(const MCInst *MI, unsigned OpNo, | |
| raw_ostream &O, const char *Modifier) { | |
| assert((Modifier == 0 || Modifier[0] == 0) && "No modifiers supported"); | |
| if (OpNo < MI->getNumOperands()) { | |
| const MCOperand &Op = MI->getOperand(OpNo); | |
| if (Op.isReg()) | |
| O << '%' << (Register::virtReg2Index(Op.getReg()) + 1); | |
| else if (Op.isImm()) | |
| O << formatImm((int64_t)Op.getImm()); | |
| else if (Op.isDFPImm()) | |
| O << formatImm((double)Op.getDFPImm()); | |
| else if (Op.isExpr()) | |
| printExpr(Op.getExpr(), O); | |
| else | |
| llvm_unreachable("Unexpected operand type"); | |
| } | |
| } | |
| void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo, | |
| raw_ostream &O) { | |
| const unsigned NumOps = MI->getNumOperands(); | |
| unsigned StrStartIndex = OpNo; | |
| while (StrStartIndex < NumOps) { | |
| if (MI->getOperand(StrStartIndex).isReg()) | |
| break; | |
| std::string Str = getSPIRVStringOperand(*MI, OpNo); | |
| if (StrStartIndex != OpNo) | |
| O << ' '; // Add a space if we're starting a new string/argument. | |
| O << '"'; | |
| for (char c : Str) { | |
| if (c == '"') | |
| O.write('\\'); // Escape " characters (might break for complex UTF-8). | |
| O.write(c); | |
| } | |
| O << '"'; | |
| unsigned numOpsInString = (Str.size() / 4) + 1; | |
| StrStartIndex += numOpsInString; | |
| // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute". | |
| if (MI->getOpcode() == SPIRV::OpDecorate && | |
| MI->getOperand(1).getImm() == | |
| static_cast<unsigned>(Decoration::LinkageAttributes)) { | |
| O << ' '; | |
| printSymbolicOperand<OperandCategory::LinkageTypeOperand>( | |
| MI, StrStartIndex, O); | |
| break; | |
| } | |
| } | |
| } | |
| void SPIRVInstPrinter::printExtension(const MCInst *MI, unsigned OpNo, | |
| raw_ostream &O) { | |
| auto SetReg = MI->getOperand(2).getReg(); | |
| auto Set = ExtInstSetIDs[SetReg]; | |
| auto Op = MI->getOperand(OpNo).getImm(); | |
| O << getExtInstName(Set, Op); | |
| } | |
| template <OperandCategory::OperandCategory category> | |
| void SPIRVInstPrinter::printSymbolicOperand(const MCInst *MI, unsigned OpNo, | |
| raw_ostream &O) { | |
| if (OpNo < MI->getNumOperands()) { | |
| O << getSymbolicOperandMnemonic(category, MI->getOperand(OpNo).getImm()); | |
| } | |
| } |