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" |
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, |
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 | |
48 | void 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 | |
268 | static 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 | |
292 | void 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 | |
330 | void 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 | |
341 | void 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 | |
350 | void 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 | |