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