1//=- WebAssemblyInstPrinter.cpp - WebAssembly assembly instruction printing -=//
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/// \file
10/// Print MCInst instructions to wasm format.
11///
12//===----------------------------------------------------------------------===//
13
14#include "MCTargetDesc/WebAssemblyInstPrinter.h"
15#include "MCTargetDesc/WebAssemblyMCAsmInfo.h"
16#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
17#include "MCTargetDesc/WebAssemblyMCTypeUtilities.h"
18#include "llvm/ADT/APFloat.h"
19#include "llvm/ADT/SmallSet.h"
20#include "llvm/ADT/StringExtras.h"
21#include "llvm/MC/MCAsmInfo.h"
22#include "llvm/MC/MCExpr.h"
23#include "llvm/MC/MCInst.h"
24#include "llvm/MC/MCInstrInfo.h"
25#include "llvm/MC/MCSubtargetInfo.h"
26#include "llvm/MC/MCSymbol.h"
27#include "llvm/MC/MCSymbolWasm.h"
28#include "llvm/Support/Casting.h"
29#include "llvm/Support/ErrorHandling.h"
30using namespace llvm;
31
32#define DEBUG_TYPE "asm-printer"
33
34#include "WebAssemblyGenAsmWriter.inc"
35
36WebAssemblyInstPrinter::WebAssemblyInstPrinter(const MCAsmInfo &MAI,
37 const MCInstrInfo &MII,
38 const MCRegisterInfo &MRI)
39 : MCInstPrinter(MAI, MII, MRI) {}
40
41void WebAssemblyInstPrinter::printRegName(raw_ostream &OS, MCRegister Reg) {
42 assert(Reg.id() != WebAssembly::UnusedReg);
43 // Note that there's an implicit local.get/local.set here!
44 OS << "$" << Reg.id();
45}
46
47void WebAssemblyInstPrinter::printInst(const MCInst *MI, uint64_t Address,
48 StringRef Annot,
49 const MCSubtargetInfo &STI,
50 raw_ostream &OS) {
51 unsigned TypeOperand = 0;
52 unsigned TableOperand = 1;
53 switch (MI->getOpcode()) {
54 case WebAssembly::CALL_INDIRECT: {
55 unsigned NumDefs = MI->getOperand(i: 0).getImm();
56 TypeOperand = NumDefs + 1;
57 TableOperand = NumDefs + 2;
58 [[fallthrough]];
59 }
60 case WebAssembly::RET_CALL_INDIRECT:
61 case WebAssembly::CALL_INDIRECT_S:
62 case WebAssembly::RET_CALL_INDIRECT_S: {
63 // A special case for call_indirect (and ret_call_indirect): the order of
64 // the type and table operands is inverted in the text format relative to
65 // the binary format. The table operand is omitted when it refers to the
66 // default table 0 of an MVP compilation unit. Otherwise (a table symbol,
67 // or a non-zero table index from a multi-table module) it is printed
68 // first. A disassembler can also hand us an arbitrary immediate here when
69 // decoding misaligned bytes, so print it rather than asserting.
70 OS << "\t";
71 OS << getMnemonic(MI: *MI).first;
72 OS << " ";
73 const MCOperand &TableOp = MI->getOperand(i: TableOperand);
74 if (TableOp.isExpr() || (TableOp.isImm() && TableOp.getImm() != 0)) {
75 printOperand(MI, OpNo: TableOperand, STI, O&: OS);
76 OS << ", ";
77 }
78 printOperand(MI, OpNo: TypeOperand, STI, O&: OS);
79 if (MI->getOpcode() == WebAssembly::CALL_INDIRECT)
80 OS << ", ";
81 break;
82 }
83 default:
84 // Print the instruction (this uses the AsmStrings from the .td files).
85 printInstruction(MI, Address, STI, O&: OS);
86 break;
87 }
88
89 // Print any additional variadic operands.
90 const MCInstrDesc &Desc = MII.get(Opcode: MI->getOpcode());
91 if (Desc.isVariadic()) {
92 if ((Desc.getNumOperands() == 0 && MI->getNumOperands() > 0) ||
93 Desc.variadicOpsAreDefs())
94 OS << "\t";
95 unsigned Start = Desc.getNumOperands();
96 unsigned NumVariadicDefs = 0;
97 if (Desc.variadicOpsAreDefs()) {
98 // The number of variadic defs is encoded in an immediate by MCInstLower
99 NumVariadicDefs = MI->getOperand(i: 0).getImm();
100 Start = 1;
101 }
102 bool NeedsComma = Desc.getNumOperands() > 0 && !Desc.variadicOpsAreDefs();
103 for (auto I = Start, E = MI->getNumOperands(); I < E; ++I) {
104 if (MI->getOpcode() == WebAssembly::CALL_INDIRECT &&
105 I - Start == NumVariadicDefs) {
106 // Skip type and table arguments when printing for tests.
107 ++I;
108 continue;
109 }
110 if (NeedsComma)
111 OS << ", ";
112 printOperand(MI, OpNo: I, STI, O&: OS, IsVariadicDef: I - Start < NumVariadicDefs);
113 NeedsComma = true;
114 }
115 }
116
117 // Print any added annotation.
118 printAnnotation(OS, Annot);
119
120 auto PrintBranchAnnotation = [&](const MCOperand &Op,
121 SmallSet<uint64_t, 8> &Printed) {
122 uint64_t Depth = Op.getImm();
123 if (!Printed.insert(V: Depth).second)
124 return;
125 if (Depth >= ControlFlowStack.size()) {
126 printAnnotation(OS, Annot: "Invalid depth argument!");
127 } else {
128 const auto &Pair = ControlFlowStack.rbegin()[Depth];
129 printAnnotation(OS, Annot: utostr(X: Depth) + ": " + (Pair.second ? "up" : "down") +
130 " to label" + utostr(X: Pair.first));
131 }
132 };
133
134 if (CommentStream) {
135 // Observe any effects on the control flow stack, for use in annotating
136 // control flow label references.
137 unsigned Opc = MI->getOpcode();
138 switch (Opc) {
139 default:
140 break;
141
142 case WebAssembly::LOOP:
143 case WebAssembly::LOOP_S:
144 printAnnotation(OS, Annot: "label" + utostr(X: ControlFlowCounter) + ':');
145 ControlFlowStack.push_back(Elt: std::make_pair(x: ControlFlowCounter++, y: true));
146 return;
147
148 case WebAssembly::BLOCK:
149 case WebAssembly::BLOCK_S:
150 ControlFlowStack.push_back(Elt: std::make_pair(x: ControlFlowCounter++, y: false));
151 return;
152
153 case WebAssembly::TRY:
154 case WebAssembly::TRY_S:
155 ControlFlowStack.push_back(Elt: std::make_pair(x&: ControlFlowCounter, y: false));
156 TryStack.push_back(Elt: ControlFlowCounter++);
157 EHInstStack.push_back(Elt: TRY);
158 return;
159
160 case WebAssembly::TRY_TABLE:
161 case WebAssembly::TRY_TABLE_S: {
162 SmallSet<uint64_t, 8> Printed;
163 unsigned OpIdx = 1;
164 const MCOperand &Op = MI->getOperand(i: OpIdx++);
165 unsigned NumCatches = Op.getImm();
166 for (unsigned I = 0; I < NumCatches; I++) {
167 int64_t CatchOpcode = MI->getOperand(i: OpIdx++).getImm();
168 if (CatchOpcode == wasm::WASM_OPCODE_CATCH ||
169 CatchOpcode == wasm::WASM_OPCODE_CATCH_REF)
170 OpIdx++; // Skip tag
171 PrintBranchAnnotation(MI->getOperand(i: OpIdx++), Printed);
172 }
173 ControlFlowStack.push_back(Elt: std::make_pair(x: ControlFlowCounter++, y: false));
174 return;
175 }
176
177 case WebAssembly::SELECT_T:
178 case WebAssembly::SELECT_T_S:
179 // The trailing operands encode a vec of valtypes, not branch targets;
180 // skip the generic branch-annotation pass below.
181 return;
182
183 case WebAssembly::END_LOOP:
184 case WebAssembly::END_LOOP_S:
185 if (ControlFlowStack.empty()) {
186 printAnnotation(OS, Annot: "End marker mismatch!");
187 } else {
188 ControlFlowStack.pop_back();
189 }
190 return;
191
192 case WebAssembly::END_BLOCK:
193 case WebAssembly::END_BLOCK_S:
194 case WebAssembly::END_TRY_TABLE:
195 case WebAssembly::END_TRY_TABLE_S:
196 if (ControlFlowStack.empty()) {
197 printAnnotation(OS, Annot: "End marker mismatch!");
198 } else {
199 printAnnotation(
200 OS, Annot: "label" + utostr(X: ControlFlowStack.pop_back_val().first) + ':');
201 }
202 return;
203
204 case WebAssembly::END_TRY:
205 case WebAssembly::END_TRY_S:
206 if (ControlFlowStack.empty() || EHInstStack.empty()) {
207 printAnnotation(OS, Annot: "End marker mismatch!");
208 } else {
209 printAnnotation(
210 OS, Annot: "label" + utostr(X: ControlFlowStack.pop_back_val().first) + ':');
211 EHInstStack.pop_back();
212 }
213 return;
214
215 case WebAssembly::CATCH_LEGACY:
216 case WebAssembly::CATCH_LEGACY_S:
217 case WebAssembly::CATCH_ALL_LEGACY:
218 case WebAssembly::CATCH_ALL_LEGACY_S:
219 // There can be multiple catch instructions for one try instruction, so
220 // we print a label only for the first 'catch' label.
221 if (EHInstStack.empty()) {
222 printAnnotation(OS, Annot: "try-catch mismatch!");
223 } else if (EHInstStack.back() == CATCH_ALL_LEGACY) {
224 printAnnotation(OS, Annot: "catch/catch_all cannot occur after catch_all");
225 } else if (EHInstStack.back() == TRY) {
226 if (TryStack.empty()) {
227 printAnnotation(OS, Annot: "try-catch mismatch!");
228 } else {
229 printAnnotation(OS, Annot: "catch" + utostr(X: TryStack.pop_back_val()) + ':');
230 }
231 EHInstStack.pop_back();
232 if (Opc == WebAssembly::CATCH_LEGACY ||
233 Opc == WebAssembly::CATCH_LEGACY_S) {
234 EHInstStack.push_back(Elt: CATCH_LEGACY);
235 } else {
236 EHInstStack.push_back(Elt: CATCH_ALL_LEGACY);
237 }
238 }
239 return;
240
241 case WebAssembly::RETHROW:
242 case WebAssembly::RETHROW_S:
243 // 'rethrow' rethrows to the nearest enclosing catch scope, if any. If
244 // there's no enclosing catch scope, it throws up to the caller.
245 if (TryStack.empty()) {
246 printAnnotation(OS, Annot: "to caller");
247 } else {
248 printAnnotation(OS, Annot: "down to catch" + utostr(X: TryStack.back()));
249 }
250 return;
251
252 case WebAssembly::DELEGATE:
253 case WebAssembly::DELEGATE_S:
254 if (ControlFlowStack.empty() || TryStack.empty() || EHInstStack.empty()) {
255 printAnnotation(OS, Annot: "try-delegate mismatch!");
256 } else {
257 // 'delegate' is
258 // 1. A marker for the end of block label
259 // 2. A destination for throwing instructions
260 // 3. An instruction that itself rethrows to another 'catch'
261 assert(ControlFlowStack.back().first == TryStack.back());
262 std::string Label = "label/catch" +
263 utostr(X: ControlFlowStack.pop_back_val().first) +
264 ": ";
265 TryStack.pop_back();
266 EHInstStack.pop_back();
267 uint64_t Depth = MI->getOperand(i: 0).getImm();
268 if (Depth >= ControlFlowStack.size()) {
269 Label += "to caller";
270 } else {
271 const auto &Pair = ControlFlowStack.rbegin()[Depth];
272 if (Pair.second)
273 printAnnotation(OS, Annot: "delegate cannot target a loop");
274 else
275 Label += "down to catch" + utostr(X: Pair.first);
276 }
277 printAnnotation(OS, Annot: Label);
278 }
279 return;
280 }
281
282 // Annotate any control flow label references.
283
284 unsigned NumFixedOperands = Desc.NumOperands;
285 SmallSet<uint64_t, 8> Printed;
286 for (unsigned I = 0, E = MI->getNumOperands(); I < E; ++I) {
287 // See if this operand denotes a basic block target.
288 if (I < NumFixedOperands) {
289 // A non-variable_ops operand, check its type.
290 if (Desc.operands()[I].OperandType != WebAssembly::OPERAND_BASIC_BLOCK)
291 continue;
292 } else {
293 // A variable_ops operand, which currently can be immediates (used in
294 // br_table) which are basic block targets, or for call instructions
295 // when using -wasm-keep-registers (in which case they are registers,
296 // and should not be processed).
297 if (!MI->getOperand(i: I).isImm())
298 continue;
299 }
300 PrintBranchAnnotation(MI->getOperand(i: I), Printed);
301 }
302 }
303}
304
305static std::string toString(const APFloat &FP) {
306 // Print NaNs with custom payloads specially.
307 if (FP.isNaN() && !FP.bitwiseIsEqual(RHS: APFloat::getQNaN(Sem: FP.getSemantics())) &&
308 !FP.bitwiseIsEqual(
309 RHS: APFloat::getQNaN(Sem: FP.getSemantics(), /*Negative=*/true))) {
310 APInt AI = FP.bitcastToAPInt();
311 return std::string(AI.isNegative() ? "-" : "") + "nan:0x" +
312 utohexstr(X: AI.getZExtValue() &
313 (AI.getBitWidth() == 32 ? INT64_C(0x007fffff)
314 : INT64_C(0x000fffffffffffff)),
315 /*LowerCase=*/true);
316 }
317
318 // Use C99's hexadecimal floating-point representation.
319 static const size_t BufBytes = 128;
320 char Buf[BufBytes];
321 auto Written = FP.convertToHexString(
322 DST: Buf, /*HexDigits=*/0, /*UpperCase=*/false, RM: APFloat::rmNearestTiesToEven);
323 (void)Written;
324 assert(Written != 0);
325 assert(Written < BufBytes);
326 return Buf;
327}
328
329void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
330 const MCSubtargetInfo &STI,
331 raw_ostream &O, bool IsVariadicDef) {
332 const MCOperand &Op = MI->getOperand(i: OpNo);
333 if (Op.isReg()) {
334 const MCInstrDesc &Desc = MII.get(Opcode: MI->getOpcode());
335 MCRegister WAReg = Op.getReg();
336 if (int(WAReg.id()) >= 0)
337 printRegName(OS&: O, Reg: WAReg);
338 else if (OpNo >= Desc.getNumDefs() && !IsVariadicDef)
339 O << "$pop" << WebAssembly::getWARegStackId(Reg: WAReg);
340 else if (WAReg != WebAssembly::UnusedReg)
341 O << "$push" << WebAssembly::getWARegStackId(Reg: WAReg);
342 else
343 O << "$drop";
344 // Add a '=' suffix if this is a def.
345 if (OpNo < MII.get(Opcode: MI->getOpcode()).getNumDefs() || IsVariadicDef)
346 O << '=';
347 } else if (Op.isImm()) {
348 O << Op.getImm();
349 } else if (Op.isSFPImm()) {
350 O << ::toString(FP: APFloat(APFloat::IEEEsingle(), APInt(32, Op.getSFPImm())));
351 } else if (Op.isDFPImm()) {
352 O << ::toString(FP: APFloat(APFloat::IEEEdouble(), APInt(64, Op.getDFPImm())));
353 } else {
354 assert(Op.isExpr() && "unknown operand kind in printOperand");
355 // call_indirect instructions have a TYPEINDEX operand that we print
356 // as a signature here, such that the assembler can recover this
357 // information.
358 auto SRE = static_cast<const MCSymbolRefExpr *>(Op.getExpr());
359 if (SRE->getSpecifier() == WebAssembly::S_TYPEINDEX) {
360 auto &Sym = static_cast<const MCSymbolWasm &>(SRE->getSymbol());
361 O << WebAssembly::signatureToString(Sig: Sym.getSignature());
362 } else {
363 MAI.printExpr(O, *Op.getExpr());
364 }
365 }
366}
367
368void WebAssemblyInstPrinter::printBrList(const MCInst *MI, unsigned OpNo,
369 const MCSubtargetInfo &STI,
370 raw_ostream &O) {
371 O << "{";
372 for (unsigned I = OpNo, E = MI->getNumOperands(); I != E; ++I) {
373 if (I != OpNo)
374 O << ", ";
375 O << MI->getOperand(i: I).getImm();
376 }
377 O << "}";
378}
379
380void WebAssemblyInstPrinter::printWebAssemblyP2AlignOperand(
381 const MCInst *MI, unsigned OpNo, const MCSubtargetInfo &STI,
382 raw_ostream &O) {
383 int64_t Imm = MI->getOperand(i: OpNo).getImm();
384 if (Imm == WebAssembly::GetDefaultP2Align(Opc: MI->getOpcode()))
385 return;
386 O << ":p2align=" << Imm;
387}
388
389void WebAssemblyInstPrinter::printWebAssemblyMemOrderOperand(
390 const MCInst *MI, unsigned OpNo, const MCSubtargetInfo &STI,
391 raw_ostream &O) {
392 int64_t Imm = MI->getOperand(i: OpNo).getImm();
393
394 switch (Imm) {
395 case wasm::WASM_MEM_ORDER_RMW_ACQ_REL:
396 case wasm::WASM_MEM_ORDER_ACQ_REL:
397 O << "acqrel";
398 break;
399 case wasm::WASM_MEM_ORDER_SEQ_CST:
400 if (STI.getFeatureBits()[WebAssembly::FeatureRelaxedAtomics])
401 O << "seqcst";
402 break;
403 default:
404 llvm_unreachable("Unknown memory ordering");
405 }
406}
407
408void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(
409 const MCInst *MI, unsigned OpNo, const MCSubtargetInfo &STI,
410 raw_ostream &O) {
411 const MCOperand &Op = MI->getOperand(i: OpNo);
412 if (Op.isImm()) {
413 auto Imm = static_cast<unsigned>(Op.getImm());
414 if (Imm != wasm::WASM_TYPE_NORESULT)
415 O << WebAssembly::anyTypeToString(Type: Imm);
416 } else {
417 auto Expr = cast<MCSymbolRefExpr>(Val: Op.getExpr());
418 auto *Sym = static_cast<const MCSymbolWasm *>(&Expr->getSymbol());
419 if (Sym->getSignature()) {
420 O << WebAssembly::signatureToString(Sig: Sym->getSignature());
421 } else {
422 // Disassembler does not currently produce a signature
423 O << "unknown_type";
424 }
425 }
426}
427
428void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,
429 const MCSubtargetInfo &STI,
430 raw_ostream &O) {
431 unsigned OpIdx = OpNo;
432 const MCOperand &Op = MI->getOperand(i: OpIdx++);
433 unsigned NumCatches = Op.getImm();
434
435 auto PrintTagOp = [&](const MCOperand &Op) {
436 const MCSymbolRefExpr *TagExpr = nullptr;
437 const MCSymbol *TagSym = nullptr;
438 if (Op.isExpr()) {
439 TagExpr = cast<MCSymbolRefExpr>(Val: Op.getExpr());
440 TagSym = &TagExpr->getSymbol();
441 O << TagSym->getName() << " ";
442 } else {
443 // When instructions are parsed from the disassembler, we have an
444 // immediate tag index and not a tag expr
445 O << Op.getImm() << " ";
446 }
447 };
448
449 for (unsigned I = 0; I < NumCatches; I++) {
450 const MCOperand &Op = MI->getOperand(i: OpIdx++);
451 O << "(";
452 switch (Op.getImm()) {
453 case wasm::WASM_OPCODE_CATCH:
454 O << "catch ";
455 PrintTagOp(MI->getOperand(i: OpIdx++));
456 break;
457 case wasm::WASM_OPCODE_CATCH_REF:
458 O << "catch_ref ";
459 PrintTagOp(MI->getOperand(i: OpIdx++));
460 break;
461 case wasm::WASM_OPCODE_CATCH_ALL:
462 O << "catch_all ";
463 break;
464 case wasm::WASM_OPCODE_CATCH_ALL_REF:
465 O << "catch_all_ref ";
466 break;
467 }
468 O << MI->getOperand(i: OpIdx++).getImm(); // destination
469 O << ")";
470 if (I < NumCatches - 1)
471 O << " ";
472 }
473}
474
475void WebAssemblyInstPrinter::printTypeList(const MCInst *MI, unsigned OpNo,
476 const MCSubtargetInfo &STI,
477 raw_ostream &O) {
478 unsigned OpIdx = OpNo;
479 uint64_t NumTypes = uint64_t(MI->getOperand(i: OpIdx++).getImm());
480 uint64_t Remaining = MI->getNumOperands() - OpIdx;
481 if (NumTypes > Remaining)
482 NumTypes = Remaining;
483 for (uint64_t I = 0; I < NumTypes; I++) {
484 if (I != 0)
485 O << ' ';
486 O << WebAssembly::anyTypeToString(Type: MI->getOperand(i: OpIdx++).getImm());
487 }
488}
489