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
24using namespace llvm;
25using 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
32void 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, OpNo: i, O);
43 }
44 }
45}
46
47void 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 if (MI->getOpcode() == SPIRV::OpConstantI && NumVarOps > 2) {
54 // SPV_ALTERA_arbitrary_precision_integers allows for integer widths greater
55 // than 64, which will be encoded via multiple operands.
56 for (unsigned I = StartIndex; I != MI->getNumOperands(); ++I)
57 O << ' ' << MI->getOperand(i: I).getImm();
58 return;
59 }
60
61 assert((NumVarOps == 1 || NumVarOps == 2) &&
62 "Unsupported number of bits for literal variable");
63
64 O << ' ';
65
66 uint64_t Imm = MI->getOperand(i: StartIndex).getImm();
67
68 // Handle 64 bit literals.
69 if (NumVarOps == 2) {
70 Imm |= (MI->getOperand(i: StartIndex + 1).getImm() << 32);
71 }
72
73 // Format and print float values.
74 if (MI->getOpcode() == SPIRV::OpConstantF && IsBitwidth16 == 0) {
75 APFloat FP = NumVarOps == 1 ? APFloat(APInt(32, Imm).bitsToFloat())
76 : APFloat(APInt(64, Imm).bitsToDouble());
77
78 // Print infinity and NaN as hex floats.
79 // TODO: Make sure subnormal numbers are handled correctly as they may also
80 // require hex float notation.
81 if (FP.isInfinity()) {
82 if (FP.isNegative())
83 O << '-';
84 O << "0x1p+128";
85 return;
86 }
87 if (FP.isNaN()) {
88 O << "0x1.8p+128";
89 return;
90 }
91
92 // Format val as a decimal floating point or scientific notation (whichever
93 // is shorter), with enough digits of precision to produce the exact value.
94 O << format(Fmt: "%.*g", Vals: std::numeric_limits<double>::max_digits10,
95 Vals: FP.convertToDouble());
96
97 return;
98 }
99
100 // Print integer values directly.
101 O << Imm;
102}
103
104void SPIRVInstPrinter::recordOpExtInstImport(const MCInst *MI) {
105 MCRegister Reg = MI->getOperand(i: 0).getReg();
106 auto Name = getSPIRVStringOperand(MI: *MI, StartIndex: 1);
107 auto Set = getExtInstSetFromString(SetName: std::move(Name));
108 ExtInstSetIDs.insert(KV: {Reg, Set});
109}
110
111void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address,
112 StringRef Annot, const MCSubtargetInfo &STI,
113 raw_ostream &OS) {
114 const unsigned OpCode = MI->getOpcode();
115 printInstruction(MI, Address, O&: OS);
116
117 if (OpCode == SPIRV::OpDecorate) {
118 printOpDecorate(MI, O&: OS);
119 } else if (OpCode == SPIRV::OpExtInstImport) {
120 recordOpExtInstImport(MI);
121 } else if (OpCode == SPIRV::OpExtInst) {
122 printOpExtInst(MI, O&: OS);
123 } else if (OpCode == SPIRV::UNKNOWN_type) {
124 printUnknownType(MI, O&: OS);
125 } else {
126 // Print any extra operands for variadic instructions.
127 const MCInstrDesc &MCDesc = MII.get(Opcode: OpCode);
128 if (MCDesc.isVariadic()) {
129 const unsigned NumFixedOps = MCDesc.getNumOperands();
130 const unsigned LastFixedIndex = NumFixedOps - 1;
131 const int FirstVariableIndex = NumFixedOps;
132 if (NumFixedOps > 0 && MCDesc.operands()[LastFixedIndex].OperandType ==
133 MCOI::OPERAND_UNKNOWN) {
134 // For instructions where a custom type (not reg or immediate) comes as
135 // the last operand before the variable_ops. This is usually a StringImm
136 // operand, but there are a few other cases.
137 switch (OpCode) {
138 case SPIRV::OpTypeImage:
139 OS << ' ';
140 printSymbolicOperand<OperandCategory::AccessQualifierOperand>(
141 MI, OpNo: FirstVariableIndex, O&: OS);
142 break;
143 case SPIRV::OpVariable:
144 OS << ' ';
145 printOperand(MI, OpNo: FirstVariableIndex, O&: OS);
146 break;
147 case SPIRV::OpEntryPoint: {
148 // Print the interface ID operands, skipping the name's string
149 // literal.
150 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O&: OS, SkipFirstSpace: false, SkipImmediates: true);
151 break;
152 }
153 case SPIRV::OpMemberDecorate:
154 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O&: OS);
155 break;
156 case SPIRV::OpExecutionMode:
157 case SPIRV::OpExecutionModeId:
158 case SPIRV::OpLoopMerge:
159 case SPIRV::OpLoopControlINTEL: {
160 // Print any literals after the OPERAND_UNKNOWN argument normally.
161 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O&: OS);
162 break;
163 }
164 default:
165 break; // printStringImm has already been handled.
166 }
167 } else {
168 // For instructions with no fixed ops or a reg/immediate as the final
169 // fixed operand, we can usually print the rest with "printOperand", but
170 // check for a few cases with custom types first.
171 switch (OpCode) {
172 case SPIRV::OpLoad:
173 case SPIRV::OpStore:
174 OS << ' ';
175 printSymbolicOperand<OperandCategory::MemoryOperandOperand>(
176 MI, OpNo: FirstVariableIndex, O&: OS);
177 printRemainingVariableOps(MI, StartIndex: FirstVariableIndex + 1, O&: OS);
178 break;
179 case SPIRV::OpSwitch:
180 if (MI->getFlags() & SPIRV::INST_PRINTER_WIDTH64) {
181 // In binary format 64-bit types are split into two 32-bit operands,
182 // but in text format combine these into a single 64-bit value as
183 // this is what tools such as spirv-as require.
184 const unsigned NumOps = MI->getNumOperands();
185 for (unsigned OpIdx = NumFixedOps; OpIdx < NumOps;) {
186 if (OpIdx + 1 >= NumOps || !MI->getOperand(i: OpIdx).isImm() ||
187 !MI->getOperand(i: OpIdx + 1).isImm()) {
188 llvm_unreachable("Unexpected OpSwitch operands");
189 continue;
190 }
191 OS << ' ';
192 uint64_t LowBits = MI->getOperand(i: OpIdx).getImm();
193 uint64_t HighBits = MI->getOperand(i: OpIdx + 1).getImm();
194 uint64_t CombinedValue = (HighBits << 32) | LowBits;
195 OS << formatImm(Value: CombinedValue);
196 OpIdx += 2;
197
198 // Next should be the label
199 if (OpIdx < NumOps) {
200 OS << ' ';
201 printOperand(MI, OpNo: OpIdx, O&: OS);
202 OpIdx++;
203 }
204 }
205 } else {
206 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O&: OS);
207 }
208 break;
209 case SPIRV::OpImageSampleImplicitLod:
210 case SPIRV::OpImageSampleDrefImplicitLod:
211 case SPIRV::OpImageSampleProjImplicitLod:
212 case SPIRV::OpImageSampleProjDrefImplicitLod:
213 case SPIRV::OpImageFetch:
214 case SPIRV::OpImageGather:
215 case SPIRV::OpImageDrefGather:
216 case SPIRV::OpImageRead:
217 case SPIRV::OpImageWrite:
218 case SPIRV::OpImageSparseSampleImplicitLod:
219 case SPIRV::OpImageSparseSampleDrefImplicitLod:
220 case SPIRV::OpImageSparseSampleProjImplicitLod:
221 case SPIRV::OpImageSparseSampleProjDrefImplicitLod:
222 case SPIRV::OpImageSparseFetch:
223 case SPIRV::OpImageSparseGather:
224 case SPIRV::OpImageSparseDrefGather:
225 case SPIRV::OpImageSparseRead:
226 case SPIRV::OpImageSampleFootprintNV:
227 OS << ' ';
228 printSymbolicOperand<OperandCategory::ImageOperandOperand>(
229 MI, OpNo: FirstVariableIndex, O&: OS);
230 printRemainingVariableOps(MI, StartIndex: NumFixedOps + 1, O&: OS);
231 break;
232 case SPIRV::OpCopyMemory:
233 case SPIRV::OpCopyMemorySized: {
234 const unsigned NumOps = MI->getNumOperands();
235 for (unsigned i = NumFixedOps; i < NumOps; ++i) {
236 OS << ' ';
237 printSymbolicOperand<OperandCategory::MemoryOperandOperand>(MI, OpNo: i,
238 O&: OS);
239 if (MI->getOperand(i).getImm() & MemoryOperand::Aligned) {
240 assert(i + 1 < NumOps && "Missing alignment operand");
241 OS << ' ';
242 printOperand(MI, OpNo: i + 1, O&: OS);
243 i += 1;
244 }
245 }
246 break;
247 }
248 case SPIRV::OpConstantI:
249 case SPIRV::OpConstantF:
250 // The last fixed operand along with any variadic operands that follow
251 // are part of the variable value.
252 assert(NumFixedOps > 0 && "Expected at least one fixed operand");
253 printOpConstantVarOps(MI, StartIndex: NumFixedOps - 1, O&: OS);
254 break;
255 case SPIRV::OpCooperativeMatrixMulAddKHR: {
256 const unsigned NumOps = MI->getNumOperands();
257 if (NumFixedOps == NumOps)
258 break;
259
260 OS << ' ';
261 const unsigned MulAddOp = MI->getOperand(i: FirstVariableIndex).getImm();
262 if (MulAddOp == 0) {
263 printSymbolicOperand<
264 OperandCategory::CooperativeMatrixOperandsOperand>(
265 MI, OpNo: FirstVariableIndex, O&: OS);
266 } else {
267 std::string Buffer;
268 for (unsigned Mask = 0x1;
269 Mask != SPIRV::CooperativeMatrixOperands::
270 MatrixResultBFloat16ComponentsINTEL;
271 Mask <<= 1) {
272 if (MulAddOp & Mask) {
273 if (!Buffer.empty())
274 Buffer += '|';
275 Buffer += getSymbolicOperandMnemonic(
276 Category: OperandCategory::CooperativeMatrixOperandsOperand, Value: Mask);
277 }
278 }
279 OS << Buffer;
280 }
281 break;
282 }
283 case SPIRV::OpSubgroupMatrixMultiplyAccumulateINTEL: {
284 const unsigned NumOps = MI->getNumOperands();
285 if (NumFixedOps >= NumOps)
286 break;
287 OS << ' ';
288 const unsigned Flags = MI->getOperand(i: NumOps - 1).getImm();
289 if (Flags == 0) {
290 printSymbolicOperand<
291 OperandCategory::MatrixMultiplyAccumulateOperandsOperand>(
292 MI, OpNo: NumOps - 1, O&: OS);
293 } else {
294 std::string Buffer;
295 for (unsigned Mask = 0x1;
296 Mask <= SPIRV::MatrixMultiplyAccumulateOperands::
297 MatrixBPackedBFloat16INTEL;
298 Mask <<= 1) {
299 if (Flags & Mask) {
300 if (!Buffer.empty())
301 Buffer += '|';
302 Buffer += getSymbolicOperandMnemonic(
303 Category: OperandCategory::MatrixMultiplyAccumulateOperandsOperand,
304 Value: Mask);
305 }
306 }
307 OS << Buffer;
308 }
309 break;
310 }
311 case SPIRV::OpSDot:
312 case SPIRV::OpUDot:
313 case SPIRV::OpSUDot:
314 case SPIRV::OpSDotAccSat:
315 case SPIRV::OpUDotAccSat:
316 case SPIRV::OpSUDotAccSat: {
317 const unsigned NumOps = MI->getNumOperands();
318 if (NumOps > NumFixedOps) {
319 OS << ' ';
320 printSymbolicOperand<OperandCategory::PackedVectorFormatsOperand>(
321 MI, OpNo: NumOps - 1, O&: OS);
322 break;
323 }
324 break;
325 }
326 case SPIRV::OpPredicatedLoadINTEL:
327 case SPIRV::OpPredicatedStoreINTEL: {
328 const unsigned NumOps = MI->getNumOperands();
329 if (NumOps > NumFixedOps) {
330 OS << ' ';
331 printSymbolicOperand<OperandCategory::MemoryOperandOperand>(
332 MI, OpNo: NumOps - 1, O&: OS);
333 break;
334 }
335 break;
336 }
337 default:
338 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O&: OS);
339 break;
340 }
341 }
342 }
343 }
344
345 printAnnotation(OS, Annot);
346}
347
348void SPIRVInstPrinter::printOpExtInst(const MCInst *MI, raw_ostream &O) {
349 // The fixed operands have already been printed, so just need to decide what
350 // type of ExtInst operands to print based on the instruction set and number.
351 const MCInstrDesc &MCDesc = MII.get(Opcode: MI->getOpcode());
352 unsigned NumFixedOps = MCDesc.getNumOperands();
353 const auto NumOps = MI->getNumOperands();
354 if (NumOps == NumFixedOps)
355 return;
356
357 O << ' ';
358
359 // TODO: implement special printing for OpenCLExtInst::vstor*.
360 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O, SkipFirstSpace: true);
361}
362
363void SPIRVInstPrinter::printOpDecorate(const MCInst *MI, raw_ostream &O) {
364 // The fixed operands have already been printed, so just need to decide what
365 // type of decoration operands to print based on the Decoration type.
366 const MCInstrDesc &MCDesc = MII.get(Opcode: MI->getOpcode());
367 unsigned NumFixedOps = MCDesc.getNumOperands();
368
369 if (NumFixedOps != MI->getNumOperands()) {
370 auto DecOp = MI->getOperand(i: NumFixedOps - 1);
371 auto Dec = static_cast<Decoration::Decoration>(DecOp.getImm());
372
373 O << ' ';
374
375 switch (Dec) {
376 case Decoration::BuiltIn:
377 printSymbolicOperand<OperandCategory::BuiltInOperand>(MI, OpNo: NumFixedOps, O);
378 break;
379 case Decoration::UniformId:
380 printSymbolicOperand<OperandCategory::ScopeOperand>(MI, OpNo: NumFixedOps, O);
381 break;
382 case Decoration::FuncParamAttr:
383 printSymbolicOperand<OperandCategory::FunctionParameterAttributeOperand>(
384 MI, OpNo: NumFixedOps, O);
385 break;
386 case Decoration::FPRoundingMode:
387 printSymbolicOperand<OperandCategory::FPRoundingModeOperand>(
388 MI, OpNo: NumFixedOps, O);
389 break;
390 case Decoration::FPFastMathMode:
391 printSymbolicOperand<OperandCategory::FPFastMathModeOperand>(
392 MI, OpNo: NumFixedOps, O);
393 break;
394 case Decoration::LinkageAttributes:
395 case Decoration::UserSemantic:
396 printStringImm(MI, OpNo: NumFixedOps, O);
397 break;
398 case Decoration::HostAccessINTEL:
399 printOperand(MI, OpNo: NumFixedOps, O);
400 if (NumFixedOps + 1 < MI->getNumOperands()) {
401 O << ' ';
402 printStringImm(MI, OpNo: NumFixedOps + 1, O);
403 }
404 break;
405 default:
406 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O, SkipFirstSpace: true);
407 break;
408 }
409 }
410}
411
412void SPIRVInstPrinter::printUnknownType(const MCInst *MI, raw_ostream &O) {
413 const auto EnumOperand = MI->getOperand(i: 1);
414 assert(EnumOperand.isImm() &&
415 "second operand of UNKNOWN_type must be opcode!");
416
417 const auto Enumerant = EnumOperand.getImm();
418 const auto NumOps = MI->getNumOperands();
419
420 // Print the opcode using the spirv-as unknown opcode syntax
421 O << "OpUnknown(" << Enumerant << ", " << NumOps << ") ";
422
423 // The result ID must be printed after the opcode when using this syntax
424 printOperand(MI, OpNo: 0, O);
425
426 O << " ";
427
428 const MCInstrDesc &MCDesc = MII.get(Opcode: MI->getOpcode());
429 unsigned NumFixedOps = MCDesc.getNumOperands();
430 if (NumOps == NumFixedOps)
431 return;
432
433 // Print the rest of the operands
434 printRemainingVariableOps(MI, StartIndex: NumFixedOps, O, SkipFirstSpace: true);
435}
436
437void SPIRVInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
438 raw_ostream &O) {
439 if (OpNo < MI->getNumOperands()) {
440 const MCOperand &Op = MI->getOperand(i: OpNo);
441 if (Op.isReg())
442 O << '%' << (getIDFromRegister(Reg: Op.getReg().id()) + 1);
443 else if (Op.isImm()) {
444 int64_t Imm = Op.getImm();
445 // For OpVectorShuffle:
446 // A Component literal may also be FFFFFFFF, which means the corresponding
447 // result component has no source and is undefined.
448 // LLVM representation of poison/undef becomes -1 when lowered to MI.
449 if (MI->getOpcode() == SPIRV::OpVectorShuffle && Imm == -1)
450 O << "0xFFFFFFFF";
451 else
452 O << formatImm(Value: Imm);
453 } else if (Op.isDFPImm())
454 O << formatImm(Value: (double)Op.getDFPImm());
455 else if (Op.isExpr())
456 MAI.printExpr(O, *Op.getExpr());
457 else
458 llvm_unreachable("Unexpected operand type");
459 }
460}
461
462void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo,
463 raw_ostream &O) {
464 const unsigned NumOps = MI->getNumOperands();
465 unsigned StrStartIndex = OpNo;
466 while (StrStartIndex < NumOps) {
467 if (MI->getOperand(i: StrStartIndex).isReg())
468 break;
469
470 std::string Str = getSPIRVStringOperand(MI: *MI, StartIndex: StrStartIndex);
471 if (StrStartIndex != OpNo)
472 O << ' '; // Add a space if we're starting a new string/argument.
473 O << '"';
474 for (char c : Str) {
475 // Escape ", \n characters (might break for complex UTF-8).
476 if (c == '\n') {
477 O.write(Ptr: "\\n", Size: 2);
478 } else {
479 if (c == '"')
480 O.write(C: '\\');
481 O.write(C: c);
482 }
483 }
484 O << '"';
485
486 unsigned numOpsInString = (Str.size() / 4) + 1;
487 StrStartIndex += numOpsInString;
488
489 // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute".
490 if (MI->getOpcode() == SPIRV::OpDecorate &&
491 MI->getOperand(i: 1).getImm() ==
492 static_cast<unsigned>(Decoration::LinkageAttributes)) {
493 O << ' ';
494 printSymbolicOperand<OperandCategory::LinkageTypeOperand>(
495 MI, OpNo: StrStartIndex, O);
496 break;
497 }
498 }
499}
500
501void SPIRVInstPrinter::printExtension(const MCInst *MI, unsigned OpNo,
502 raw_ostream &O) {
503 auto SetReg = MI->getOperand(i: 2).getReg();
504 auto Set = ExtInstSetIDs[SetReg];
505 auto Op = MI->getOperand(i: OpNo).getImm();
506 O << getExtInstName(Set, InstructionNumber: Op);
507}
508
509template <OperandCategory::OperandCategory category>
510void SPIRVInstPrinter::printSymbolicOperand(const MCInst *MI, unsigned OpNo,
511 raw_ostream &O) {
512 if (OpNo < MI->getNumOperands()) {
513 O << getSymbolicOperandMnemonic(Category: category, Value: MI->getOperand(i: OpNo).getImm());
514 }
515}
516