%PDF- %PDF-
| Direktori : /home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/wasm/ |
| Current File : //home2/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/wasm/wasm-disassembler.cc |
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-disassembler.h"
#include "src/debug/debug-interface.h"
#include "src/numbers/conversions.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/names-provider.h"
#include "src/wasm/wasm-disassembler-impl.h"
#include "src/wasm/wasm-opcodes-inl.h"
namespace v8 {
namespace internal {
namespace wasm {
////////////////////////////////////////////////////////////////////////////////
// Public interface.
void Disassemble(const WasmModule* module, ModuleWireBytes wire_bytes,
NamesProvider* names,
v8::debug::DisassemblyCollector* collector,
std::vector<int>* function_body_offsets) {
MultiLineStringBuilder out;
AccountingAllocator allocator;
constexpr bool kCollectOffsets = true;
ModuleDisassembler md(out, module, names, wire_bytes, &allocator,
kCollectOffsets, function_body_offsets);
md.PrintModule({0, 2}, v8_flags.wasm_disassembly_max_mb);
out.ToDisassemblyCollector(collector);
}
void Disassemble(base::Vector<const uint8_t> wire_bytes,
v8::debug::DisassemblyCollector* collector,
std::vector<int>* function_body_offsets) {
ModuleResult result = DecodeWasmModuleForDisassembler(wire_bytes);
MultiLineStringBuilder out;
AccountingAllocator allocator;
constexpr bool kCollectOffsets = true;
if (result.failed()) {
WasmError error = result.error();
out << "Decoding error: " << error.message() << " at offset "
<< error.offset();
out.ToDisassemblyCollector(collector);
return;
}
const WasmModule* module = result.value().get();
NamesProvider names(module, wire_bytes);
ModuleWireBytes module_bytes(wire_bytes);
ModuleDisassembler md(out, module, &names, module_bytes, &allocator,
kCollectOffsets, function_body_offsets);
md.PrintModule({0, 2}, v8_flags.wasm_disassembly_max_mb);
out.ToDisassemblyCollector(collector);
}
void MultiLineStringBuilder::ToDisassemblyCollector(
v8::debug::DisassemblyCollector* collector) {
if (length() != 0) NextLine(0); // Finalize last line.
collector->ReserveLineCount(lines_.size());
for (const Line& l : lines_) {
// Don't include trailing '\n'.
collector->AddLine(l.data, l.len - 1, l.bytecode_offset);
}
}
////////////////////////////////////////////////////////////////////////////////
// Helpers.
static constexpr char kHexChars[] = "0123456789abcdef";
static constexpr char kUpperHexChars[] = "0123456789ABCDEF";
// Returns the log2 of the alignment, e.g. "4" means 2<<4 == 16 bytes.
// This is the same format as used in .wasm binary modules.
uint32_t GetDefaultAlignment(WasmOpcode opcode) {
switch (opcode) {
case kExprS128LoadMem:
case kExprS128StoreMem:
return 4;
case kExprS128Load8x8S:
case kExprS128Load8x8U:
case kExprS128Load16x4S:
case kExprS128Load16x4U:
case kExprS128Load32x2S:
case kExprS128Load32x2U:
case kExprS128Load64Splat:
case kExprS128Load64Zero:
case kExprS128Load64Lane:
case kExprS128Store64Lane:
return 3;
case kExprS128Load32Splat:
case kExprS128Load32Zero:
case kExprS128Load32Lane:
case kExprS128Store32Lane:
return 2;
case kExprS128Load16Splat:
case kExprS128Load16Lane:
case kExprS128Store16Lane:
return 1;
case kExprS128Load8Splat:
case kExprS128Load8Lane:
case kExprS128Store8Lane:
return 0;
#define CASE(Opcode, ...) \
case kExpr##Opcode: \
return GetLoadType(kExpr##Opcode).size_log_2();
FOREACH_LOAD_MEM_OPCODE(CASE)
#undef CASE
#define CASE(Opcode, ...) \
case kExpr##Opcode: \
return GetStoreType(kExpr##Opcode).size_log_2();
FOREACH_STORE_MEM_OPCODE(CASE)
#undef CASE
#define CASE(Opcode, Type) \
case kExpr##Opcode: \
return ElementSizeLog2Of(MachineType::Type().representation());
ATOMIC_OP_LIST(CASE)
ATOMIC_STORE_OP_LIST(CASE)
#undef CASE
default:
UNREACHABLE();
}
}
StringBuilder& operator<<(StringBuilder& sb, uint64_t n) {
if (n == 0) {
*sb.allocate(1) = '0';
return sb;
}
static constexpr size_t kBufferSize = 20; // Just enough for a uint64.
char buffer[kBufferSize];
char* end = buffer + kBufferSize;
char* out = end;
while (n != 0) {
*(--out) = '0' + (n % 10);
n /= 10;
}
sb.write(out, static_cast<size_t>(end - out));
return sb;
}
void PrintSignatureOneLine(StringBuilder& out, const FunctionSig* sig,
uint32_t func_index, NamesProvider* names,
bool param_names,
IndexAsComment indices_as_comments) {
if (param_names) {
for (uint32_t i = 0; i < sig->parameter_count(); i++) {
out << " (param ";
names->PrintLocalName(out, func_index, i, indices_as_comments);
out << ' ';
names->PrintValueType(out, sig->GetParam(i));
out << ")";
}
} else if (sig->parameter_count() > 0) {
out << " (param";
for (uint32_t i = 0; i < sig->parameter_count(); i++) {
out << " ";
names->PrintValueType(out, sig->GetParam(i));
}
out << ")";
}
for (size_t i = 0; i < sig->return_count(); i++) {
out << " (result ";
names->PrintValueType(out, sig->GetReturn(i));
out << ")";
}
}
void PrintStringRaw(StringBuilder& out, const uint8_t* start,
const uint8_t* end) {
for (const uint8_t* ptr = start; ptr < end; ptr++) {
uint8_t b = *ptr;
if (b < 32 || b >= 127 || b == '"' || b == '\\') {
out << '\\' << kHexChars[b >> 4] << kHexChars[b & 0xF];
} else {
out << static_cast<char>(b);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// FunctionBodyDisassembler.
void FunctionBodyDisassembler::DecodeAsWat(MultiLineStringBuilder& out,
Indentation indentation,
FunctionHeader include_header,
uint32_t* first_instruction_offset) {
out_ = &out;
int base_indentation = indentation.current();
// Print header.
if (include_header == kPrintHeader) {
out << indentation << "(func ";
names_->PrintFunctionName(out, func_index_, NamesProvider::kDevTools);
PrintSignatureOneLine(out, sig_, func_index_, names_, true,
kIndicesAsComments);
out.NextLine(pc_offset());
} else {
out.set_current_line_bytecode_offset(pc_offset());
}
indentation.increase();
// Decode and print locals.
uint32_t locals_length = DecodeLocals(pc_);
if (failed()) {
// TODO(jkummerow): Improve error handling.
out << "Failed to decode locals\n";
return;
}
for (uint32_t i = static_cast<uint32_t>(sig_->parameter_count());
i < num_locals_; i++) {
out << indentation << "(local ";
names_->PrintLocalName(out, func_index_, i);
out << " ";
names_->PrintValueType(out, local_type(i));
out << ")";
out.NextLine(pc_offset());
}
consume_bytes(locals_length);
out.set_current_line_bytecode_offset(pc_offset());
if (first_instruction_offset) *first_instruction_offset = pc_offset();
// Main loop.
while (pc_ < end_) {
WasmOpcode opcode = GetOpcode();
current_opcode_ = opcode; // Some immediates need to know this.
// Deal with indentation.
if (opcode == kExprEnd || opcode == kExprElse || opcode == kExprCatch ||
opcode == kExprCatchAll || opcode == kExprDelegate) {
if (indentation.current() >= base_indentation) {
indentation.decrease();
}
}
out << indentation;
if (opcode == kExprElse || opcode == kExprCatch ||
opcode == kExprCatchAll || opcode == kExprBlock || opcode == kExprIf ||
opcode == kExprLoop || opcode == kExprTry) {
indentation.increase();
}
// Print the opcode and its immediates.
if (opcode == kExprEnd) {
if (indentation.current() < base_indentation) {
out << ";; Unexpected end byte";
} else if (indentation.current() == base_indentation) {
out << ")"; // End of the function.
} else {
out << "end";
const LabelInfo& label = label_stack_.back();
if (label.start != nullptr) {
out << " ";
out.write(label.start, label.length);
}
label_stack_.pop_back();
}
} else {
out << WasmOpcodes::OpcodeName(opcode);
}
if (opcode == kExprBlock || opcode == kExprIf || opcode == kExprLoop ||
opcode == kExprTry) {
label_stack_.emplace_back(out.line_number(), out.length(),
label_occurrence_index_++);
}
uint32_t length = PrintImmediatesAndGetLength(out);
pc_ += length;
out.NextLine(pc_offset());
}
if (pc_ != end_) {
// TODO(jkummerow): Improve error handling.
out << "Beyond end of code";
}
}
void FunctionBodyDisassembler::DecodeGlobalInitializer(StringBuilder& out) {
while (pc_ < end_) {
WasmOpcode opcode = GetOpcode();
current_opcode_ = opcode; // Some immediates need to know this.
// Don't print the final "end".
if (opcode == kExprEnd && pc_ + 1 == end_) break;
uint32_t length;
out << " (" << WasmOpcodes::OpcodeName(opcode);
length = PrintImmediatesAndGetLength(out);
out << ")";
pc_ += length;
}
}
WasmOpcode FunctionBodyDisassembler::GetOpcode() {
WasmOpcode opcode = static_cast<WasmOpcode>(*pc_);
if (!WasmOpcodes::IsPrefixOpcode(opcode)) return opcode;
return read_prefixed_opcode<ValidationTag>(pc_).first;
}
void FunctionBodyDisassembler::PrintHexNumber(StringBuilder& out,
uint64_t number) {
constexpr size_t kBufferSize = sizeof(number) * 2 + 2; // +2 for "0x".
char buffer[kBufferSize];
char* end = buffer + kBufferSize;
char* ptr = end;
do {
*(--ptr) = kHexChars[number & 0xF];
number >>= 4;
} while (number > 0);
*(--ptr) = 'x';
*(--ptr) = '0';
size_t length = static_cast<size_t>(end - ptr);
char* output = out.allocate(length);
memcpy(output, ptr, length);
}
////////////////////////////////////////////////////////////////////////////////
// ImmediatesPrinter.
template <typename ValidationTag>
class ImmediatesPrinter {
public:
ImmediatesPrinter(StringBuilder& out, FunctionBodyDisassembler* owner)
: out_(out), owner_(owner) {}
void PrintDepthAsLabel(int imm_depth) {
out_ << " ";
size_t label_start_position = out_.length();
int depth = imm_depth;
if (owner_->current_opcode_ == kExprDelegate) depth++;
// Be robust: if the module is invalid, print what we got.
if (depth < 0 || depth >= static_cast<int>(owner_->label_stack_.size())) {
out_ << imm_depth;
return;
}
// If the label's name has already been determined and backpatched, just
// copy it here.
LabelInfo& label_info = owner_->label_info(depth);
if (label_info.start != nullptr) {
out_.write(label_info.start, label_info.length);
return;
}
// Determine the label's name and backpatch the line that opened the block.
names()->PrintLabelName(out_, owner_->func_index_,
label_info.name_section_index,
owner_->label_generation_index_++);
label_info.length = out_.length() - label_start_position;
owner_->out_->PatchLabel(label_info, out_.start() + label_start_position);
}
void PrintSignature(uint32_t sig_index) {
if (owner_->module_->has_signature(sig_index)) {
const FunctionSig* sig = owner_->module_->signature(sig_index);
PrintSignatureOneLine(out_, sig, 0 /* ignored */, names(), false);
} else {
out_ << " (signature: " << sig_index << " INVALID)";
}
}
void BlockType(BlockTypeImmediate& imm) {
if (imm.sig.all().begin() == nullptr) {
PrintSignature(imm.sig_index);
} else {
PrintSignatureOneLine(out_, &imm.sig, 0 /* ignored */, names(), false);
}
}
void HeapType(HeapTypeImmediate& imm) {
out_ << " ";
names()->PrintHeapType(out_, imm.type);
if (imm.type.is_index()) use_type(imm.type.ref_index());
}
void ValueType(HeapTypeImmediate& imm, bool is_nullable) {
out_ << " ";
names()->PrintValueType(
out_, ValueType::RefMaybeNull(imm.type,
is_nullable ? kNullable : kNonNullable));
if (imm.type.is_index()) use_type(imm.type.ref_index());
}
void BranchDepth(BranchDepthImmediate& imm) { PrintDepthAsLabel(imm.depth); }
void BranchTable(BranchTableImmediate& imm) {
const uint8_t* pc = imm.table;
for (uint32_t i = 0; i <= imm.table_count; i++) {
auto [target, length] = owner_->read_u32v<ValidationTag>(pc);
PrintDepthAsLabel(target);
pc += length;
}
}
void CallIndirect(CallIndirectImmediate& imm) {
PrintSignature(imm.sig_imm.index);
if (imm.table_imm.index != 0) TableIndex(imm.table_imm);
}
void SelectType(SelectTypeImmediate& imm) {
out_ << " ";
names()->PrintValueType(out_, imm.type);
}
void MemoryAccess(MemoryAccessImmediate& imm) {
if (imm.offset != 0) out_ << " offset=" << imm.offset;
if (imm.alignment != GetDefaultAlignment(owner_->current_opcode_)) {
out_ << " align=" << (1u << imm.alignment);
}
}
void SimdLane(SimdLaneImmediate& imm) { out_ << " " << uint32_t{imm.lane}; }
void Field(FieldImmediate& imm) {
TypeIndex(imm.struct_imm);
out_ << " ";
names()->PrintFieldName(out_, imm.struct_imm.index, imm.field_imm.index);
}
void Length(IndexImmediate& imm) {
out_ << " " << imm.index; // --
}
void TagIndex(TagIndexImmediate& imm) {
out_ << " ";
names()->PrintTagName(out_, imm.index);
}
void FunctionIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintFunctionName(out_, imm.index, NamesProvider::kDevTools);
}
void TypeIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintTypeName(out_, imm.index);
use_type(imm.index);
}
void LocalIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintLocalName(out_, func_index(), imm.index);
}
void GlobalIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintGlobalName(out_, imm.index);
}
void TableIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintTableName(out_, imm.index);
}
void MemoryIndex(MemoryIndexImmediate& imm) {
if (imm.index == 0) return;
out_ << " " << imm.index;
}
void DataSegmentIndex(IndexImmediate& imm) {
if (kSkipDataSegmentNames) {
out_ << " " << imm.index;
} else {
out_ << " ";
names()->PrintDataSegmentName(out_, imm.index);
}
}
void ElemSegmentIndex(IndexImmediate& imm) {
out_ << " ";
names()->PrintElementSegmentName(out_, imm.index);
}
void I32Const(ImmI32Immediate& imm) {
out_ << " " << imm.value; // --
}
void I64Const(ImmI64Immediate& imm) {
if (imm.value >= 0) {
out_ << " " << static_cast<uint64_t>(imm.value);
} else {
out_ << " -" << ((~static_cast<uint64_t>(imm.value)) + 1);
}
}
void F32Const(ImmF32Immediate& imm) {
float f = imm.value;
if (f == 0) {
out_ << (1 / f < 0 ? " -0.0" : " 0.0");
} else if (std::isinf(f)) {
out_ << (f > 0 ? " inf" : " -inf");
} else if (std::isnan(f)) {
uint32_t bits = base::bit_cast<uint32_t>(f);
uint32_t payload = bits & 0x7F'FFFFu;
uint32_t signbit = bits >> 31;
if (payload == 0x40'0000u) {
out_ << (signbit == 1 ? " -nan" : " nan");
} else {
out_ << (signbit == 1 ? " -nan:" : " +nan:");
owner_->PrintHexNumber(out_, payload);
}
} else {
std::ostringstream o;
// TODO(dlehmann): Change to `std::format` (C++20) or to `std::to_chars`
// (C++17) once available, so that `0.1` isn't printed as `0.100000001`
// any more.
o << std::setprecision(std::numeric_limits<float>::max_digits10) << f;
out_ << " " << o.str();
}
}
void F64Const(ImmF64Immediate& imm) {
double d = imm.value;
if (d == 0) {
out_ << (1 / d < 0 ? " -0.0" : " 0.0");
} else if (std::isinf(d)) {
out_ << (d > 0 ? " inf" : " -inf");
} else if (std::isnan(d)) {
uint64_t bits = base::bit_cast<uint64_t>(d);
uint64_t payload = bits & 0xF'FFFF'FFFF'FFFFull;
uint64_t signbit = bits >> 63;
if (payload == 0x8'0000'0000'0000ull) {
out_ << (signbit == 1 ? " -nan" : " nan");
} else {
out_ << (signbit == 1 ? " -nan:" : " +nan:");
owner_->PrintHexNumber(out_, payload);
}
} else {
char buffer[100];
const char* str = DoubleToCString(d, base::VectorOf(buffer, 100u));
out_ << " " << str;
}
}
void S128Const(Simd128Immediate& imm) {
if (owner_->current_opcode_ == kExprI8x16Shuffle) {
for (int i = 0; i < 16; i++) {
out_ << " " << uint32_t{imm.value[i]};
}
} else {
DCHECK_EQ(owner_->current_opcode_, kExprS128Const);
out_ << " i32x4";
for (int i = 0; i < 4; i++) {
out_ << " 0x";
for (int j = 3; j >= 0; j--) { // Little endian.
uint8_t b = imm.value[i * 4 + j];
out_ << kUpperHexChars[b >> 4];
out_ << kUpperHexChars[b & 0xF];
}
}
}
}
void StringConst(StringConstImmediate& imm) {
if (imm.index >= owner_->module_->stringref_literals.size()) {
out_ << " " << imm.index << " INVALID";
return;
}
out_ << " \"";
const WasmStringRefLiteral& lit =
owner_->module_->stringref_literals[imm.index];
const uint8_t* start = owner_->wire_bytes_.start() + lit.source.offset();
static constexpr uint32_t kMaxCharsPrinted = 40;
if (lit.source.length() <= kMaxCharsPrinted) {
const uint8_t* end =
owner_->wire_bytes_.start() + lit.source.end_offset();
PrintStringRaw(out_, start, end);
} else {
const uint8_t* end = start + kMaxCharsPrinted - 1;
PrintStringRaw(out_, start, end);
out_ << "…";
}
out_ << '"';
if (kIndicesAsComments) out_ << " (;" << imm.index << ";)";
}
void MemoryInit(MemoryInitImmediate& imm) {
DataSegmentIndex(imm.data_segment);
if (imm.memory.index != 0) out_ << " " << uint32_t{imm.memory.index};
}
void MemoryCopy(MemoryCopyImmediate& imm) {
if (imm.memory_dst.index == 0 && imm.memory_src.index == 0) return;
out_ << " " << uint32_t{imm.memory_dst.index};
out_ << " " << uint32_t{imm.memory_src.index};
}
void TableInit(TableInitImmediate& imm) {
if (imm.table.index != 0) TableIndex(imm.table);
ElemSegmentIndex(imm.element_segment);
}
void TableCopy(TableCopyImmediate& imm) {
if (imm.table_dst.index == 0 && imm.table_src.index == 0) return;
out_ << " ";
names()->PrintTableName(out_, imm.table_dst.index);
out_ << " ";
names()->PrintTableName(out_, imm.table_src.index);
}
void ArrayCopy(IndexImmediate& dst, IndexImmediate& src) {
out_ << " ";
names()->PrintTypeName(out_, dst.index);
out_ << " ";
names()->PrintTypeName(out_, src.index);
use_type(dst.index);
use_type(src.index);
}
private:
void use_type(uint32_t type_index) { owner_->used_types_.insert(type_index); }
NamesProvider* names() { return owner_->names_; }
uint32_t func_index() { return owner_->func_index_; }
StringBuilder& out_;
FunctionBodyDisassembler* owner_;
};
uint32_t FunctionBodyDisassembler::PrintImmediatesAndGetLength(
StringBuilder& out) {
using Printer = ImmediatesPrinter<ValidationTag>;
Printer imm_printer(out, this);
return WasmDecoder::OpcodeLength<Printer>(this, this->pc_, imm_printer);
}
////////////////////////////////////////////////////////////////////////////////
// OffsetsProvider.
class OffsetsProvider : public ITracer {
public:
OffsetsProvider() = default;
void CollectOffsets(const WasmModule* module,
base::Vector<const uint8_t> wire_bytes) {
num_imported_tables_ = module->num_imported_tables;
num_imported_globals_ = module->num_imported_globals;
num_imported_tags_ = module->num_imported_tags;
type_offsets_.reserve(module->types.size());
import_offsets_.reserve(module->import_table.size());
table_offsets_.reserve(module->tables.size() - num_imported_tables_);
tag_offsets_.reserve(module->tags.size() - num_imported_tags_);
global_offsets_.reserve(module->globals.size() - num_imported_globals_);
element_offsets_.reserve(module->elem_segments.size());
data_offsets_.reserve(module->data_segments.size());
ModuleDecoderImpl decoder{WasmFeatures::All(), wire_bytes, kWasmOrigin,
kDoNotPopulateExplicitRecGroups, this};
constexpr bool kNoVerifyFunctions = false;
decoder.DecodeModule(kNoVerifyFunctions);
enabled_ = true;
}
void TypeOffset(uint32_t offset) override { type_offsets_.push_back(offset); }
void ImportOffset(uint32_t offset) override {
import_offsets_.push_back(offset);
}
void TableOffset(uint32_t offset) override {
table_offsets_.push_back(offset);
}
void MemoryOffset(uint32_t offset) override { memory_offset_ = offset; }
void TagOffset(uint32_t offset) override { tag_offsets_.push_back(offset); }
void GlobalOffset(uint32_t offset) override {
global_offsets_.push_back(offset);
}
void StartOffset(uint32_t offset) override { start_offset_ = offset; }
void ElementOffset(uint32_t offset) override {
element_offsets_.push_back(offset);
}
void DataOffset(uint32_t offset) override { data_offsets_.push_back(offset); }
void StringOffset(uint32_t offset) override {
string_offsets_.push_back(offset);
}
// Unused by this tracer:
void ImportsDone() override {}
void Bytes(const uint8_t* start, uint32_t count) override {}
void Description(const char* desc) override {}
void Description(const char* desc, size_t length) override {}
void Description(uint32_t number) override {}
void Description(ValueType type) override {}
void Description(HeapType type) override {}
void Description(const FunctionSig* sig) override {}
void NextLine() override {}
void NextLineIfFull() override {}
void NextLineIfNonEmpty() override {}
void InitializerExpression(const uint8_t* start, const uint8_t* end,
ValueType expected_type) override {}
void FunctionBody(const WasmFunction* func, const uint8_t* start) override {}
void FunctionName(uint32_t func_index) override {}
void NameSection(const uint8_t* start, const uint8_t* end,
uint32_t offset) override {}
#define GETTER(name) \
uint32_t name##_offset(uint32_t index) { \
if (!enabled_) return 0; \
return name##_offsets_[index]; \
}
GETTER(type)
GETTER(import)
GETTER(element)
GETTER(data)
GETTER(string)
#undef GETTER
#define IMPORT_ADJUSTED_GETTER(name) \
uint32_t name##_offset(uint32_t index) { \
if (!enabled_) return 0; \
DCHECK(index >= num_imported_##name##s_ && \
index - num_imported_##name##s_ < name##_offsets_.size()); \
return name##_offsets_[index - num_imported_##name##s_]; \
}
IMPORT_ADJUSTED_GETTER(table)
IMPORT_ADJUSTED_GETTER(tag)
IMPORT_ADJUSTED_GETTER(global)
#undef IMPORT_ADJUSTED_GETTER
uint32_t memory_offset() { return memory_offset_; }
uint32_t start_offset() { return start_offset_; }
private:
bool enabled_{false};
uint32_t num_imported_tables_{0};
uint32_t num_imported_globals_{0};
uint32_t num_imported_tags_{0};
std::vector<uint32_t> type_offsets_;
std::vector<uint32_t> import_offsets_;
std::vector<uint32_t> table_offsets_;
std::vector<uint32_t> tag_offsets_;
std::vector<uint32_t> global_offsets_;
std::vector<uint32_t> element_offsets_;
std::vector<uint32_t> data_offsets_;
std::vector<uint32_t> string_offsets_;
uint32_t memory_offset_{0};
uint32_t start_offset_{0};
};
////////////////////////////////////////////////////////////////////////////////
// ModuleDisassembler.
ModuleDisassembler::ModuleDisassembler(
MultiLineStringBuilder& out, const WasmModule* module, NamesProvider* names,
const ModuleWireBytes wire_bytes, AccountingAllocator* allocator,
bool collect_offsets, std::vector<int>* function_body_offsets)
: out_(out),
module_(module),
names_(names),
wire_bytes_(wire_bytes),
start_(wire_bytes_.start()),
zone_(allocator, "disassembler zone"),
offsets_(new OffsetsProvider()),
function_body_offsets_(function_body_offsets) {
if (collect_offsets) {
offsets_->CollectOffsets(module, wire_bytes_.module_bytes());
}
}
ModuleDisassembler::~ModuleDisassembler() = default;
void ModuleDisassembler::PrintTypeDefinition(uint32_t type_index,
Indentation indentation,
IndexAsComment index_as_comment) {
uint32_t offset = offsets_->type_offset(type_index);
out_.NextLine(offset);
out_ << indentation << "(type ";
names_->PrintTypeName(out_, type_index, index_as_comment);
bool has_super = module_->has_supertype(type_index);
if (module_->has_array(type_index)) {
const ArrayType* type = module_->array_type(type_index);
// TODO(jkummerow): "_subtype" is the naming convention used for nominal
// types; update this for isorecursive hybrid types.
out_ << (has_super ? " (array_subtype (field " : " (array (field ");
PrintMutableType(type->mutability(), type->element_type());
out_ << ")"; // Closes `(field ...`
if (has_super) {
out_ << " ";
names_->PrintHeapType(out_, HeapType(module_->supertype(type_index)));
}
} else if (module_->has_struct(type_index)) {
const StructType* type = module_->struct_type(type_index);
out_ << (has_super ? " (struct_subtype" : " (struct");
bool break_lines = type->field_count() > 2;
for (uint32_t i = 0; i < type->field_count(); i++) {
LineBreakOrSpace(break_lines, indentation, offset);
out_ << "(field ";
names_->PrintFieldName(out_, type_index, i);
out_ << " ";
PrintMutableType(type->mutability(i), type->field(i));
out_ << ")";
}
if (has_super) {
LineBreakOrSpace(break_lines, indentation, offset);
names_->PrintHeapType(out_, HeapType(module_->supertype(type_index)));
}
} else if (module_->has_signature(type_index)) {
const FunctionSig* sig = module_->signature(type_index);
out_ << (has_super ? " (func_subtype" : " (func");
bool break_lines = sig->parameter_count() + sig->return_count() > 2;
for (uint32_t i = 0; i < sig->parameter_count(); i++) {
LineBreakOrSpace(break_lines, indentation, offset);
out_ << "(param ";
names_->PrintLocalName(out_, type_index, i);
out_ << " ";
names_->PrintValueType(out_, sig->GetParam(i));
out_ << ")";
}
for (uint32_t i = 0; i < sig->return_count(); i++) {
LineBreakOrSpace(break_lines, indentation, offset);
out_ << "(result ";
names_->PrintValueType(out_, sig->GetReturn(i));
out_ << ")";
}
if (has_super) {
LineBreakOrSpace(break_lines, indentation, offset);
names_->PrintHeapType(out_, HeapType(module_->supertype(type_index)));
}
}
out_ << "))"; // Closes "(type" and "(array" / "(struct" / "(func".
}
void ModuleDisassembler::PrintModule(Indentation indentation, size_t max_mb) {
// 0. General infrastructure.
// We don't store import/export information on {WasmTag} currently.
size_t num_tags = module_->tags.size();
std::vector<bool> exported_tags(num_tags, false);
for (const WasmExport& ex : module_->export_table) {
if (ex.kind == kExternalTag) exported_tags[ex.index] = true;
}
// I. Module name.
out_ << indentation << "(module";
if (module_->name.is_set()) {
out_ << " $";
const uint8_t* name_start = start_ + module_->name.offset();
out_.write(name_start, module_->name.length());
}
indentation.increase();
// II. Types
// TODO(jkummerow): If we want to support binary -> WAT -> binary round
// trips, then we need to print rec groups. The difficulty is that we
// don't store that information, so we'd either have to make {WasmModule}
// bigger, or re-decode the type section here.
for (uint32_t i = 0; i < module_->types.size(); i++) {
if (kSkipFunctionTypesInTypeSection && module_->has_signature(i)) {
continue;
}
PrintTypeDefinition(i, indentation, kIndicesAsComments);
}
// III. Imports
for (uint32_t i = 0; i < module_->import_table.size(); i++) {
const WasmImport& import = module_->import_table[i];
out_.NextLine(offsets_->import_offset(i));
out_ << indentation;
switch (import.kind) {
case kExternalTable: {
out_ << "(table ";
names_->PrintTableName(out_, import.index, kIndicesAsComments);
const WasmTable& table = module_->tables[import.index];
if (table.exported) PrintExportName(kExternalTable, import.index);
PrintImportName(import);
PrintTable(table);
break;
}
case kExternalFunction: {
out_ << "(func ";
names_->PrintFunctionName(out_, import.index, NamesProvider::kDevTools,
kIndicesAsComments);
const WasmFunction& func = module_->functions[import.index];
if (func.exported) PrintExportName(kExternalFunction, import.index);
PrintImportName(import);
PrintSignatureOneLine(out_, func.sig, import.index, names_, false);
break;
}
case kExternalGlobal: {
out_ << "(global ";
names_->PrintGlobalName(out_, import.index, kIndicesAsComments);
const WasmGlobal& global = module_->globals[import.index];
if (global.exported) PrintExportName(kExternalGlobal, import.index);
PrintImportName(import);
PrintGlobal(global);
break;
}
case kExternalMemory:
out_ << "(memory ";
names_->PrintMemoryName(out_, import.index, kIndicesAsComments);
if (module_->memories[import.index].exported) {
PrintExportName(kExternalMemory, 0);
}
PrintImportName(import);
PrintMemory(module_->memories[import.index]);
break;
case kExternalTag:
out_ << "(tag ";
names_->PrintTagName(out_, import.index, kIndicesAsComments);
PrintImportName(import);
if (exported_tags[import.index]) {
PrintExportName(kExternalTag, import.index);
}
PrintTagSignature(module_->tags[import.index].sig);
break;
}
out_ << ")";
}
// IV. Tables
for (uint32_t i = module_->num_imported_tables; i < module_->tables.size();
i++) {
const WasmTable& table = module_->tables[i];
DCHECK(!table.imported);
out_.NextLine(offsets_->table_offset(i));
out_ << indentation << "(table ";
names_->PrintTableName(out_, i, kIndicesAsComments);
if (table.exported) PrintExportName(kExternalTable, i);
PrintTable(table);
out_ << ")";
}
// V. Memories
uint32_t num_memories = static_cast<uint32_t>(module_->memories.size());
for (uint32_t memory_index = 0; memory_index < num_memories; ++memory_index) {
const WasmMemory& memory = module_->memories[memory_index];
if (memory.imported) continue;
out_.NextLine(offsets_->memory_offset());
out_ << indentation << "(memory ";
names_->PrintMemoryName(out_, memory_index, kIndicesAsComments);
if (memory.exported) PrintExportName(kExternalMemory, memory_index);
PrintMemory(memory);
out_ << ")";
}
// VI.Tags
for (uint32_t i = module_->num_imported_tags; i < module_->tags.size(); i++) {
const WasmTag& tag = module_->tags[i];
out_.NextLine(offsets_->tag_offset(i));
out_ << indentation << "(tag ";
names_->PrintTagName(out_, i, kIndicesAsComments);
if (exported_tags[i]) PrintExportName(kExternalTag, i);
PrintTagSignature(tag.sig);
out_ << ")";
}
// VII. String literals
size_t num_strings = module_->stringref_literals.size();
for (uint32_t i = 0; i < num_strings; i++) {
const WasmStringRefLiteral lit = module_->stringref_literals[i];
out_.NextLine(offsets_->string_offset(i));
out_ << indentation << "(string \"";
PrintString(lit.source);
out_ << '"';
if (kIndicesAsComments) out_ << " (;" << i << ";)";
out_ << ")";
}
// VIII. Globals
for (uint32_t i = module_->num_imported_globals; i < module_->globals.size();
i++) {
const WasmGlobal& global = module_->globals[i];
DCHECK(!global.imported);
out_.NextLine(offsets_->global_offset(i));
out_ << indentation << "(global ";
names_->PrintGlobalName(out_, i, kIndicesAsComments);
if (global.exported) PrintExportName(kExternalGlobal, i);
PrintGlobal(global);
PrintInitExpression(global.init, global.type);
out_ << ")";
}
// IX. Start
if (module_->start_function_index >= 0) {
out_.NextLine(offsets_->start_offset());
out_ << indentation << "(start ";
names_->PrintFunctionName(out_, module_->start_function_index,
NamesProvider::kDevTools);
out_ << ")";
}
// X. Elements
for (uint32_t i = 0; i < module_->elem_segments.size(); i++) {
const WasmElemSegment& elem = module_->elem_segments[i];
out_.NextLine(offsets_->element_offset(i));
out_ << indentation << "(elem ";
names_->PrintElementSegmentName(out_, i, kIndicesAsComments);
if (elem.status == WasmElemSegment::kStatusDeclarative) {
out_ << " declare";
} else if (elem.status == WasmElemSegment::kStatusActive) {
if (elem.table_index != 0) {
out_ << " (table ";
names_->PrintTableName(out_, elem.table_index);
out_ << ")";
}
PrintInitExpression(elem.offset, kWasmI32);
}
out_ << " ";
names_->PrintValueType(out_, elem.type);
ModuleDecoderImpl decoder(WasmFeatures::All(), wire_bytes_.module_bytes(),
ModuleOrigin::kWasmOrigin);
decoder.consume_bytes(elem.elements_wire_bytes_offset);
for (size_t i = 0; i < elem.element_count; i++) {
ConstantExpression entry = decoder.consume_element_segment_entry(
const_cast<WasmModule*>(module_), elem);
PrintInitExpression(entry, elem.type);
}
out_ << ")";
}
// For the FunctionBodyDisassembler, we flip the convention: {NextLine} is
// now called *after* printing something, instead of before.
if (out_.length() != 0) out_.NextLine(0);
// XI. Code / function bodies.
if (function_body_offsets_ != nullptr) {
size_t num_defined_functions =
module_->functions.size() - module_->num_imported_functions;
function_body_offsets_->reserve(num_defined_functions * 2);
}
for (uint32_t i = module_->num_imported_functions;
i < module_->functions.size(); i++) {
const WasmFunction* func = &module_->functions[i];
out_.set_current_line_bytecode_offset(func->code.offset());
out_ << indentation << "(func ";
names_->PrintFunctionName(out_, i, NamesProvider::kDevTools,
kIndicesAsComments);
if (func->exported) PrintExportName(kExternalFunction, i);
PrintSignatureOneLine(out_, func->sig, i, names_, true, kIndicesAsComments);
out_.NextLine(func->code.offset());
WasmFeatures detected;
base::Vector<const uint8_t> code = wire_bytes_.GetFunctionBytes(func);
FunctionBodyDisassembler d(&zone_, module_, i, &detected, func->sig,
code.begin(), code.end(), func->code.offset(),
wire_bytes_, names_);
uint32_t first_instruction_offset;
d.DecodeAsWat(out_, indentation, FunctionBodyDisassembler::kSkipHeader,
&first_instruction_offset);
if (function_body_offsets_ != nullptr) {
function_body_offsets_->push_back(first_instruction_offset);
function_body_offsets_->push_back(d.pc_offset());
}
if (out_.ApproximateSizeMB() > max_mb) {
out_ << "<truncated...>";
return;
}
}
// XII. Data
for (uint32_t i = 0; i < module_->data_segments.size(); i++) {
const WasmDataSegment& data = module_->data_segments[i];
out_.set_current_line_bytecode_offset(offsets_->data_offset(i));
out_ << indentation << "(data";
if (!kSkipDataSegmentNames) {
out_ << " ";
names_->PrintDataSegmentName(out_, i, kIndicesAsComments);
}
if (data.active) {
ValueType type = module_->memories[data.memory_index].is_memory64
? kWasmI64
: kWasmI32;
PrintInitExpression(data.dest_addr, type);
}
out_ << " \"";
PrintString(data.source);
out_ << "\")";
out_.NextLine(0);
if (out_.ApproximateSizeMB() > max_mb) {
out_ << "<truncated...>";
return;
}
}
indentation.decrease();
out_.set_current_line_bytecode_offset(
static_cast<uint32_t>(wire_bytes_.length()));
out_ << indentation << ")"; // End of the module.
out_.NextLine(0);
}
void ModuleDisassembler::PrintImportName(const WasmImport& import) {
out_ << " (import \"";
PrintString(import.module_name);
out_ << "\" \"";
PrintString(import.field_name);
out_ << "\")";
}
void ModuleDisassembler::PrintExportName(ImportExportKindCode kind,
uint32_t index) {
for (const WasmExport& ex : module_->export_table) {
if (ex.kind != kind || ex.index != index) continue;
out_ << " (export \"";
PrintStringAsJSON(ex.name);
out_ << "\")";
}
}
void ModuleDisassembler::PrintMutableType(bool mutability, ValueType type) {
if (mutability) out_ << "(mut ";
names_->PrintValueType(out_, type);
if (mutability) out_ << ")";
}
void ModuleDisassembler::PrintTable(const WasmTable& table) {
out_ << " " << table.initial_size << " ";
if (table.has_maximum_size) out_ << table.maximum_size << " ";
names_->PrintValueType(out_, table.type);
}
void ModuleDisassembler::PrintMemory(const WasmMemory& memory) {
out_ << " " << memory.initial_pages;
if (memory.has_maximum_pages) out_ << " " << memory.maximum_pages;
if (memory.is_shared) out_ << " shared";
}
void ModuleDisassembler::PrintGlobal(const WasmGlobal& global) {
out_ << " ";
PrintMutableType(global.mutability, global.type);
}
void ModuleDisassembler::PrintInitExpression(const ConstantExpression& init,
ValueType expected_type) {
switch (init.kind()) {
case ConstantExpression::kEmpty:
break;
case ConstantExpression::kI32Const:
out_ << " (i32.const " << init.i32_value() << ")";
break;
case ConstantExpression::kRefNull:
out_ << " (ref.null ";
names_->PrintHeapType(out_, HeapType(init.repr()));
out_ << ")";
break;
case ConstantExpression::kRefFunc:
out_ << " (ref.func ";
names_->PrintFunctionName(out_, init.index(), NamesProvider::kDevTools);
out_ << ")";
break;
case ConstantExpression::kWireBytesRef:
WireBytesRef ref = init.wire_bytes_ref();
const uint8_t* start = start_ + ref.offset();
const uint8_t* end = start_ + ref.end_offset();
auto sig = FixedSizeSignature<ValueType>::Returns(expected_type);
WasmFeatures detected;
FunctionBodyDisassembler d(&zone_, module_, 0, &detected, &sig, start,
end, ref.offset(), wire_bytes_, names_);
d.DecodeGlobalInitializer(out_);
break;
}
}
void ModuleDisassembler::PrintTagSignature(const FunctionSig* sig) {
for (uint32_t i = 0; i < sig->parameter_count(); i++) {
out_ << " (param ";
names_->PrintValueType(out_, sig->GetParam(i));
out_ << ")";
}
}
void ModuleDisassembler::PrintString(WireBytesRef ref) {
PrintStringRaw(out_, start_ + ref.offset(), start_ + ref.end_offset());
}
// This mimics legacy wasmparser behavior. It might be a questionable choice,
// but we'll follow suit for now.
void ModuleDisassembler::PrintStringAsJSON(WireBytesRef ref) {
for (const uint8_t* ptr = start_ + ref.offset();
ptr < start_ + ref.end_offset(); ptr++) {
uint8_t b = *ptr;
if (b <= 34) {
switch (b) {
// clang-format off
case '\b': out_ << "\\b"; break;
case '\t': out_ << "\\t"; break;
case '\n': out_ << "\\n"; break;
case '\f': out_ << "\\f"; break;
case '\r': out_ << "\\r"; break;
case ' ': out_ << ' '; break;
case '!': out_ << '!'; break;
case '"': out_ << "\\\""; break;
// clang-format on
default:
out_ << "\\u00" << kHexChars[b >> 4] << kHexChars[b & 0xF];
break;
}
} else if (b != 127 && b != '\\') {
out_ << static_cast<char>(b);
} else if (b == '\\') {
out_ << "\\\\";
} else {
out_ << "\\x7F";
}
}
}
void ModuleDisassembler::LineBreakOrSpace(bool break_lines,
Indentation indentation,
uint32_t byte_offset) {
if (break_lines) {
out_.NextLine(byte_offset);
out_ << indentation.Extra(2);
} else {
out_ << " ";
}
}
} // namespace wasm
} // namespace internal
} // namespace v8