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