CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
386 lines
14 KiB
C++
386 lines
14 KiB
C++
//==- WebAssemblyDisassembler.cpp - Disassembler for WebAssembly -*- C++ -*-==//
|
|
//
|
|
// 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
|
|
/// This file is part of the WebAssembly Disassembler.
|
|
///
|
|
/// It contains code to translate the data produced by the decoder into
|
|
/// MCInsts.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MCTargetDesc/WebAssemblyMCAsmInfo.h"
|
|
#include "MCTargetDesc/WebAssemblyMCTypeUtilities.h"
|
|
#include "TargetInfo/WebAssemblyTargetInfo.h"
|
|
#include "llvm/BinaryFormat/Wasm.h"
|
|
#include "llvm/MC/MCContext.h"
|
|
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
|
|
#include "llvm/MC/MCInst.h"
|
|
#include "llvm/MC/MCInstrInfo.h"
|
|
#include "llvm/MC/MCSubtargetInfo.h"
|
|
#include "llvm/MC/MCSymbol.h"
|
|
#include "llvm/MC/MCSymbolWasm.h"
|
|
#include "llvm/MC/TargetRegistry.h"
|
|
#include "llvm/Support/Compiler.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/LEB128.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "wasm-disassembler"
|
|
|
|
using DecodeStatus = MCDisassembler::DecodeStatus;
|
|
|
|
#include "WebAssemblyGenDisassemblerTables.inc"
|
|
|
|
static constexpr int WebAssemblyInstructionTableSize = 256;
|
|
|
|
namespace {
|
|
class WebAssemblyDisassembler final : public MCDisassembler {
|
|
std::unique_ptr<const MCInstrInfo> MCII;
|
|
|
|
DecodeStatus getInstruction(MCInst &Instr, uint64_t &Size,
|
|
ArrayRef<uint8_t> Bytes, uint64_t Address,
|
|
raw_ostream &CStream) const override;
|
|
|
|
Expected<bool> onSymbolStart(SymbolInfoTy &Symbol, uint64_t &Size,
|
|
ArrayRef<uint8_t> Bytes,
|
|
uint64_t Address) const override;
|
|
|
|
public:
|
|
WebAssemblyDisassembler(const MCSubtargetInfo &STI, MCContext &Ctx,
|
|
std::unique_ptr<const MCInstrInfo> MCII)
|
|
: MCDisassembler(STI, Ctx), MCII(std::move(MCII)) {}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
static MCDisassembler *createWebAssemblyDisassembler(const Target &T,
|
|
const MCSubtargetInfo &STI,
|
|
MCContext &Ctx) {
|
|
std::unique_ptr<const MCInstrInfo> MCII(T.createMCInstrInfo());
|
|
return new WebAssemblyDisassembler(STI, Ctx, std::move(MCII));
|
|
}
|
|
|
|
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
|
LLVMInitializeWebAssemblyDisassembler() {
|
|
// Register the disassembler for each target.
|
|
TargetRegistry::RegisterMCDisassembler(getTheWebAssemblyTarget32(),
|
|
createWebAssemblyDisassembler);
|
|
TargetRegistry::RegisterMCDisassembler(getTheWebAssemblyTarget64(),
|
|
createWebAssemblyDisassembler);
|
|
}
|
|
|
|
static int nextByte(ArrayRef<uint8_t> Bytes, uint64_t &Size) {
|
|
if (Size >= Bytes.size())
|
|
return -1;
|
|
auto V = Bytes[Size];
|
|
Size++;
|
|
return V;
|
|
}
|
|
|
|
static bool nextLEB(int64_t &Val, ArrayRef<uint8_t> Bytes, uint64_t &Size,
|
|
bool Signed) {
|
|
unsigned N = 0;
|
|
const char *Error = nullptr;
|
|
Val = Signed ? decodeSLEB128(Bytes.data() + Size, &N,
|
|
Bytes.data() + Bytes.size(), &Error)
|
|
: static_cast<int64_t>(decodeULEB128(Bytes.data() + Size, &N,
|
|
Bytes.data() + Bytes.size(),
|
|
&Error));
|
|
if (Error)
|
|
return false;
|
|
Size += N;
|
|
return true;
|
|
}
|
|
|
|
static bool parseLEBImmediate(MCInst &MI, uint64_t &Size,
|
|
ArrayRef<uint8_t> Bytes, bool Signed) {
|
|
int64_t Val;
|
|
if (!nextLEB(Val, Bytes, Size, Signed))
|
|
return false;
|
|
MI.addOperand(MCOperand::createImm(Val));
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool parseImmediate(MCInst &MI, uint64_t &Size, ArrayRef<uint8_t> Bytes) {
|
|
if (Size + sizeof(T) > Bytes.size())
|
|
return false;
|
|
T Val =
|
|
support::endian::read<T, llvm::endianness::little>(Bytes.data() + Size);
|
|
Size += sizeof(T);
|
|
if (std::is_floating_point<T>::value) {
|
|
MI.addOperand(
|
|
MCOperand::createDFPImm(bit_cast<uint64_t>(static_cast<double>(Val))));
|
|
} else {
|
|
MI.addOperand(MCOperand::createImm(static_cast<int64_t>(Val)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Expected<bool> WebAssemblyDisassembler::onSymbolStart(SymbolInfoTy &Symbol,
|
|
uint64_t &Size,
|
|
ArrayRef<uint8_t> Bytes,
|
|
uint64_t Address) const {
|
|
Size = 0;
|
|
if (Symbol.Type == wasm::WASM_SYMBOL_TYPE_SECTION) {
|
|
// Start of a code section: we're parsing only the function count.
|
|
int64_t FunctionCount;
|
|
if (!nextLEB(FunctionCount, Bytes, Size, false))
|
|
return false;
|
|
outs() << " # " << FunctionCount << " functions in section.";
|
|
} else {
|
|
// Parse the start of a single function.
|
|
int64_t BodySize, LocalEntryCount;
|
|
if (!nextLEB(BodySize, Bytes, Size, false) ||
|
|
!nextLEB(LocalEntryCount, Bytes, Size, false))
|
|
return false;
|
|
if (LocalEntryCount) {
|
|
outs() << " .local ";
|
|
for (int64_t I = 0; I < LocalEntryCount; I++) {
|
|
int64_t Count, Type;
|
|
if (!nextLEB(Count, Bytes, Size, false) ||
|
|
!nextLEB(Type, Bytes, Size, false))
|
|
return false;
|
|
for (int64_t J = 0; J < Count; J++) {
|
|
if (I || J)
|
|
outs() << ", ";
|
|
outs() << WebAssembly::anyTypeToString(Type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
outs() << "\n";
|
|
return true;
|
|
}
|
|
|
|
MCDisassembler::DecodeStatus WebAssemblyDisassembler::getInstruction(
|
|
MCInst &MI, uint64_t &Size, ArrayRef<uint8_t> Bytes, uint64_t /*Address*/,
|
|
raw_ostream &CS) const {
|
|
CommentStream = &CS;
|
|
Size = 0;
|
|
int Opc = nextByte(Bytes, Size);
|
|
if (Opc < 0)
|
|
return MCDisassembler::Fail;
|
|
const auto *WasmInst = &InstructionTable0[Opc];
|
|
// If this is a prefix byte, indirect to another table.
|
|
if (WasmInst->ET == ET_Prefix) {
|
|
WasmInst = nullptr;
|
|
// Linear search, so far only 4 entries.
|
|
for (const auto &[Prefix, Table] : PrefixTable) {
|
|
if (Prefix == Opc) {
|
|
WasmInst = Table;
|
|
break;
|
|
}
|
|
}
|
|
if (!WasmInst)
|
|
return MCDisassembler::Fail;
|
|
int64_t PrefixedOpc;
|
|
if (!nextLEB(PrefixedOpc, Bytes, Size, false))
|
|
return MCDisassembler::Fail;
|
|
if (PrefixedOpc < 0 || PrefixedOpc >= WebAssemblyInstructionTableSize)
|
|
return MCDisassembler::Fail;
|
|
WasmInst += PrefixedOpc;
|
|
}
|
|
if (WasmInst->ET == ET_Unused)
|
|
return MCDisassembler::Fail;
|
|
// At this point we must have a valid instruction to decode.
|
|
assert(WasmInst->ET == ET_Instruction);
|
|
MI.setOpcode(WasmInst->Opcode);
|
|
// Parse any operands.
|
|
for (uint8_t OPI = 0; OPI < WasmInst->NumOperands; OPI++) {
|
|
auto OT = OperandTable[WasmInst->OperandStart + OPI];
|
|
switch (OT) {
|
|
// ULEB operands:
|
|
case WebAssembly::OPERAND_BASIC_BLOCK:
|
|
case WebAssembly::OPERAND_LOCAL:
|
|
case WebAssembly::OPERAND_GLOBAL:
|
|
case WebAssembly::OPERAND_FUNCTION32:
|
|
case WebAssembly::OPERAND_TABLE:
|
|
case WebAssembly::OPERAND_OFFSET32:
|
|
case WebAssembly::OPERAND_OFFSET64:
|
|
case WebAssembly::OPERAND_P2ALIGN:
|
|
case WebAssembly::OPERAND_TYPEINDEX:
|
|
case WebAssembly::OPERAND_TAG:
|
|
case MCOI::OPERAND_IMMEDIATE: {
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false))
|
|
return MCDisassembler::Fail;
|
|
if (OT == WebAssembly::OPERAND_P2ALIGN) {
|
|
// The byte that encodes P2align also encodes whether a memory index
|
|
// and/or ordering is present.
|
|
int64_t Val = MI.getOperand(MI.getNumOperands() - 1).getImm();
|
|
if (Val & wasm::WASM_MEMARG_HAS_MEM_ORDER) {
|
|
// Clear the order so we just have the alignment in this operand.
|
|
MI.getOperand(MI.getNumOperands() - 1)
|
|
.setImm(Val & ~wasm::WASM_MEMARG_HAS_MEM_ORDER);
|
|
if (Size >= Bytes.size())
|
|
return MCDisassembler::Fail;
|
|
uint8_t Order = Bytes[Size++];
|
|
// If we have a memory ordering, it must have been preceded by a
|
|
// MEMORDER operand which was added as a placeholder (because we are
|
|
// iterating in MI operand order which has mem order first, but this
|
|
// byte is encoded first).
|
|
assert(OPI > 0 &&
|
|
OperandTable[WasmInst->OperandStart + OPI - 1] ==
|
|
WebAssembly::OPERAND_MEMORDER &&
|
|
"P2ALIGN with memory order not preceded by MEMORDER");
|
|
if (Order == wasm::WASM_MEM_ORDER_RMW_ACQ_REL ||
|
|
Order == wasm::WASM_MEM_ORDER_ACQ_REL)
|
|
MI.getOperand(MI.getNumOperands() - 2)
|
|
.setImm(wasm::WASM_MEM_ORDER_ACQ_REL);
|
|
else
|
|
MI.getOperand(MI.getNumOperands() - 2)
|
|
.setImm(wasm::WASM_MEM_ORDER_SEQ_CST);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// SLEB operands:
|
|
case WebAssembly::OPERAND_I32IMM:
|
|
case WebAssembly::OPERAND_I64IMM: {
|
|
if (!parseLEBImmediate(MI, Size, Bytes, true))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
// block_type operands:
|
|
case WebAssembly::OPERAND_SIGNATURE: {
|
|
int64_t Val;
|
|
uint64_t PrevSize = Size;
|
|
if (!nextLEB(Val, Bytes, Size, true))
|
|
return MCDisassembler::Fail;
|
|
if (Val < 0) {
|
|
// Negative values are single septet value types or empty types
|
|
if (Size != PrevSize + 1) {
|
|
MI.addOperand(
|
|
MCOperand::createImm(int64_t(WebAssembly::BlockType::Invalid)));
|
|
} else {
|
|
MI.addOperand(MCOperand::createImm(Val & 0x7f));
|
|
}
|
|
} else {
|
|
// We don't have access to the signature, so create a symbol without one
|
|
MCSymbol *Sym = getContext().createTempSymbol("typeindex", true);
|
|
auto *WasmSym = static_cast<MCSymbolWasm *>(Sym);
|
|
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
|
|
const MCExpr *Expr = MCSymbolRefExpr::create(
|
|
WasmSym, WebAssembly::S_TYPEINDEX, getContext());
|
|
MI.addOperand(MCOperand::createExpr(Expr));
|
|
}
|
|
break;
|
|
}
|
|
// FP operands.
|
|
case WebAssembly::OPERAND_F32IMM: {
|
|
if (!parseImmediate<float>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_F64IMM: {
|
|
if (!parseImmediate<double>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
// Vector lane operands (not LEB encoded).
|
|
case WebAssembly::OPERAND_VEC_I8IMM: {
|
|
if (!parseImmediate<uint8_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_MEMORDER: {
|
|
uint8_t Val;
|
|
if (OPI + 1 < WasmInst->NumOperands &&
|
|
OperandTable[WasmInst->OperandStart + OPI + 1] ==
|
|
WebAssembly::OPERAND_P2ALIGN) {
|
|
// If we have P2ALIGN next, it will be encoded as part of the memarg,
|
|
// which has not been parsed yet. Default to SEQ_CST
|
|
// and we will update it when we parse P2ALIGN if necessary.
|
|
Val = wasm::WASM_MEM_ORDER_SEQ_CST;
|
|
} else {
|
|
// atomic.fence instructions have no p2align operand.
|
|
if (Size >= Bytes.size())
|
|
return MCDisassembler::Fail;
|
|
Val = Bytes[Size++];
|
|
}
|
|
if (Val == wasm::WASM_MEM_ORDER_RMW_ACQ_REL ||
|
|
Val == wasm::WASM_MEM_ORDER_ACQ_REL)
|
|
MI.addOperand(MCOperand::createImm(wasm::WASM_MEM_ORDER_ACQ_REL));
|
|
else
|
|
MI.addOperand(MCOperand::createImm(wasm::WASM_MEM_ORDER_SEQ_CST));
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_VEC_I16IMM: {
|
|
if (!parseImmediate<uint16_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_VEC_I32IMM: {
|
|
if (!parseImmediate<uint32_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_VEC_I64IMM: {
|
|
if (!parseImmediate<uint64_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_BRLIST: {
|
|
int64_t TargetTableLen;
|
|
if (!nextLEB(TargetTableLen, Bytes, Size, false))
|
|
return MCDisassembler::Fail;
|
|
for (int64_t I = 0; I < TargetTableLen; I++) {
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false))
|
|
return MCDisassembler::Fail;
|
|
}
|
|
// Default case.
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false))
|
|
return MCDisassembler::Fail;
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_CATCH_LIST: {
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false))
|
|
return MCDisassembler::Fail;
|
|
int64_t NumCatches = MI.getOperand(MI.getNumOperands() - 1).getImm();
|
|
for (int64_t I = 0; I < NumCatches; I++) {
|
|
if (!parseImmediate<uint8_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
int64_t CatchOpcode = MI.getOperand(MI.getNumOperands() - 1).getImm();
|
|
if (CatchOpcode == wasm::WASM_OPCODE_CATCH ||
|
|
CatchOpcode == wasm::WASM_OPCODE_CATCH_REF) {
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false)) // tag index
|
|
return MCDisassembler::Fail;
|
|
}
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false)) // destination
|
|
return MCDisassembler::Fail;
|
|
}
|
|
break;
|
|
}
|
|
case WebAssembly::OPERAND_VALTYPE_LIST: {
|
|
// A vec of valtypes: a ULEB count followed by that many value types.
|
|
// Common valtypes (i32, i64, f32, f64, v128, funcref, externref, exnref)
|
|
// all encode as a single byte; read the raw byte to preserve the wasm
|
|
// type code for later printing.
|
|
if (!parseLEBImmediate(MI, Size, Bytes, false))
|
|
return MCDisassembler::Fail;
|
|
int64_t NumTypes = MI.getOperand(MI.getNumOperands() - 1).getImm();
|
|
for (int64_t I = 0; I < NumTypes; I++) {
|
|
if (!parseImmediate<uint8_t>(MI, Size, Bytes))
|
|
return MCDisassembler::Fail;
|
|
}
|
|
break;
|
|
}
|
|
case MCOI::OPERAND_REGISTER:
|
|
// The tablegen header currently does not have any register operands since
|
|
// we use only the stack (_S) instructions.
|
|
// If you hit this that probably means a bad instruction definition in
|
|
// tablegen.
|
|
llvm_unreachable("Register operand in WebAssemblyDisassembler");
|
|
default:
|
|
llvm_unreachable("Unknown operand type in WebAssemblyDisassembler");
|
|
}
|
|
}
|
|
return MCDisassembler::Success;
|
|
}
|