438 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// 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"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, 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 47void 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 table55 // operand is a symbol: the order of the type and table operands is inverted56 // in the text format relative to the binary format. Otherwise if table the57 // operand isn't a symbol, then we have an MVP compilation unit, and the58 // table shouldn't appear in the output.59 OS << "\t";60 OS << getMnemonic(*MI).first;61 OS << " ";62 63 assert(MI->getNumOperands() == 2);64 const unsigned TypeOperand = 0;65 const unsigned TableOperand = 1;66 if (MI->getOperand(TableOperand).isExpr()) {67 printOperand(MI, TableOperand, OS);68 OS << ", ";69 } else {70 assert(MI->getOperand(TableOperand).getImm() == 0);71 }72 printOperand(MI, TypeOperand, OS);73 break;74 }75 default:76 // Print the instruction (this uses the AsmStrings from the .td files).77 printInstruction(MI, Address, OS);78 break;79 }80 81 // Print any additional variadic operands.82 const MCInstrDesc &Desc = MII.get(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 MCInstLower91 NumVariadicDefs = MI->getOperand(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, I, OS, 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(Depth).second)116 return;117 if (Depth >= ControlFlowStack.size()) {118 printAnnotation(OS, "Invalid depth argument!");119 } else {120 const auto &Pair = ControlFlowStack.rbegin()[Depth];121 printAnnotation(OS, utostr(Depth) + ": " + (Pair.second ? "up" : "down") +122 " to label" + utostr(Pair.first));123 }124 };125 126 if (CommentStream) {127 // Observe any effects on the control flow stack, for use in annotating128 // 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, "label" + utostr(ControlFlowCounter) + ':');137 ControlFlowStack.push_back(std::make_pair(ControlFlowCounter++, true));138 return;139 140 case WebAssembly::BLOCK:141 case WebAssembly::BLOCK_S:142 ControlFlowStack.push_back(std::make_pair(ControlFlowCounter++, false));143 return;144 145 case WebAssembly::TRY:146 case WebAssembly::TRY_S:147 ControlFlowStack.push_back(std::make_pair(ControlFlowCounter, false));148 TryStack.push_back(ControlFlowCounter++);149 EHInstStack.push_back(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(OpIdx++);157 unsigned NumCatches = Op.getImm();158 for (unsigned I = 0; I < NumCatches; I++) {159 int64_t CatchOpcode = MI->getOperand(OpIdx++).getImm();160 if (CatchOpcode == wasm::WASM_OPCODE_CATCH ||161 CatchOpcode == wasm::WASM_OPCODE_CATCH_REF)162 OpIdx++; // Skip tag163 PrintBranchAnnotation(MI->getOperand(OpIdx++), Printed);164 }165 ControlFlowStack.push_back(std::make_pair(ControlFlowCounter++, false));166 return;167 }168 169 case WebAssembly::END_LOOP:170 case WebAssembly::END_LOOP_S:171 if (ControlFlowStack.empty()) {172 printAnnotation(OS, "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, "End marker mismatch!");184 } else {185 printAnnotation(186 OS, "label" + utostr(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, "End marker mismatch!");194 } else {195 printAnnotation(196 OS, "label" + utostr(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, so206 // we print a label only for the first 'catch' label.207 if (EHInstStack.empty()) {208 printAnnotation(OS, "try-catch mismatch!");209 } else if (EHInstStack.back() == CATCH_ALL_LEGACY) {210 printAnnotation(OS, "catch/catch_all cannot occur after catch_all");211 } else if (EHInstStack.back() == TRY) {212 if (TryStack.empty()) {213 printAnnotation(OS, "try-catch mismatch!");214 } else {215 printAnnotation(OS, "catch" + utostr(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(CATCH_LEGACY);221 } else {222 EHInstStack.push_back(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. If230 // there's no enclosing catch scope, it throws up to the caller.231 if (TryStack.empty()) {232 printAnnotation(OS, "to caller");233 } else {234 printAnnotation(OS, "down to catch" + utostr(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, "try-delegate mismatch!");242 } else {243 // 'delegate' is244 // 1. A marker for the end of block label245 // 2. A destination for throwing instructions246 // 3. An instruction that itself rethrows to another 'catch'247 assert(ControlFlowStack.back().first == TryStack.back());248 std::string Label = "label/catch" +249 utostr(ControlFlowStack.pop_back_val().first) +250 ": ";251 TryStack.pop_back();252 EHInstStack.pop_back();253 uint64_t Depth = MI->getOperand(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, "delegate cannot target a loop");260 else261 Label += "down to catch" + utostr(Pair.first);262 }263 printAnnotation(OS, 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 in280 // br_table) which are basic block targets, or for call instructions281 // when using -wasm-keep-registers (in which case they are registers,282 // and should not be processed).283 if (!MI->getOperand(I).isImm())284 continue;285 }286 PrintBranchAnnotation(MI->getOperand(I), Printed);287 }288 }289}290 291static std::string toString(const APFloat &FP) {292 // Print NaNs with custom payloads specially.293 if (FP.isNaN() && !FP.bitwiseIsEqual(APFloat::getQNaN(FP.getSemantics())) &&294 !FP.bitwiseIsEqual(295 APFloat::getQNaN(FP.getSemantics(), /*Negative=*/true))) {296 APInt AI = FP.bitcastToAPInt();297 return std::string(AI.isNegative() ? "-" : "") + "nan:0x" +298 utohexstr(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 Buf, /*HexDigits=*/0, /*UpperCase=*/false, APFloat::rmNearestTiesToEven);309 (void)Written;310 assert(Written != 0);311 assert(Written < BufBytes);312 return Buf;313}314 315void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,316 raw_ostream &O, bool IsVariadicDef) {317 const MCOperand &Op = MI->getOperand(OpNo);318 if (Op.isReg()) {319 const MCInstrDesc &Desc = MII.get(MI->getOpcode());320 MCRegister WAReg = Op.getReg();321 if (int(WAReg.id()) >= 0)322 printRegName(O, WAReg);323 else if (OpNo >= Desc.getNumDefs() && !IsVariadicDef)324 O << "$pop" << WebAssembly::getWARegStackId(WAReg);325 else if (WAReg != WebAssembly::UnusedReg)326 O << "$push" << WebAssembly::getWARegStackId(WAReg);327 else328 O << "$drop";329 // Add a '=' suffix if this is a def.330 if (OpNo < MII.get(MI->getOpcode()).getNumDefs() || IsVariadicDef)331 O << '=';332 } else if (Op.isImm()) {333 O << Op.getImm();334 } else if (Op.isSFPImm()) {335 O << ::toString(APFloat(APFloat::IEEEsingle(), APInt(32, Op.getSFPImm())));336 } else if (Op.isDFPImm()) {337 O << ::toString(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 print341 // as a signature here, such that the assembler can recover this342 // 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(Sym.getSignature());347 } else {348 MAI.printExpr(O, *Op.getExpr());349 }350 }351}352 353void 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).getImm();360 }361 O << "}";362}363 364void WebAssemblyInstPrinter::printWebAssemblyP2AlignOperand(const MCInst *MI,365 unsigned OpNo,366 raw_ostream &O) {367 int64_t Imm = MI->getOperand(OpNo).getImm();368 if (Imm == WebAssembly::GetDefaultP2Align(MI->getOpcode()))369 return;370 O << ":p2align=" << Imm;371}372 373void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(const MCInst *MI,374 unsigned OpNo,375 raw_ostream &O) {376 const MCOperand &Op = MI->getOperand(OpNo);377 if (Op.isImm()) {378 auto Imm = static_cast<unsigned>(Op.getImm());379 if (Imm != wasm::WASM_TYPE_NORESULT)380 O << WebAssembly::anyTypeToString(Imm);381 } else {382 auto Expr = cast<MCSymbolRefExpr>(Op.getExpr());383 auto *Sym = static_cast<const MCSymbolWasm *>(&Expr->getSymbol());384 if (Sym->getSignature()) {385 O << WebAssembly::signatureToString(Sym->getSignature());386 } else {387 // Disassembler does not currently produce a signature388 O << "unknown_type";389 }390 }391}392 393void WebAssemblyInstPrinter::printCatchList(const MCInst *MI, unsigned OpNo,394 raw_ostream &O) {395 unsigned OpIdx = OpNo;396 const MCOperand &Op = MI->getOperand(OpIdx++);397 unsigned NumCatches = Op.getImm();398 399 auto PrintTagOp = [&](const MCOperand &Op) {400 const MCSymbolRefExpr *TagExpr = nullptr;401 const MCSymbol *TagSym = nullptr;402 if (Op.isExpr()) {403 TagExpr = cast<MCSymbolRefExpr>(Op.getExpr());404 TagSym = &TagExpr->getSymbol();405 O << TagSym->getName() << " ";406 } else {407 // When instructions are parsed from the disassembler, we have an408 // immediate tag index and not a tag expr409 O << Op.getImm() << " ";410 }411 };412 413 for (unsigned I = 0; I < NumCatches; I++) {414 const MCOperand &Op = MI->getOperand(OpIdx++);415 O << "(";416 switch (Op.getImm()) {417 case wasm::WASM_OPCODE_CATCH:418 O << "catch ";419 PrintTagOp(MI->getOperand(OpIdx++));420 break;421 case wasm::WASM_OPCODE_CATCH_REF:422 O << "catch_ref ";423 PrintTagOp(MI->getOperand(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(OpIdx++).getImm(); // destination433 O << ")";434 if (I < NumCatches - 1)435 O << " ";436 }437}438