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