| 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 | |