//===-- 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 "SPIRVInstrInfo.h" #include "llvm/ADT/APFloat.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) { unsigned IsBitwidth16 = MI->getFlags() & SPIRV::ASM_PRINTER_WIDTH16; const unsigned NumVarOps = MI->getNumOperands() - StartIndex; assert((NumVarOps == 1 || NumVarOps == 2) && "Unsupported number of bits for literal variable"); O << ' '; uint64_t Imm = MI->getOperand(StartIndex).getImm(); // Handle 64 bit literals. if (NumVarOps == 2) { Imm |= (MI->getOperand(StartIndex + 1).getImm() << 32); } // Format and print float values. if (MI->getOpcode() == SPIRV::OpConstantF && IsBitwidth16 == 0) { APFloat FP = NumVarOps == 1 ? APFloat(APInt(32, Imm).bitsToFloat()) : APFloat(APInt(64, Imm).bitsToDouble()); // Print infinity and NaN as hex floats. // TODO: Make sure subnormal numbers are handled correctly as they may also // require hex float notation. if (FP.isInfinity()) { if (FP.isNegative()) O << '-'; O << "0x1p+128"; return; } if (FP.isNaN()) { O << "0x1.8p+128"; return; } // Format val as a decimal floating point or scientific notation (whichever // is shorter), with enough digits of precision to produce the exact value. O << format("%.*g", std::numeric_limits::max_digits10, FP.convertToDouble()); return; } // Print integer values directly. O << Imm; } 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( 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( 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( 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(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: // The last fixed operand along with any variadic operands that follow // are part of the variable value. printOpConstantVarOps(MI, NumFixedOps - 1, 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(DecOp.getImm()); O << ' '; switch (Dec) { case Decoration::BuiltIn: printSymbolicOperand(MI, NumFixedOps, O); break; case Decoration::UniformId: printSymbolicOperand(MI, NumFixedOps, O); break; case Decoration::FuncParamAttr: printSymbolicOperand( MI, NumFixedOps, O); break; case Decoration::FPRoundingMode: printSymbolicOperand( MI, NumFixedOps, O); break; case Decoration::FPFastMathMode: printSymbolicOperand( MI, NumFixedOps, O); break; case Decoration::LinkageAttributes: case Decoration::UserSemantic: printStringImm(MI, NumFixedOps, O); break; case Decoration::HostAccessINTEL: printOperand(MI, NumFixedOps, O); if (NumFixedOps + 1 < MI->getNumOperands()) { O << ' '; printStringImm(MI, NumFixedOps + 1, 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(Expr)) SRE = cast(BE->getLHS()); else SRE = cast(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, StrStartIndex); if (StrStartIndex != OpNo) O << ' '; // Add a space if we're starting a new string/argument. O << '"'; for (char c : Str) { // Escape ", \n characters (might break for complex UTF-8). if (c == '\n') { O.write("\\n", 2); } else { if (c == '"') O.write('\\'); 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(Decoration::LinkageAttributes)) { O << ' '; printSymbolicOperand( 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 void SPIRVInstPrinter::printSymbolicOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O) { if (OpNo < MI->getNumOperands()) { O << getSymbolicOperandMnemonic(category, MI->getOperand(OpNo).getImm()); } }