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