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