xref: /freebsd/contrib/llvm-project/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
1 //===-- SPIRVInstPrinter.cpp - Output SPIR-V MCInsts as ASM -----*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This class prints a SPIR-V MCInst to a .s file.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "SPIRVInstPrinter.h"
14 #include "SPIRV.h"
15 #include "SPIRVBaseInfo.h"
16 #include "llvm/ADT/APFloat.h"
17 #include "llvm/MC/MCAsmInfo.h"
18 #include "llvm/MC/MCExpr.h"
19 #include "llvm/MC/MCInst.h"
20 #include "llvm/MC/MCInstrInfo.h"
21 #include "llvm/MC/MCSymbol.h"
22 #include "llvm/Support/ErrorHandling.h"
23 
24 using namespace llvm;
25 using namespace llvm::SPIRV;
26 
27 #define DEBUG_TYPE "asm-printer"
28 
29 // Include the auto-generated portion of the assembly writer.
30 #include "SPIRVGenAsmWriter.inc"
31 
printRemainingVariableOps(const MCInst * MI,unsigned StartIndex,raw_ostream & O,bool SkipFirstSpace,bool SkipImmediates)32 void SPIRVInstPrinter::printRemainingVariableOps(const MCInst *MI,
33                                                  unsigned StartIndex,
34                                                  raw_ostream &O,
35                                                  bool SkipFirstSpace,
36                                                  bool SkipImmediates) {
37   const unsigned NumOps = MI->getNumOperands();
38   for (unsigned i = StartIndex; i < NumOps; ++i) {
39     if (!SkipImmediates || !MI->getOperand(i).isImm()) {
40       if (!SkipFirstSpace || i != StartIndex)
41         O << ' ';
42       printOperand(MI, i, O);
43     }
44   }
45 }
46 
printOpConstantVarOps(const MCInst * MI,unsigned StartIndex,raw_ostream & O)47 void SPIRVInstPrinter::printOpConstantVarOps(const MCInst *MI,
48                                              unsigned StartIndex,
49                                              raw_ostream &O) {
50   unsigned IsBitwidth16 = MI->getFlags() & SPIRV::INST_PRINTER_WIDTH16;
51   const unsigned NumVarOps = MI->getNumOperands() - StartIndex;
52 
53   assert((NumVarOps == 1 || NumVarOps == 2) &&
54          "Unsupported number of bits for literal variable");
55 
56   O << ' ';
57 
58   uint64_t Imm = MI->getOperand(StartIndex).getImm();
59 
60   // Handle 64 bit literals.
61   if (NumVarOps == 2) {
62     Imm |= (MI->getOperand(StartIndex + 1).getImm() << 32);
63   }
64 
65   // Format and print float values.
66   if (MI->getOpcode() == SPIRV::OpConstantF && IsBitwidth16 == 0) {
67     APFloat FP = NumVarOps == 1 ? APFloat(APInt(32, Imm).bitsToFloat())
68                                 : APFloat(APInt(64, Imm).bitsToDouble());
69 
70     // Print infinity and NaN as hex floats.
71     // TODO: Make sure subnormal numbers are handled correctly as they may also
72     // require hex float notation.
73     if (FP.isInfinity()) {
74       if (FP.isNegative())
75         O << '-';
76       O << "0x1p+128";
77       return;
78     }
79     if (FP.isNaN()) {
80       O << "0x1.8p+128";
81       return;
82     }
83 
84     // Format val as a decimal floating point or scientific notation (whichever
85     // is shorter), with enough digits of precision to produce the exact value.
86     O << format("%.*g", std::numeric_limits<double>::max_digits10,
87                 FP.convertToDouble());
88 
89     return;
90   }
91 
92   // Print integer values directly.
93   O << Imm;
94 }
95 
recordOpExtInstImport(const MCInst * MI)96 void SPIRVInstPrinter::recordOpExtInstImport(const MCInst *MI) {
97   MCRegister Reg = MI->getOperand(0).getReg();
98   auto Name = getSPIRVStringOperand(*MI, 1);
99   auto Set = getExtInstSetFromString(Name);
100   ExtInstSetIDs.insert({Reg, Set});
101 }
102 
printInst(const MCInst * MI,uint64_t Address,StringRef Annot,const MCSubtargetInfo & STI,raw_ostream & OS)103 void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address,
104                                  StringRef Annot, const MCSubtargetInfo &STI,
105                                  raw_ostream &OS) {
106   const unsigned OpCode = MI->getOpcode();
107   printInstruction(MI, Address, OS);
108 
109   if (OpCode == SPIRV::OpDecorate) {
110     printOpDecorate(MI, OS);
111   } else if (OpCode == SPIRV::OpExtInstImport) {
112     recordOpExtInstImport(MI);
113   } else if (OpCode == SPIRV::OpExtInst) {
114     printOpExtInst(MI, OS);
115   } else if (OpCode == SPIRV::UNKNOWN_type) {
116     printUnknownType(MI, OS);
117   } else {
118     // Print any extra operands for variadic instructions.
119     const MCInstrDesc &MCDesc = MII.get(OpCode);
120     if (MCDesc.isVariadic()) {
121       const unsigned NumFixedOps = MCDesc.getNumOperands();
122       const unsigned LastFixedIndex = NumFixedOps - 1;
123       const int FirstVariableIndex = NumFixedOps;
124       if (NumFixedOps > 0 && MCDesc.operands()[LastFixedIndex].OperandType ==
125                                  MCOI::OPERAND_UNKNOWN) {
126         // For instructions where a custom type (not reg or immediate) comes as
127         // the last operand before the variable_ops. This is usually a StringImm
128         // operand, but there are a few other cases.
129         switch (OpCode) {
130         case SPIRV::OpTypeImage:
131           OS << ' ';
132           printSymbolicOperand<OperandCategory::AccessQualifierOperand>(
133               MI, FirstVariableIndex, OS);
134           break;
135         case SPIRV::OpVariable:
136           OS << ' ';
137           printOperand(MI, FirstVariableIndex, OS);
138           break;
139         case SPIRV::OpEntryPoint: {
140           // Print the interface ID operands, skipping the name's string
141           // literal.
142           printRemainingVariableOps(MI, NumFixedOps, OS, false, true);
143           break;
144         }
145         case SPIRV::OpMemberDecorate:
146           printRemainingVariableOps(MI, NumFixedOps, OS);
147           break;
148         case SPIRV::OpExecutionMode:
149         case SPIRV::OpExecutionModeId:
150         case SPIRV::OpLoopMerge: {
151           // Print any literals after the OPERAND_UNKNOWN argument normally.
152           printRemainingVariableOps(MI, NumFixedOps, OS);
153           break;
154         }
155         default:
156           break; // printStringImm has already been handled.
157         }
158       } else {
159         // For instructions with no fixed ops or a reg/immediate as the final
160         // fixed operand, we can usually print the rest with "printOperand", but
161         // check for a few cases with custom types first.
162         switch (OpCode) {
163         case SPIRV::OpLoad:
164         case SPIRV::OpStore:
165           OS << ' ';
166           printSymbolicOperand<OperandCategory::MemoryOperandOperand>(
167               MI, FirstVariableIndex, OS);
168           printRemainingVariableOps(MI, FirstVariableIndex + 1, OS);
169           break;
170         case SPIRV::OpImageSampleImplicitLod:
171         case SPIRV::OpImageSampleDrefImplicitLod:
172         case SPIRV::OpImageSampleProjImplicitLod:
173         case SPIRV::OpImageSampleProjDrefImplicitLod:
174         case SPIRV::OpImageFetch:
175         case SPIRV::OpImageGather:
176         case SPIRV::OpImageDrefGather:
177         case SPIRV::OpImageRead:
178         case SPIRV::OpImageWrite:
179         case SPIRV::OpImageSparseSampleImplicitLod:
180         case SPIRV::OpImageSparseSampleDrefImplicitLod:
181         case SPIRV::OpImageSparseSampleProjImplicitLod:
182         case SPIRV::OpImageSparseSampleProjDrefImplicitLod:
183         case SPIRV::OpImageSparseFetch:
184         case SPIRV::OpImageSparseGather:
185         case SPIRV::OpImageSparseDrefGather:
186         case SPIRV::OpImageSparseRead:
187         case SPIRV::OpImageSampleFootprintNV:
188           OS << ' ';
189           printSymbolicOperand<OperandCategory::ImageOperandOperand>(
190               MI, FirstVariableIndex, OS);
191           printRemainingVariableOps(MI, NumFixedOps + 1, OS);
192           break;
193         case SPIRV::OpCopyMemory:
194         case SPIRV::OpCopyMemorySized: {
195           const unsigned NumOps = MI->getNumOperands();
196           for (unsigned i = NumFixedOps; i < NumOps; ++i) {
197             OS << ' ';
198             printSymbolicOperand<OperandCategory::MemoryOperandOperand>(MI, i,
199                                                                         OS);
200             if (MI->getOperand(i).getImm() & MemoryOperand::Aligned) {
201               assert(i + 1 < NumOps && "Missing alignment operand");
202               OS << ' ';
203               printOperand(MI, i + 1, OS);
204               i += 1;
205             }
206           }
207           break;
208         }
209         case SPIRV::OpConstantI:
210         case SPIRV::OpConstantF:
211           // The last fixed operand along with any variadic operands that follow
212           // are part of the variable value.
213           printOpConstantVarOps(MI, NumFixedOps - 1, OS);
214           break;
215         case SPIRV::OpCooperativeMatrixMulAddKHR: {
216           const unsigned NumOps = MI->getNumOperands();
217           if (NumFixedOps == NumOps)
218             break;
219 
220           OS << ' ';
221           const unsigned MulAddOp = MI->getOperand(FirstVariableIndex).getImm();
222           if (MulAddOp == 0) {
223             printSymbolicOperand<
224                 OperandCategory::CooperativeMatrixOperandsOperand>(
225                 MI, FirstVariableIndex, OS);
226           } else {
227             std::string Buffer;
228             for (unsigned Mask = 0x1;
229                  Mask != SPIRV::CooperativeMatrixOperands::
230                              MatrixResultBFloat16ComponentsINTEL;
231                  Mask <<= 1) {
232               if (MulAddOp & Mask) {
233                 if (!Buffer.empty())
234                   Buffer += '|';
235                 Buffer += getSymbolicOperandMnemonic(
236                     OperandCategory::CooperativeMatrixOperandsOperand, Mask);
237               }
238             }
239             OS << Buffer;
240           }
241           break;
242         }
243         case SPIRV::OpSubgroupMatrixMultiplyAccumulateINTEL: {
244           const unsigned NumOps = MI->getNumOperands();
245           if (NumFixedOps >= NumOps)
246             break;
247           OS << ' ';
248           const unsigned Flags = MI->getOperand(NumOps - 1).getImm();
249           if (Flags == 0) {
250             printSymbolicOperand<
251                 OperandCategory::MatrixMultiplyAccumulateOperandsOperand>(
252                 MI, NumOps - 1, OS);
253           } else {
254             std::string Buffer;
255             for (unsigned Mask = 0x1;
256                  Mask <= SPIRV::MatrixMultiplyAccumulateOperands::
257                              MatrixBPackedBFloat16INTEL;
258                  Mask <<= 1) {
259               if (Flags & Mask) {
260                 if (!Buffer.empty())
261                   Buffer += '|';
262                 Buffer += getSymbolicOperandMnemonic(
263                     OperandCategory::MatrixMultiplyAccumulateOperandsOperand,
264                     Mask);
265               }
266             }
267             OS << Buffer;
268           }
269           break;
270         }
271         default:
272           printRemainingVariableOps(MI, NumFixedOps, OS);
273           break;
274         }
275       }
276     }
277   }
278 
279   printAnnotation(OS, Annot);
280 }
281 
printOpExtInst(const MCInst * MI,raw_ostream & O)282 void SPIRVInstPrinter::printOpExtInst(const MCInst *MI, raw_ostream &O) {
283   // The fixed operands have already been printed, so just need to decide what
284   // type of ExtInst operands to print based on the instruction set and number.
285   const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
286   unsigned NumFixedOps = MCDesc.getNumOperands();
287   const auto NumOps = MI->getNumOperands();
288   if (NumOps == NumFixedOps)
289     return;
290 
291   O << ' ';
292 
293   // TODO: implement special printing for OpenCLExtInst::vstor*.
294   printRemainingVariableOps(MI, NumFixedOps, O, true);
295 }
296 
printOpDecorate(const MCInst * MI,raw_ostream & O)297 void SPIRVInstPrinter::printOpDecorate(const MCInst *MI, raw_ostream &O) {
298   // The fixed operands have already been printed, so just need to decide what
299   // type of decoration operands to print based on the Decoration type.
300   const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
301   unsigned NumFixedOps = MCDesc.getNumOperands();
302 
303   if (NumFixedOps != MI->getNumOperands()) {
304     auto DecOp = MI->getOperand(NumFixedOps - 1);
305     auto Dec = static_cast<Decoration::Decoration>(DecOp.getImm());
306 
307     O << ' ';
308 
309     switch (Dec) {
310     case Decoration::BuiltIn:
311       printSymbolicOperand<OperandCategory::BuiltInOperand>(MI, NumFixedOps, O);
312       break;
313     case Decoration::UniformId:
314       printSymbolicOperand<OperandCategory::ScopeOperand>(MI, NumFixedOps, O);
315       break;
316     case Decoration::FuncParamAttr:
317       printSymbolicOperand<OperandCategory::FunctionParameterAttributeOperand>(
318           MI, NumFixedOps, O);
319       break;
320     case Decoration::FPRoundingMode:
321       printSymbolicOperand<OperandCategory::FPRoundingModeOperand>(
322           MI, NumFixedOps, O);
323       break;
324     case Decoration::FPFastMathMode:
325       printSymbolicOperand<OperandCategory::FPFastMathModeOperand>(
326           MI, NumFixedOps, O);
327       break;
328     case Decoration::LinkageAttributes:
329     case Decoration::UserSemantic:
330       printStringImm(MI, NumFixedOps, O);
331       break;
332     case Decoration::HostAccessINTEL:
333       printOperand(MI, NumFixedOps, O);
334       if (NumFixedOps + 1 < MI->getNumOperands()) {
335         O << ' ';
336         printStringImm(MI, NumFixedOps + 1, O);
337       }
338       break;
339     default:
340       printRemainingVariableOps(MI, NumFixedOps, O, true);
341       break;
342     }
343   }
344 }
345 
printUnknownType(const MCInst * MI,raw_ostream & O)346 void SPIRVInstPrinter::printUnknownType(const MCInst *MI, raw_ostream &O) {
347   const auto EnumOperand = MI->getOperand(1);
348   assert(EnumOperand.isImm() &&
349          "second operand of UNKNOWN_type must be opcode!");
350 
351   const auto Enumerant = EnumOperand.getImm();
352   const auto NumOps = MI->getNumOperands();
353 
354   // Print the opcode using the spirv-as unknown opcode syntax
355   O << "OpUnknown(" << Enumerant << ", " << NumOps << ") ";
356 
357   // The result ID must be printed after the opcode when using this syntax
358   printOperand(MI, 0, O);
359 
360   O << " ";
361 
362   const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
363   unsigned NumFixedOps = MCDesc.getNumOperands();
364   if (NumOps == NumFixedOps)
365     return;
366 
367   // Print the rest of the operands
368   printRemainingVariableOps(MI, NumFixedOps, O, true);
369 }
370 
printOperand(const MCInst * MI,unsigned OpNo,raw_ostream & O)371 void SPIRVInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
372                                     raw_ostream &O) {
373   if (OpNo < MI->getNumOperands()) {
374     const MCOperand &Op = MI->getOperand(OpNo);
375     if (Op.isReg())
376       O << '%' << (getIDFromRegister(Op.getReg().id()) + 1);
377     else if (Op.isImm())
378       O << formatImm((int64_t)Op.getImm());
379     else if (Op.isDFPImm())
380       O << formatImm((double)Op.getDFPImm());
381     else if (Op.isExpr())
382       MAI.printExpr(O, *Op.getExpr());
383     else
384       llvm_unreachable("Unexpected operand type");
385   }
386 }
387 
printStringImm(const MCInst * MI,unsigned OpNo,raw_ostream & O)388 void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo,
389                                       raw_ostream &O) {
390   const unsigned NumOps = MI->getNumOperands();
391   unsigned StrStartIndex = OpNo;
392   while (StrStartIndex < NumOps) {
393     if (MI->getOperand(StrStartIndex).isReg())
394       break;
395 
396     std::string Str = getSPIRVStringOperand(*MI, StrStartIndex);
397     if (StrStartIndex != OpNo)
398       O << ' '; // Add a space if we're starting a new string/argument.
399     O << '"';
400     for (char c : Str) {
401       // Escape ", \n characters (might break for complex UTF-8).
402       if (c == '\n') {
403         O.write("\\n", 2);
404       } else {
405         if (c == '"')
406           O.write('\\');
407         O.write(c);
408       }
409     }
410     O << '"';
411 
412     unsigned numOpsInString = (Str.size() / 4) + 1;
413     StrStartIndex += numOpsInString;
414 
415     // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute".
416     if (MI->getOpcode() == SPIRV::OpDecorate &&
417         MI->getOperand(1).getImm() ==
418             static_cast<unsigned>(Decoration::LinkageAttributes)) {
419       O << ' ';
420       printSymbolicOperand<OperandCategory::LinkageTypeOperand>(
421           MI, StrStartIndex, O);
422       break;
423     }
424   }
425 }
426 
printExtension(const MCInst * MI,unsigned OpNo,raw_ostream & O)427 void SPIRVInstPrinter::printExtension(const MCInst *MI, unsigned OpNo,
428                                       raw_ostream &O) {
429   auto SetReg = MI->getOperand(2).getReg();
430   auto Set = ExtInstSetIDs[SetReg];
431   auto Op = MI->getOperand(OpNo).getImm();
432   O << getExtInstName(Set, Op);
433 }
434 
435 template <OperandCategory::OperandCategory category>
printSymbolicOperand(const MCInst * MI,unsigned OpNo,raw_ostream & O)436 void SPIRVInstPrinter::printSymbolicOperand(const MCInst *MI, unsigned OpNo,
437                                             raw_ostream &O) {
438   if (OpNo < MI->getNumOperands()) {
439     O << getSymbolicOperandMnemonic(category, MI->getOperand(OpNo).getImm());
440   }
441 }
442