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