The WebAssembly text and binary formats have different operand orders for the "type" and "table" fields of call_indirect (and return_call_indirect). In LLVM we use the binary order for the MCInstr, but when we produce or consume the text format we should use the text order. For compilation units targetting WebAssembly 1.0 (without the reference types feature), we omit the table operand entirely. Differential Revision: https://reviews.llvm.org/D97761
440 lines
15 KiB
C++
440 lines
15 KiB
C++
//=- WebAssemblyInstPrinter.cpp - WebAssembly assembly instruction printing -=//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// Print MCInst instructions to wasm format.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MCTargetDesc/WebAssemblyInstPrinter.h"
|
|
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
|
#include "WebAssembly.h"
|
|
#include "WebAssemblyMachineFunctionInfo.h"
|
|
#include "WebAssemblyUtilities.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/CodeGen/TargetRegisterInfo.h"
|
|
#include "llvm/MC/MCExpr.h"
|
|
#include "llvm/MC/MCInst.h"
|
|
#include "llvm/MC/MCInstrInfo.h"
|
|
#include "llvm/MC/MCSubtargetInfo.h"
|
|
#include "llvm/MC/MCSymbol.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormattedStream.h"
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "asm-printer"
|
|
|
|
#include "WebAssemblyGenAsmWriter.inc"
|
|
|
|
WebAssemblyInstPrinter::WebAssemblyInstPrinter(const MCAsmInfo &MAI,
|
|
const MCInstrInfo &MII,
|
|
const MCRegisterInfo &MRI)
|
|
: MCInstPrinter(MAI, MII, MRI) {}
|
|
|
|
void WebAssemblyInstPrinter::printRegName(raw_ostream &OS,
|
|
unsigned RegNo) const {
|
|
assert(RegNo != WebAssemblyFunctionInfo::UnusedReg);
|
|
// Note that there's an implicit local.get/local.set here!
|
|
OS << "$" << RegNo;
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printInst(const MCInst *MI, uint64_t Address,
|
|
StringRef Annot,
|
|
const MCSubtargetInfo &STI,
|
|
raw_ostream &OS) {
|
|
switch (MI->getOpcode()) {
|
|
case WebAssembly::CALL_INDIRECT_S:
|
|
case WebAssembly::RET_CALL_INDIRECT_S: {
|
|
// A special case for call_indirect (and ret_call_indirect), if the table
|
|
// operand is a symbol: the order of the type and table operands is inverted
|
|
// in the text format relative to the binary format. Otherwise if table the
|
|
// operand isn't a symbol, then we have an MVP compilation unit, and the
|
|
// table shouldn't appear in the output.
|
|
OS << "\t";
|
|
OS << getMnemonic(MI).first;
|
|
OS << " ";
|
|
|
|
assert(MI->getNumOperands() == 2);
|
|
const unsigned TypeOperand = 0;
|
|
const unsigned TableOperand = 1;
|
|
if (MI->getOperand(TableOperand).isExpr()) {
|
|
printOperand(MI, TableOperand, OS);
|
|
OS << ", ";
|
|
} else {
|
|
assert(MI->getOperand(TableOperand).getImm() == 0);
|
|
}
|
|
printOperand(MI, TypeOperand, OS);
|
|
break;
|
|
}
|
|
default:
|
|
// Print the instruction (this uses the AsmStrings from the .td files).
|
|
printInstruction(MI, Address, OS);
|
|
break;
|
|
}
|
|
|
|
// Print any additional variadic operands.
|
|
const MCInstrDesc &Desc = MII.get(MI->getOpcode());
|
|
if (Desc.isVariadic()) {
|
|
if ((Desc.getNumOperands() == 0 && MI->getNumOperands() > 0) ||
|
|
Desc.variadicOpsAreDefs())
|
|
OS << "\t";
|
|
unsigned Start = Desc.getNumOperands();
|
|
unsigned NumVariadicDefs = 0;
|
|
if (Desc.variadicOpsAreDefs()) {
|
|
// The number of variadic defs is encoded in an immediate by MCInstLower
|
|
NumVariadicDefs = MI->getOperand(0).getImm();
|
|
Start = 1;
|
|
}
|
|
bool NeedsComma = Desc.getNumOperands() > 0 && !Desc.variadicOpsAreDefs();
|
|
for (auto I = Start, E = MI->getNumOperands(); I < E; ++I) {
|
|
if (MI->getOpcode() == WebAssembly::CALL_INDIRECT &&
|
|
I - Start == NumVariadicDefs) {
|
|
// Skip type and table arguments when printing for tests.
|
|
++I;
|
|
continue;
|
|
}
|
|
if (NeedsComma)
|
|
OS << ", ";
|
|
printOperand(MI, I, OS, I - Start < NumVariadicDefs);
|
|
NeedsComma = true;
|
|
}
|
|
}
|
|
|
|
// Print any added annotation.
|
|
printAnnotation(OS, Annot);
|
|
|
|
if (CommentStream) {
|
|
// Observe any effects on the control flow stack, for use in annotating
|
|
// control flow label references.
|
|
unsigned Opc = MI->getOpcode();
|
|
switch (Opc) {
|
|
default:
|
|
break;
|
|
|
|
case WebAssembly::LOOP:
|
|
case WebAssembly::LOOP_S:
|
|
printAnnotation(OS, "label" + utostr(ControlFlowCounter) + ':');
|
|
ControlFlowStack.push_back(std::make_pair(ControlFlowCounter++, true));
|
|
return;
|
|
|
|
case WebAssembly::BLOCK:
|
|
case WebAssembly::BLOCK_S:
|
|
ControlFlowStack.push_back(std::make_pair(ControlFlowCounter++, false));
|
|
return;
|
|
|
|
case WebAssembly::TRY:
|
|
case WebAssembly::TRY_S:
|
|
ControlFlowStack.push_back(std::make_pair(ControlFlowCounter, false));
|
|
TryStack.push_back(ControlFlowCounter++);
|
|
EHInstStack.push_back(TRY);
|
|
return;
|
|
|
|
case WebAssembly::END_LOOP:
|
|
case WebAssembly::END_LOOP_S:
|
|
if (ControlFlowStack.empty()) {
|
|
printAnnotation(OS, "End marker mismatch!");
|
|
} else {
|
|
ControlFlowStack.pop_back();
|
|
}
|
|
return;
|
|
|
|
case WebAssembly::END_BLOCK:
|
|
case WebAssembly::END_BLOCK_S:
|
|
if (ControlFlowStack.empty()) {
|
|
printAnnotation(OS, "End marker mismatch!");
|
|
} else {
|
|
printAnnotation(
|
|
OS, "label" + utostr(ControlFlowStack.pop_back_val().first) + ':');
|
|
}
|
|
return;
|
|
|
|
case WebAssembly::END_TRY:
|
|
case WebAssembly::END_TRY_S:
|
|
if (ControlFlowStack.empty() || EHInstStack.empty()) {
|
|
printAnnotation(OS, "End marker mismatch!");
|
|
} else {
|
|
printAnnotation(
|
|
OS, "label" + utostr(ControlFlowStack.pop_back_val().first) + ':');
|
|
EHInstStack.pop_back();
|
|
}
|
|
return;
|
|
|
|
case WebAssembly::CATCH:
|
|
case WebAssembly::CATCH_S:
|
|
case WebAssembly::CATCH_ALL:
|
|
case WebAssembly::CATCH_ALL_S:
|
|
// There can be multiple catch instructions for one try instruction, so
|
|
// we print a label only for the first 'catch' label.
|
|
if (EHInstStack.empty()) {
|
|
printAnnotation(OS, "try-catch mismatch!");
|
|
} else if (EHInstStack.back() == CATCH_ALL) {
|
|
printAnnotation(OS, "catch/catch_all cannot occur after catch_all");
|
|
} else if (EHInstStack.back() == TRY) {
|
|
if (TryStack.empty()) {
|
|
printAnnotation(OS, "try-catch mismatch!");
|
|
} else {
|
|
printAnnotation(OS, "catch" + utostr(TryStack.pop_back_val()) + ':');
|
|
}
|
|
EHInstStack.pop_back();
|
|
if (Opc == WebAssembly::CATCH || Opc == WebAssembly::CATCH_S) {
|
|
EHInstStack.push_back(CATCH);
|
|
} else {
|
|
EHInstStack.push_back(CATCH_ALL);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case WebAssembly::RETHROW:
|
|
case WebAssembly::RETHROW_S:
|
|
// 'rethrow' rethrows to the nearest enclosing catch scope, if any. If
|
|
// there's no enclosing catch scope, it throws up to the caller.
|
|
if (TryStack.empty()) {
|
|
printAnnotation(OS, "to caller");
|
|
} else {
|
|
printAnnotation(OS, "down to catch" + utostr(TryStack.back()));
|
|
}
|
|
return;
|
|
|
|
case WebAssembly::DELEGATE:
|
|
case WebAssembly::DELEGATE_S:
|
|
if (ControlFlowStack.empty() || TryStack.empty() || EHInstStack.empty()) {
|
|
printAnnotation(OS, "try-delegate mismatch!");
|
|
} else {
|
|
// 'delegate' is
|
|
// 1. A marker for the end of block label
|
|
// 2. A destination for throwing instructions
|
|
// 3. An instruction that itself rethrows to another 'catch'
|
|
assert(ControlFlowStack.back().first == TryStack.back());
|
|
std::string Label = "label/catch" +
|
|
utostr(ControlFlowStack.pop_back_val().first) +
|
|
": ";
|
|
TryStack.pop_back();
|
|
EHInstStack.pop_back();
|
|
uint64_t Depth = MI->getOperand(0).getImm();
|
|
if (Depth >= ControlFlowStack.size()) {
|
|
Label += "to caller";
|
|
} else {
|
|
const auto &Pair = ControlFlowStack.rbegin()[Depth];
|
|
if (Pair.second)
|
|
printAnnotation(OS, "delegate cannot target a loop");
|
|
else
|
|
Label += "down to catch" + utostr(Pair.first);
|
|
}
|
|
printAnnotation(OS, Label);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Annotate any control flow label references.
|
|
|
|
unsigned NumFixedOperands = Desc.NumOperands;
|
|
SmallSet<uint64_t, 8> Printed;
|
|
for (unsigned I = 0, E = MI->getNumOperands(); I < E; ++I) {
|
|
// See if this operand denotes a basic block target.
|
|
if (I < NumFixedOperands) {
|
|
// A non-variable_ops operand, check its type.
|
|
if (Desc.OpInfo[I].OperandType != WebAssembly::OPERAND_BASIC_BLOCK)
|
|
continue;
|
|
} else {
|
|
// A variable_ops operand, which currently can be immediates (used in
|
|
// br_table) which are basic block targets, or for call instructions
|
|
// when using -wasm-keep-registers (in which case they are registers,
|
|
// and should not be processed).
|
|
if (!MI->getOperand(I).isImm())
|
|
continue;
|
|
}
|
|
uint64_t Depth = MI->getOperand(I).getImm();
|
|
if (!Printed.insert(Depth).second)
|
|
continue;
|
|
if (Depth >= ControlFlowStack.size()) {
|
|
printAnnotation(OS, "Invalid depth argument!");
|
|
} else {
|
|
const auto &Pair = ControlFlowStack.rbegin()[Depth];
|
|
printAnnotation(OS, utostr(Depth) + ": " +
|
|
(Pair.second ? "up" : "down") + " to label" +
|
|
utostr(Pair.first));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::string toString(const APFloat &FP) {
|
|
// Print NaNs with custom payloads specially.
|
|
if (FP.isNaN() && !FP.bitwiseIsEqual(APFloat::getQNaN(FP.getSemantics())) &&
|
|
!FP.bitwiseIsEqual(
|
|
APFloat::getQNaN(FP.getSemantics(), /*Negative=*/true))) {
|
|
APInt AI = FP.bitcastToAPInt();
|
|
return std::string(AI.isNegative() ? "-" : "") + "nan:0x" +
|
|
utohexstr(AI.getZExtValue() &
|
|
(AI.getBitWidth() == 32 ? INT64_C(0x007fffff)
|
|
: INT64_C(0x000fffffffffffff)),
|
|
/*LowerCase=*/true);
|
|
}
|
|
|
|
// Use C99's hexadecimal floating-point representation.
|
|
static const size_t BufBytes = 128;
|
|
char Buf[BufBytes];
|
|
auto Written = FP.convertToHexString(
|
|
Buf, /*HexDigits=*/0, /*UpperCase=*/false, APFloat::rmNearestTiesToEven);
|
|
(void)Written;
|
|
assert(Written != 0);
|
|
assert(Written < BufBytes);
|
|
return Buf;
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
|
|
raw_ostream &O, bool IsVariadicDef) {
|
|
const MCOperand &Op = MI->getOperand(OpNo);
|
|
if (Op.isReg()) {
|
|
const MCInstrDesc &Desc = MII.get(MI->getOpcode());
|
|
unsigned WAReg = Op.getReg();
|
|
if (int(WAReg) >= 0)
|
|
printRegName(O, WAReg);
|
|
else if (OpNo >= Desc.getNumDefs() && !IsVariadicDef)
|
|
O << "$pop" << WebAssemblyFunctionInfo::getWARegStackId(WAReg);
|
|
else if (WAReg != WebAssemblyFunctionInfo::UnusedReg)
|
|
O << "$push" << WebAssemblyFunctionInfo::getWARegStackId(WAReg);
|
|
else
|
|
O << "$drop";
|
|
// Add a '=' suffix if this is a def.
|
|
if (OpNo < MII.get(MI->getOpcode()).getNumDefs() || IsVariadicDef)
|
|
O << '=';
|
|
} else if (Op.isImm()) {
|
|
O << Op.getImm();
|
|
} else if (Op.isSFPImm()) {
|
|
O << ::toString(APFloat(APFloat::IEEEsingle(), APInt(32, Op.getSFPImm())));
|
|
} else if (Op.isDFPImm()) {
|
|
O << ::toString(APFloat(APFloat::IEEEdouble(), APInt(64, Op.getDFPImm())));
|
|
} else {
|
|
assert(Op.isExpr() && "unknown operand kind in printOperand");
|
|
// call_indirect instructions have a TYPEINDEX operand that we print
|
|
// as a signature here, such that the assembler can recover this
|
|
// information.
|
|
auto SRE = static_cast<const MCSymbolRefExpr *>(Op.getExpr());
|
|
if (SRE->getKind() == MCSymbolRefExpr::VK_WASM_TYPEINDEX) {
|
|
auto &Sym = static_cast<const MCSymbolWasm &>(SRE->getSymbol());
|
|
O << WebAssembly::signatureToString(Sym.getSignature());
|
|
} else {
|
|
Op.getExpr()->print(O, &MAI);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printBrList(const MCInst *MI, unsigned OpNo,
|
|
raw_ostream &O) {
|
|
O << "{";
|
|
for (unsigned I = OpNo, E = MI->getNumOperands(); I != E; ++I) {
|
|
if (I != OpNo)
|
|
O << ", ";
|
|
O << MI->getOperand(I).getImm();
|
|
}
|
|
O << "}";
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printWebAssemblyP2AlignOperand(const MCInst *MI,
|
|
unsigned OpNo,
|
|
raw_ostream &O) {
|
|
int64_t Imm = MI->getOperand(OpNo).getImm();
|
|
if (Imm == WebAssembly::GetDefaultP2Align(MI->getOpcode()))
|
|
return;
|
|
O << ":p2align=" << Imm;
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printWebAssemblySignatureOperand(const MCInst *MI,
|
|
unsigned OpNo,
|
|
raw_ostream &O) {
|
|
const MCOperand &Op = MI->getOperand(OpNo);
|
|
if (Op.isImm()) {
|
|
auto Imm = static_cast<unsigned>(Op.getImm());
|
|
if (Imm != wasm::WASM_TYPE_NORESULT)
|
|
O << WebAssembly::anyTypeToString(Imm);
|
|
} else {
|
|
auto Expr = cast<MCSymbolRefExpr>(Op.getExpr());
|
|
auto *Sym = cast<MCSymbolWasm>(&Expr->getSymbol());
|
|
if (Sym->getSignature()) {
|
|
O << WebAssembly::signatureToString(Sym->getSignature());
|
|
} else {
|
|
// Disassembler does not currently produce a signature
|
|
O << "unknown_type";
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebAssemblyInstPrinter::printWebAssemblyHeapTypeOperand(const MCInst *MI,
|
|
unsigned OpNo,
|
|
raw_ostream &O) {
|
|
const MCOperand &Op = MI->getOperand(OpNo);
|
|
if (Op.isImm()) {
|
|
switch (Op.getImm()) {
|
|
case long(wasm::ValType::EXTERNREF):
|
|
O << "extern";
|
|
break;
|
|
case long(wasm::ValType::FUNCREF):
|
|
O << "func";
|
|
break;
|
|
default:
|
|
O << "unsupported_heap_type_value";
|
|
break;
|
|
}
|
|
} else {
|
|
// Typed function references and other subtypes of funcref and externref
|
|
// currently unimplemented.
|
|
O << "unsupported_heap_type_operand";
|
|
}
|
|
}
|
|
|
|
// We have various enums representing a subset of these types, use this
|
|
// function to convert any of them to text.
|
|
const char *WebAssembly::anyTypeToString(unsigned Ty) {
|
|
switch (Ty) {
|
|
case wasm::WASM_TYPE_I32:
|
|
return "i32";
|
|
case wasm::WASM_TYPE_I64:
|
|
return "i64";
|
|
case wasm::WASM_TYPE_F32:
|
|
return "f32";
|
|
case wasm::WASM_TYPE_F64:
|
|
return "f64";
|
|
case wasm::WASM_TYPE_V128:
|
|
return "v128";
|
|
case wasm::WASM_TYPE_FUNCREF:
|
|
return "funcref";
|
|
case wasm::WASM_TYPE_EXTERNREF:
|
|
return "externref";
|
|
case wasm::WASM_TYPE_FUNC:
|
|
return "func";
|
|
case wasm::WASM_TYPE_NORESULT:
|
|
return "void";
|
|
default:
|
|
return "invalid_type";
|
|
}
|
|
}
|
|
|
|
const char *WebAssembly::typeToString(wasm::ValType Ty) {
|
|
return anyTypeToString(static_cast<unsigned>(Ty));
|
|
}
|
|
|
|
std::string WebAssembly::typeListToString(ArrayRef<wasm::ValType> List) {
|
|
std::string S;
|
|
for (auto &Ty : List) {
|
|
if (&Ty != &List[0]) S += ", ";
|
|
S += WebAssembly::typeToString(Ty);
|
|
}
|
|
return S;
|
|
}
|
|
|
|
std::string WebAssembly::signatureToString(const wasm::WasmSignature *Sig) {
|
|
std::string S("(");
|
|
S += typeListToString(Sig->Params);
|
|
S += ") -> (";
|
|
S += typeListToString(Sig->Returns);
|
|
S += ")";
|
|
return S;
|
|
}
|