LLVM 24.0.0git
SPIRVInstPrinter.cpp
Go to the documentation of this file.
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"
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
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
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 // Look up the bitwidth of this int type register from
55 // IntTypeBitwidths map.
56 MCRegister IntTypeReg = MI->getOperand(1).getReg();
57 unsigned Bitwidth = IntTypeBitwidths.at(IntTypeReg);
58
59 // SPV_ALTERA_arbitrary_precision_integers allows for integer widths greater
60 // than 64, which will be encoded via multiple operands.
61 const unsigned TotalBits = NumVarOps * 32;
62 APInt Val(TotalBits, 0);
63 for (unsigned i = 0; i < NumVarOps; ++i) {
64 uint64_t Word = MI->getOperand(StartIndex + i).getImm();
65 Val |= APInt(TotalBits, Word) << (i * 32);
66 }
67 APInt ActualVal = Val.trunc(Bitwidth);
68 O << ' ';
69 ActualVal.print(O, /*isSigned=*/false);
70 return;
71 }
72
73 assert((NumVarOps == 1 || NumVarOps == 2) &&
74 "Unsupported number of bits for literal variable");
75
76 O << ' ';
77
78 uint64_t Imm = MI->getOperand(StartIndex).getImm();
79
80 // Handle 64 bit literals.
81 if (NumVarOps == 2) {
82 Imm |= (MI->getOperand(StartIndex + 1).getImm() << 32);
83 }
84
85 // Format and print float values.
86 if (MI->getOpcode() == SPIRV::OpConstantF && IsBitwidth16 == 0) {
87 APFloat FP = NumVarOps == 1 ? APFloat(APInt(32, Imm).bitsToFloat())
88 : APFloat(APInt(64, Imm).bitsToDouble());
89
90 // Print infinity and NaN as hex floats. The exponent depends on the
91 // actual width of FP (f32 vs f64), not a fixed constant.
92 // TODO: Make sure subnormal numbers are handled correctly as they may also
93 // require hex float notation.
94 if (FP.isInfinity() || FP.isNaN()) {
95 unsigned MaxExp = APFloat::semanticsMaxExponent(FP.getSemantics()) + 1;
96 if (FP.isInfinity()) {
97 if (FP.isNegative())
98 O << '-';
99 O << "0x1p+" << MaxExp;
100 } else {
101 O << "0x1.8p+" << MaxExp;
102 }
103 return;
104 }
105
106 // Format val as a decimal floating point or scientific notation (whichever
107 // is shorter), with enough digits of precision to produce the exact value.
108 O << format("%.*g", std::numeric_limits<double>::max_digits10,
109 FP.convertToDouble());
110
111 return;
112 }
113
114 // Print integer values directly.
115 O << Imm;
116}
117
118unsigned SPIRVInstPrinter::printMemoryOperand(const MCInst *MI, unsigned OpNo,
119 raw_ostream &O) {
120 O << ' ';
121 if (OpNo >= MI->getNumOperands())
122 return OpNo;
123 const uint64_t Mask = MI->getOperand(OpNo).getImm();
125 unsigned NextOp = OpNo + 1;
126 static constexpr uint64_t ParameterizedMasks[] = {
127 SPIRV::MemoryOperand::Aligned,
128 SPIRV::MemoryOperand::MakePointerAvailableKHR,
129 SPIRV::MemoryOperand::MakePointerVisibleKHR,
130 SPIRV::MemoryOperand::AliasScopeINTELMask,
131 SPIRV::MemoryOperand::NoAliasINTELMask,
132 };
133 for (uint64_t ParamMask : ParameterizedMasks) {
134 if (!(Mask & ParamMask))
135 continue;
136 O << ' ';
137 printOperand(MI, NextOp, O);
138 ++NextOp;
139 }
140 return NextOp;
141}
142
143void SPIRVInstPrinter::recordIntType(const MCInst *MI) {
144 MCRegister IntTypeReg = MI->getOperand(0).getReg();
145 unsigned Bitwidth = MI->getOperand(1).getImm();
146 IntTypeBitwidths[IntTypeReg] = Bitwidth;
147}
148
149void SPIRVInstPrinter::recordOpExtInstImport(const MCInst *MI) {
150 MCRegister Reg = MI->getOperand(0).getReg();
151 auto Name = getSPIRVStringOperand(*MI, 1);
152 auto Set = getExtInstSetFromString(std::move(Name));
153 ExtInstSetIDs.insert({Reg, Set});
154}
155
157 StringRef Annot, const MCSubtargetInfo &STI,
158 raw_ostream &OS) {
159 const unsigned OpCode = MI->getOpcode();
161 if (OpCode == SPIRV::OpTypeInt) {
162 recordIntType(MI);
163 }
164
165 if (OpCode == SPIRV::OpDecorate || OpCode == SPIRV::OpDecorateId) {
166 printOpDecorate(MI, OS);
167 } else if (OpCode == SPIRV::OpExtInstImport) {
168 recordOpExtInstImport(MI);
169 } else if (OpCode == SPIRV::OpExtInst) {
170 printOpExtInst(MI, OS);
171 } else if (OpCode == SPIRV::UNKNOWN_type) {
172 printUnknownType(MI, OS);
173 } else {
174 // Print any extra operands for variadic instructions.
175 const MCInstrDesc &MCDesc = MII.get(OpCode);
176 if (MCDesc.isVariadic()) {
177 const unsigned NumFixedOps = MCDesc.getNumOperands();
178 const unsigned LastFixedIndex = NumFixedOps - 1;
179 const int FirstVariableIndex = NumFixedOps;
180 if (NumFixedOps > 0 && MCDesc.operands()[LastFixedIndex].OperandType ==
182 // For instructions where a custom type (not reg or immediate) comes as
183 // the last operand before the variable_ops. This is usually a StringImm
184 // operand, but there are a few other cases.
185 switch (OpCode) {
186 case SPIRV::OpTypeImage:
187 OS << ' ';
189 MI, FirstVariableIndex, OS);
190 break;
191 case SPIRV::OpVariable:
192 OS << ' ';
193 printOperand(MI, FirstVariableIndex, OS);
194 break;
195 case SPIRV::OpEntryPoint: {
196 // Print the interface ID operands, skipping the name's string
197 // literal.
198 printRemainingVariableOps(MI, NumFixedOps, OS, false, true);
199 break;
200 }
201 case SPIRV::OpMemberDecorate:
202 printRemainingVariableOps(MI, NumFixedOps, OS);
203 break;
204 case SPIRV::OpExecutionMode:
205 case SPIRV::OpExecutionModeId:
206 case SPIRV::OpLoopMerge:
207 case SPIRV::OpLoopControlINTEL: {
208 // Print any literals after the OPERAND_UNKNOWN argument normally.
209 printRemainingVariableOps(MI, NumFixedOps, OS);
210 break;
211 }
212 default:
213 break; // printStringImm has already been handled.
214 }
215 } else {
216 // For instructions with no fixed ops or a reg/immediate as the final
217 // fixed operand, we can usually print the rest with "printOperand", but
218 // check for a few cases with custom types first.
219 switch (OpCode) {
220 case SPIRV::OpLoad:
221 case SPIRV::OpStore:
222 printMemoryOperand(MI, FirstVariableIndex, OS);
223 break;
224 case SPIRV::OpSwitch:
225 if (MI->getFlags() & SPIRV::INST_PRINTER_WIDTH64) {
226 // In binary format 64-bit types are split into two 32-bit operands,
227 // but in text format combine these into a single 64-bit value as
228 // this is what tools such as spirv-as require.
229 const unsigned NumOps = MI->getNumOperands();
230 for (unsigned OpIdx = NumFixedOps; OpIdx < NumOps;) {
231 if (OpIdx + 1 >= NumOps || !MI->getOperand(OpIdx).isImm() ||
232 !MI->getOperand(OpIdx + 1).isImm()) {
233 llvm_unreachable("Unexpected OpSwitch operands");
234 continue;
235 }
236 OS << ' ';
237 uint64_t LowBits = MI->getOperand(OpIdx).getImm();
238 uint64_t HighBits = MI->getOperand(OpIdx + 1).getImm();
239 uint64_t CombinedValue = (HighBits << 32) | LowBits;
240 OS << formatImm(CombinedValue);
241 OpIdx += 2;
242
243 // Next should be the label
244 if (OpIdx < NumOps) {
245 OS << ' ';
246 printOperand(MI, OpIdx, OS);
247 OpIdx++;
248 }
249 }
250 } else {
251 printRemainingVariableOps(MI, NumFixedOps, OS);
252 }
253 break;
254 case SPIRV::OpImageSampleImplicitLod:
255 case SPIRV::OpImageSampleDrefImplicitLod:
256 case SPIRV::OpImageSampleProjImplicitLod:
257 case SPIRV::OpImageSampleProjDrefImplicitLod:
258 case SPIRV::OpImageFetch:
259 case SPIRV::OpImageGather:
260 case SPIRV::OpImageDrefGather:
261 case SPIRV::OpImageRead:
262 case SPIRV::OpImageWrite:
263 case SPIRV::OpImageSparseSampleImplicitLod:
264 case SPIRV::OpImageSparseSampleDrefImplicitLod:
265 case SPIRV::OpImageSparseSampleProjImplicitLod:
266 case SPIRV::OpImageSparseSampleProjDrefImplicitLod:
267 case SPIRV::OpImageSparseFetch:
268 case SPIRV::OpImageSparseGather:
269 case SPIRV::OpImageSparseDrefGather:
270 case SPIRV::OpImageSparseRead:
271 case SPIRV::OpImageSampleFootprintNV:
272 OS << ' ';
274 MI, FirstVariableIndex, OS);
275 printRemainingVariableOps(MI, NumFixedOps + 1, OS);
276 break;
277 case SPIRV::OpCopyMemory:
278 case SPIRV::OpCopyMemorySized: {
279 const unsigned NumOps = MI->getNumOperands();
280 for (unsigned i = NumFixedOps; i < NumOps;)
281 i = printMemoryOperand(MI, i, OS);
282 break;
283 }
284 case SPIRV::OpConstantI:
285 case SPIRV::OpConstantF:
286 // The last fixed operand along with any variadic operands that follow
287 // are part of the variable value.
288 assert(NumFixedOps > 0 && "Expected at least one fixed operand");
289 printOpConstantVarOps(MI, NumFixedOps - 1, OS);
290 break;
291 case SPIRV::OpCooperativeMatrixMulAddKHR: {
292 const unsigned NumOps = MI->getNumOperands();
293 if (NumFixedOps == NumOps)
294 break;
295
296 OS << ' ';
297 const unsigned MulAddOp = MI->getOperand(FirstVariableIndex).getImm();
298 if (MulAddOp == 0) {
300 OperandCategory::CooperativeMatrixOperandsOperand>(
301 MI, FirstVariableIndex, OS);
302 } else {
303 std::string Buffer;
304 for (unsigned Mask = 0x1;
305 Mask != SPIRV::CooperativeMatrixOperands::
306 MatrixResultBFloat16ComponentsINTEL;
307 Mask <<= 1) {
308 if (MulAddOp & Mask) {
309 if (!Buffer.empty())
310 Buffer += '|';
312 OperandCategory::CooperativeMatrixOperandsOperand, Mask);
313 }
314 }
315 OS << Buffer;
316 }
317 break;
318 }
319 case SPIRV::OpSubgroupMatrixMultiplyAccumulateINTEL: {
320 const unsigned NumOps = MI->getNumOperands();
321 if (NumFixedOps >= NumOps)
322 break;
323 OS << ' ';
324 const unsigned Flags = MI->getOperand(NumOps - 1).getImm();
325 if (Flags == 0) {
327 OperandCategory::MatrixMultiplyAccumulateOperandsOperand>(
328 MI, NumOps - 1, OS);
329 } else {
330 std::string Buffer;
331 for (unsigned Mask = 0x1;
332 Mask <= SPIRV::MatrixMultiplyAccumulateOperands::
333 MatrixBPackedBFloat16INTEL;
334 Mask <<= 1) {
335 if (Flags & Mask) {
336 if (!Buffer.empty())
337 Buffer += '|';
339 OperandCategory::MatrixMultiplyAccumulateOperandsOperand,
340 Mask);
341 }
342 }
343 OS << Buffer;
344 }
345 break;
346 }
347 case SPIRV::OpSDot:
348 case SPIRV::OpUDot:
349 case SPIRV::OpSUDot:
350 case SPIRV::OpSDotAccSat:
351 case SPIRV::OpUDotAccSat:
352 case SPIRV::OpSUDotAccSat: {
353 const unsigned NumOps = MI->getNumOperands();
354 if (NumOps > NumFixedOps) {
355 OS << ' ';
357 MI, NumOps - 1, OS);
358 break;
359 }
360 break;
361 }
362 case SPIRV::OpPredicatedLoadINTEL:
363 case SPIRV::OpPredicatedStoreINTEL: {
364 if (MI->getNumOperands() > NumFixedOps)
365 printMemoryOperand(MI, NumFixedOps, OS);
366 break;
367 }
368 default:
369 printRemainingVariableOps(MI, NumFixedOps, OS);
370 break;
371 }
372 }
373 }
374 }
375
376 printAnnotation(OS, Annot);
377}
378
380 // The fixed operands have already been printed, so just need to decide what
381 // type of ExtInst operands to print based on the instruction set and number.
382 const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
383 unsigned NumFixedOps = MCDesc.getNumOperands();
384 const auto NumOps = MI->getNumOperands();
385 if (NumOps == NumFixedOps)
386 return;
387
388 O << ' ';
389
390 // TODO: implement special printing for OpenCLExtInst::vstor*.
391 printRemainingVariableOps(MI, NumFixedOps, O, true);
392}
393
395 // The fixed operands have already been printed, so just need to decide what
396 // type of decoration operands to print based on the Decoration type.
397 const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
398 unsigned NumFixedOps = MCDesc.getNumOperands();
399
400 if (NumFixedOps != MI->getNumOperands()) {
401 auto DecOp = MI->getOperand(NumFixedOps - 1);
402 auto Dec = static_cast<Decoration::Decoration>(DecOp.getImm());
403
404 O << ' ';
405
406 switch (Dec) {
407 case Decoration::BuiltIn:
409 break;
410 case Decoration::UniformId:
411 printOperand(MI, NumFixedOps, O);
412 break;
413 case Decoration::FuncParamAttr:
415 MI, NumFixedOps, O);
416 break;
417 case Decoration::FPRoundingMode:
419 MI, NumFixedOps, O);
420 break;
421 case Decoration::FPFastMathMode:
423 MI, NumFixedOps, O);
424 break;
425 case Decoration::LinkageAttributes:
426 case Decoration::UserSemantic:
427 printStringImm(MI, NumFixedOps, O);
428 break;
429 case Decoration::HostAccessINTEL:
430 printOperand(MI, NumFixedOps, O);
431 if (NumFixedOps + 1 < MI->getNumOperands()) {
432 O << ' ';
433 printStringImm(MI, NumFixedOps + 1, O);
434 }
435 break;
436 default:
437 printRemainingVariableOps(MI, NumFixedOps, O, true);
438 break;
439 }
440 }
441}
442
444 const auto EnumOperand = MI->getOperand(1);
445 assert(EnumOperand.isImm() &&
446 "second operand of UNKNOWN_type must be opcode!");
447
448 const auto Enumerant = EnumOperand.getImm();
449 const auto NumOps = MI->getNumOperands();
450
451 // Print the opcode using the spirv-as unknown opcode syntax
452 O << "OpUnknown(" << Enumerant << ", " << NumOps << ") ";
453
454 // The result ID must be printed after the opcode when using this syntax
455 printOperand(MI, 0, O);
456
457 O << " ";
458
459 const MCInstrDesc &MCDesc = MII.get(MI->getOpcode());
460 unsigned NumFixedOps = MCDesc.getNumOperands();
461 if (NumOps == NumFixedOps)
462 return;
463
464 // Print the rest of the operands
465 printRemainingVariableOps(MI, NumFixedOps, O, true);
466}
467
468void SPIRVInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
469 raw_ostream &O) {
470 if (OpNo < MI->getNumOperands()) {
471 const MCOperand &Op = MI->getOperand(OpNo);
472 if (Op.isReg())
473 O << '%' << (getIDFromRegister(Op.getReg().id()) + 1);
474 else if (Op.isImm()) {
475 int64_t Imm = Op.getImm();
476 // For OpVectorShuffle:
477 // A Component literal may also be FFFFFFFF, which means the corresponding
478 // result component has no source and is undefined.
479 // LLVM representation of poison/undef becomes -1 when lowered to MI.
480 if (MI->getOpcode() == SPIRV::OpVectorShuffle && Imm == -1)
481 O << "0xFFFFFFFF";
482 else
483 O << formatImm(Imm);
484 } else if (Op.isDFPImm())
485 O << formatImm((double)Op.getDFPImm());
486 else if (Op.isExpr())
487 MAI.printExpr(O, *Op.getExpr());
488 else
489 llvm_unreachable("Unexpected operand type");
490 }
491}
492
493void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo,
494 raw_ostream &O) {
495 const unsigned NumOps = MI->getNumOperands();
496 unsigned StrStartIndex = OpNo;
497 while (StrStartIndex < NumOps) {
498 if (MI->getOperand(StrStartIndex).isReg())
499 break;
500
501 std::string Str = getSPIRVStringOperand(*MI, StrStartIndex);
502 if (StrStartIndex != OpNo)
503 O << ' '; // Add a space if we're starting a new string/argument.
504 O << '"';
505 for (char c : Str) {
506 // Escape ", \n characters (might break for complex UTF-8).
507 if (c == '\n') {
508 O.write("\\n", 2);
509 } else {
510 if (c == '"')
511 O.write('\\');
512 O.write(c);
513 }
514 }
515 O << '"';
516
517 unsigned numOpsInString = (Str.size() / 4) + 1;
518 StrStartIndex += numOpsInString;
519
520 // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute".
521 if (MI->getOpcode() == SPIRV::OpDecorate &&
522 MI->getOperand(1).getImm() ==
523 static_cast<unsigned>(Decoration::LinkageAttributes)) {
524 O << ' ';
526 MI, StrStartIndex, O);
527 break;
528 }
529 }
530}
531
532void SPIRVInstPrinter::printExtension(const MCInst *MI, unsigned OpNo,
533 raw_ostream &O) {
534 auto SetReg = MI->getOperand(2).getReg();
535 auto Set = ExtInstSetIDs[SetReg];
536 auto Op = MI->getOperand(OpNo).getImm();
537 O << getExtInstName(Set, Op);
538}
539
540template <OperandCategory::OperandCategory category>
542 raw_ostream &O) {
543 if (OpNo < MI->getNumOperands()) {
544 O << getSymbolicOperandMnemonic(category, MI->getOperand(OpNo).getImm());
545 }
546}
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
This file declares a class to represent arbitrary precision floating point values and provide a varie...
IRTranslator LLVM IR MI
const size_t AbstractManglingParser< Derived, Alloc >::NumOps
Register Reg
MachineInstr unsigned OpIdx
static LLVM_ABI ExponentType semanticsMaxExponent(const fltSemantics &)
Definition APFloat.cpp:258
Class for arbitrary precision integers.
Definition APInt.h:78
LLVM_ABI APInt trunc(unsigned width) const
Truncate to new width.
Definition APInt.cpp:968
LLVM_ABI void print(raw_ostream &OS, bool isSigned) const
Definition APInt.cpp:2342
const MCInstrInfo & MII
void printAnnotation(raw_ostream &OS, StringRef Annot)
Utility function for printing annotations.
const MCAsmInfo & MAI
format_object< int64_t > formatImm(int64_t Value) const
Utility function to print immediates in decimal or hex.
Instances of this class represent a single low-level machine instruction.
Definition MCInst.h:188
Describe properties that are true of each instruction in the target description file.
unsigned getNumOperands() const
Return the number of declared MachineOperands for this MachineInstruction.
ArrayRef< MCOperandInfo > operands() const
bool isVariadic() const
Return true if this instruction can have a variable number of operands.
Instances of this class represent operands of the MCInst class.
Definition MCInst.h:40
Wrapper class representing physical registers. Should be passed by value.
Definition MCRegister.h:41
Generic base class for all target subtargets.
void printExtension(const MCInst *MI, unsigned OpNo, raw_ostream &O)
void printStringImm(const MCInst *MI, unsigned OpNo, raw_ostream &O)
void printInstruction(const MCInst *MI, uint64_t Address, raw_ostream &O)
void printInst(const MCInst *MI, uint64_t Address, StringRef Annot, const MCSubtargetInfo &STI, raw_ostream &OS) override
Print the specified MCInst to the specified raw_ostream.
void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O)
void printOpExtInst(const MCInst *MI, raw_ostream &O)
void printOpConstantVarOps(const MCInst *MI, unsigned StartIndex, raw_ostream &O)
void printSymbolicOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O)
void printRemainingVariableOps(const MCInst *MI, unsigned StartIndex, raw_ostream &O, bool SkipFirstSpace=false, bool SkipImmediates=false)
void printOpDecorate(const MCInst *MI, raw_ostream &O)
void printUnknownType(const MCInst *MI, raw_ostream &O)
unsigned printMemoryOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O)
Represent a constant reference to a string, i.e.
Definition StringRef.h:56
This class implements an extremely fast bulk output stream that can only output to a stream.
Definition raw_ostream.h:53
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
unsigned getIDFromRegister(unsigned Reg)
This is an optimization pass for GlobalISel generic memory operations.
std::string getExtInstName(SPIRV::InstructionSet::InstructionSet Set, uint32_t InstructionNumber)
MachineInstr * getImm(const MachineOperand &MO, const MachineRegisterInfo *MRI)
std::string getSPIRVStringOperand(const InstType &MI, unsigned StartIndex)
SPIRV::InstructionSet::InstructionSet getExtInstSetFromString(std::string SetName)
format_object< Ts... > format(const char *Fmt, const Ts &... Vals)
These are helper functions used to produce formatted output.
Definition Format.h:94
std::string getSymbolicOperandMnemonic(SPIRV::OperandCategory::OperandCategory Category, int32_t Value)
DWARFExpression::Operation Op