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" |
30 | using namespace llvm; |
31 | |
32 | #define DEBUG_TYPE "asm-printer" |
33 | |
34 | #include "WebAssemblyGenAsmWriter.inc" |
35 | |
36 | WebAssemblyInstPrinter::WebAssemblyInstPrinter(const MCAsmInfo &MAI, |
37 | const MCInstrInfo &MII, |
38 | const MCRegisterInfo &MRI) |
39 | : MCInstPrinter(MAI, MII, MRI) {} |
40 | |
41 | void 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 | |
47 | void 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 | |
291 | static 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 | |
315 | void 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 | |
353 | void 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 | |
364 | void 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 | |
373 | void 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 | |
393 | void 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 | |