mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 08:02:06 +00:00

PR-URL: https://github.com/nodejs/node/pull/54077 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
1734 lines
57 KiB
C++
1734 lines
57 KiB
C++
// Copyright 2024 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.
|
|
|
|
#ifndef V8_TOOLS_WASM_MJSUNIT_MODULE_DISASSEMBLER_IMPL_H_
|
|
#define V8_TOOLS_WASM_MJSUNIT_MODULE_DISASSEMBLER_IMPL_H_
|
|
|
|
#include <ctime>
|
|
|
|
#include "src/numbers/conversions.h"
|
|
#include "src/wasm/function-body-decoder-impl.h"
|
|
#include "src/wasm/module-decoder-impl.h"
|
|
#include "src/wasm/string-builder-multiline.h"
|
|
#include "src/wasm/wasm-disassembler-impl.h"
|
|
#include "src/wasm/wasm-module.h"
|
|
#include "src/wasm/wasm-opcodes-inl.h"
|
|
|
|
namespace v8::internal::wasm {
|
|
|
|
// Provides an implementation of a module disassembler that can produce
|
|
// test cases in "mjsunit format", i.e. using the WasmModuleBuilder from
|
|
// test/mjsunit/wasm/wasm-module-builder.js to define the module.
|
|
//
|
|
// The one relevant public method is MjsunitModuleDis::PrintModule().
|
|
|
|
static constexpr char kHexChars[] = "0123456789abcdef";
|
|
|
|
StringBuilder& operator<<(StringBuilder& sb, base::Vector<const char> chars) {
|
|
sb.write(chars.cbegin(), chars.size());
|
|
return sb;
|
|
}
|
|
|
|
enum OutputContext : bool {
|
|
// Print "kAnyRefCode" and "kWasmRef, 1," etc (inside function bodies).
|
|
kEmitWireBytes = true,
|
|
// Print "kWasmAnyRef" and "wasmRefType(1)" etc (in module builder functions).
|
|
kEmitObjects = false,
|
|
};
|
|
|
|
// Helper to surround a value by an optional ...wasmUnsignedLeb() call.
|
|
class MaybeLebScope {
|
|
public:
|
|
MaybeLebScope(StringBuilder& out, uint32_t index) : out(out), index(index) {
|
|
if (index > 0x7F) {
|
|
out << "...wasmUnsignedLeb(";
|
|
}
|
|
}
|
|
~MaybeLebScope() {
|
|
if (index > 0x7F) {
|
|
out << ')';
|
|
}
|
|
}
|
|
|
|
private:
|
|
StringBuilder& out;
|
|
uint32_t index;
|
|
};
|
|
|
|
class MjsunitNamesProvider {
|
|
public:
|
|
static constexpr const char* kLocalPrefix = "$var";
|
|
|
|
MjsunitNamesProvider(const WasmModule* module, ModuleWireBytes wire_bytes)
|
|
: module_(module), wire_bytes_(wire_bytes) {
|
|
function_variable_names_.resize(module->functions.size());
|
|
// Algorithm for selecting function names:
|
|
// 1. If the name section defines a suitable name, use that.
|
|
// 2. Else, if the function is imported and the import "field name" is
|
|
// a suitable name, use that.
|
|
// 3. Else, if the function is exported and its export name is a
|
|
// suitable name, use that.
|
|
// 4. Else, generate a name like "$func123".
|
|
// The definition of "suitable" is:
|
|
// - the name has at least one character
|
|
// - and all characters are in the set [a-zA-Z0-9$_]
|
|
// - and the name doesn't clash with common auto-generated names.
|
|
for (uint32_t i = 0; i < module->functions.size(); i++) {
|
|
WireBytesRef name =
|
|
module_->lazily_generated_names.LookupFunctionName(wire_bytes, i);
|
|
if (IsSuitableFunctionVariableName(name)) {
|
|
function_variable_names_[i] = name;
|
|
}
|
|
}
|
|
for (const WasmImport& imp : module_->import_table) {
|
|
if (imp.kind != kExternalFunction) continue;
|
|
if (function_variable_names_[imp.index].is_set()) continue;
|
|
if (IsSuitableFunctionVariableName(imp.field_name)) {
|
|
function_variable_names_[imp.index] = imp.field_name;
|
|
}
|
|
}
|
|
for (const WasmExport& ex : module_->export_table) {
|
|
if (ex.kind != kExternalFunction) continue;
|
|
if (function_variable_names_[ex.index].is_set()) continue;
|
|
if (IsSuitableFunctionVariableName(ex.name)) {
|
|
function_variable_names_[ex.index] = ex.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HasFunctionName(uint32_t function_index) {
|
|
WireBytesRef ref = module_->lazily_generated_names.LookupFunctionName(
|
|
wire_bytes_, function_index);
|
|
return ref.is_set();
|
|
}
|
|
|
|
bool FunctionNameEquals(uint32_t function_index, WireBytesRef ref) {
|
|
WireBytesRef name_ref = module_->lazily_generated_names.LookupFunctionName(
|
|
wire_bytes_, function_index);
|
|
if (name_ref.length() != ref.length()) return false;
|
|
if (name_ref.offset() == ref.offset()) return true;
|
|
WasmName name = wire_bytes_.GetNameOrNull(name_ref);
|
|
WasmName question = wire_bytes_.GetNameOrNull(ref);
|
|
return memcmp(name.begin(), question.begin(), name.length()) == 0;
|
|
}
|
|
|
|
void PrintTypeVariableName(StringBuilder& out, uint32_t index) {
|
|
// The name creation scheme must be in sync with {PrintStructType} etc.
|
|
// below!
|
|
if (module_->has_struct(index)) {
|
|
out << "$struct" << index;
|
|
} else if (module_->has_array(index)) {
|
|
out << "$array" << index;
|
|
} else {
|
|
// This function is meant for dumping the type section, so we can assume
|
|
// validity.
|
|
DCHECK(module_->has_signature(index));
|
|
out << "$sig" << index;
|
|
}
|
|
}
|
|
|
|
void PrintStructType(StringBuilder& out, uint32_t index, OutputContext mode) {
|
|
DCHECK(module_->has_struct(index));
|
|
PrintMaybeLEB(out, "$struct", index, mode);
|
|
}
|
|
|
|
void PrintArrayType(StringBuilder& out, uint32_t index, OutputContext mode) {
|
|
DCHECK(module_->has_array(index));
|
|
PrintMaybeLEB(out, "$array", index, mode);
|
|
}
|
|
|
|
void PrintSigType(StringBuilder& out, uint32_t index, OutputContext mode) {
|
|
DCHECK(module_->has_signature(index));
|
|
PrintMaybeLEB(out, "$sig", index, mode);
|
|
}
|
|
|
|
void PrintTypeIndex(StringBuilder& out, uint32_t index, OutputContext mode) {
|
|
if (module_->has_struct(index)) {
|
|
PrintStructType(out, index, mode);
|
|
} else if (module_->has_array(index)) {
|
|
PrintArrayType(out, index, mode);
|
|
} else if (module_->has_signature(index)) {
|
|
PrintSigType(out, index, mode);
|
|
} else {
|
|
// Support building invalid modules for testing.
|
|
PrintMaybeLEB(out, "/* invalid type */ ", index, mode);
|
|
}
|
|
}
|
|
|
|
// For the name section.
|
|
void PrintFunctionName(StringBuilder& out, uint32_t index) {
|
|
WireBytesRef ref =
|
|
module_->lazily_generated_names.LookupFunctionName(wire_bytes_, index);
|
|
DCHECK(ref.is_set()); // Callers should use `HasFunctionName` to check.
|
|
out.write(wire_bytes_.start() + ref.offset(), ref.length());
|
|
}
|
|
|
|
// For the JS variable referring to the function.
|
|
void PrintFunctionVariableName(StringBuilder& out, uint32_t index) {
|
|
if (index >= function_variable_names_.size()) {
|
|
// Invalid module.
|
|
out << "$invalid" << index;
|
|
return;
|
|
}
|
|
WasmName name = wire_bytes_.GetNameOrNull(function_variable_names_[index]);
|
|
if (name.size() > 0) {
|
|
out << name << index;
|
|
} else {
|
|
out << "$func" << index;
|
|
}
|
|
}
|
|
|
|
// Prints "$func" or "$func.index" depending on whether $func is imported
|
|
// or defined by `builder.addFunction`.
|
|
void PrintFunctionReference(StringBuilder& out, uint32_t index) {
|
|
PrintFunctionVariableName(out, index);
|
|
if (index < module_->functions.size() &&
|
|
!module_->functions[index].imported) {
|
|
out << ".index";
|
|
}
|
|
}
|
|
void PrintFunctionReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintFunctionReference(out, index);
|
|
}
|
|
|
|
// We only use this for comments, so it doesn't need to bother with LEBs.
|
|
void PrintLocalName(StringBuilder& out, uint32_t index) {
|
|
out << kLocalPrefix << index;
|
|
}
|
|
|
|
void PrintGlobalName(StringBuilder& out, uint32_t index) {
|
|
out << "$global" << index;
|
|
}
|
|
void PrintGlobalReference(StringBuilder& out, uint32_t index) {
|
|
PrintGlobalName(out, index);
|
|
if (index < module_->globals.size() && !module_->globals[index].imported) {
|
|
out << ".index";
|
|
}
|
|
}
|
|
void PrintGlobalReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintGlobalReference(out, index);
|
|
}
|
|
|
|
void PrintTableName(StringBuilder& out, uint32_t index) {
|
|
out << "$table" << index;
|
|
}
|
|
void PrintTableReference(StringBuilder& out, uint32_t index) {
|
|
PrintTableName(out, index);
|
|
if (index < module_->tables.size() && !module_->tables[index].imported) {
|
|
out << ".index";
|
|
}
|
|
}
|
|
|
|
void PrintTableReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintTableReference(out, index);
|
|
}
|
|
|
|
void PrintMemoryName(StringBuilder& out, uint32_t index) {
|
|
out << "$mem" << index;
|
|
}
|
|
void PrintMemoryReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintMemoryName(out, index);
|
|
}
|
|
|
|
void PrintTagName(StringBuilder& out, uint32_t index) {
|
|
out << "$tag" << index;
|
|
}
|
|
void PrintTagReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintTagName(out, index);
|
|
}
|
|
|
|
void PrintDataSegmentName(StringBuilder& out, uint32_t index) {
|
|
out << "$data" << index;
|
|
}
|
|
void PrintDataSegmentReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintDataSegmentName(out, index);
|
|
}
|
|
|
|
void PrintElementSegmentName(StringBuilder& out, uint32_t index) {
|
|
out << "$segment" << index;
|
|
}
|
|
void PrintElementSegmentReferenceLeb(StringBuilder& out, uint32_t index) {
|
|
MaybeLebScope leb_scope(out, index);
|
|
PrintElementSegmentName(out, index);
|
|
}
|
|
|
|
// Format: HeapType::* enum value, JS global constant.
|
|
#define ABSTRACT_TYPE_LIST(V) \
|
|
V(kAny, kWasmAnyRef, kAnyRefCode) \
|
|
V(kArray, kWasmArrayRef, kArrayRefCode) \
|
|
V(kEq, kWasmEqRef, kEqRefCode) \
|
|
V(kExn, kWasmExnRef, kExnRefCode) \
|
|
V(kExtern, kWasmExternRef, kExternRefCode) \
|
|
V(kFunc, kWasmFuncRef, kFuncRefCode) \
|
|
V(kI31, kWasmI31Ref, kI31RefCode) \
|
|
V(kNone, kWasmNullRef, kNullRefCode) \
|
|
V(kNoExn, kWasmNullExnRef, kNullExnRefCode) \
|
|
V(kNoExtern, kWasmNullExternRef, kNullExternRefCode) \
|
|
V(kNoFunc, kWasmNullFuncRef, kNullFuncRefCode) \
|
|
V(kString, kWasmStringRef, kStringRefCode) \
|
|
V(kStruct, kWasmStructRef, kStructRefCode)
|
|
|
|
// Same, but for types where the shorthand is non-nullable.
|
|
#define ABSTRACT_NN_TYPE_LIST(V) \
|
|
V(kStringViewWtf16, kWasmStringViewWtf16, kStringViewWtf16Code) \
|
|
V(kStringViewWtf8, kWasmStringViewWtf8, kStringViewWtf8Code) \
|
|
V(kStringViewIter, kWasmStringViewIter, kStringViewIterCode)
|
|
|
|
void PrintHeapType(StringBuilder& out, HeapType type, OutputContext mode) {
|
|
switch (type.representation()) {
|
|
#define CASE(kCpp, JS, JSCode) \
|
|
case HeapType::kCpp: \
|
|
out << (mode == kEmitWireBytes ? #JSCode : #JS); \
|
|
return;
|
|
ABSTRACT_TYPE_LIST(CASE)
|
|
ABSTRACT_NN_TYPE_LIST(CASE)
|
|
#undef CASE
|
|
case HeapType::kBottom:
|
|
UNREACHABLE();
|
|
default:
|
|
PrintTypeIndex(out, type.ref_index(), mode);
|
|
}
|
|
}
|
|
|
|
void PrintValueType(StringBuilder& out, ValueType type, OutputContext mode) {
|
|
switch (type.kind()) {
|
|
// clang-format off
|
|
case kI8: out << "kWasmI8"; return;
|
|
case kI16: out << "kWasmI16"; return;
|
|
case kI32: out << "kWasmI32"; return;
|
|
case kI64: out << "kWasmI64"; return;
|
|
case kF16: out << "kWasmF16"; return;
|
|
case kF32: out << "kWasmF32"; return;
|
|
case kF64: out << "kWasmF64"; return;
|
|
case kS128: out << "kWasmS128"; return;
|
|
// clang-format on
|
|
case kRefNull:
|
|
switch (type.heap_representation()) {
|
|
#define CASE(kCpp, _, _2) case HeapType::kCpp:
|
|
ABSTRACT_TYPE_LIST(CASE)
|
|
#undef CASE
|
|
return PrintHeapType(out, type.heap_type(), mode);
|
|
case HeapType::kBottom:
|
|
UNREACHABLE();
|
|
default:
|
|
out << (mode == kEmitObjects ? "wasmRefNullType("
|
|
: "kWasmRefNull, ");
|
|
break;
|
|
}
|
|
break;
|
|
case kRef:
|
|
switch (type.heap_representation()) {
|
|
#define CASE(kCpp, _, _2) case HeapType::kCpp:
|
|
ABSTRACT_NN_TYPE_LIST(CASE)
|
|
#undef CASE
|
|
return PrintHeapType(out, type.heap_type(), mode);
|
|
case HeapType::kBottom:
|
|
UNREACHABLE();
|
|
default:
|
|
out << (mode == kEmitObjects ? "wasmRefType(" : "kWasmRef, ");
|
|
break;
|
|
}
|
|
break;
|
|
case kBottom:
|
|
out << "/*<bot>*/";
|
|
return;
|
|
case kRtt:
|
|
case kVoid:
|
|
UNREACHABLE();
|
|
}
|
|
PrintHeapType(out, type.heap_type(), mode);
|
|
if (mode == kEmitObjects) out << ")";
|
|
}
|
|
|
|
void PrintMakeSignature(StringBuilder& out, const FunctionSig* sig) {
|
|
// Check if we can use an existing definition (for a couple of
|
|
// common cases).
|
|
// TODO(jkummerow): Is more complete coverage here worth it?
|
|
#define PREDEFINED(name) \
|
|
if (*sig == impl::kSig_##name) { \
|
|
out << "kSig_" #name; \
|
|
return; \
|
|
}
|
|
PREDEFINED(d_d)
|
|
PREDEFINED(d_dd)
|
|
PREDEFINED(i_i)
|
|
PREDEFINED(i_ii)
|
|
PREDEFINED(i_iii)
|
|
PREDEFINED(i_v)
|
|
PREDEFINED(l_l)
|
|
PREDEFINED(l_ll)
|
|
PREDEFINED(v_i)
|
|
PREDEFINED(v_ii)
|
|
PREDEFINED(v_v)
|
|
#undef PREDEFINED
|
|
|
|
// No hit among predefined signatures we checked for; define our own.
|
|
out << "makeSig([";
|
|
for (size_t i = 0; i < sig->parameter_count(); i++) {
|
|
if (i > 0) out << ", ";
|
|
PrintValueType(out, sig->GetParam(i), kEmitObjects);
|
|
}
|
|
out << "], [";
|
|
for (size_t i = 0; i < sig->return_count(); i++) {
|
|
if (i > 0) out << ", ";
|
|
PrintValueType(out, sig->GetReturn(i), kEmitObjects);
|
|
}
|
|
out << "])";
|
|
}
|
|
|
|
void PrintSignatureComment(StringBuilder& out, const FunctionSig* sig) {
|
|
out << ": [";
|
|
for (uint32_t i = 0; i < sig->parameter_count(); i++) {
|
|
if (i > 0) out << ", ";
|
|
if (sig->parameter_count() > 3) {
|
|
out << kLocalPrefix << i << ":";
|
|
}
|
|
PrintValueType(out, sig->GetParam(i), kEmitObjects);
|
|
}
|
|
out << "] -> [";
|
|
for (uint32_t i = 0; i < sig->return_count(); i++) {
|
|
if (i > 0) out << ", ";
|
|
PrintValueType(out, sig->GetReturn(i), kEmitObjects);
|
|
}
|
|
out << "]";
|
|
}
|
|
|
|
private:
|
|
bool IsSuitableFunctionVariableName(WireBytesRef ref) {
|
|
if (!ref.is_set()) return false;
|
|
if (ref.length() == 0) return false;
|
|
WasmName name = wire_bytes_.GetNameOrNull(ref);
|
|
// Check for invalid characters.
|
|
for (uint32_t i = 0; i < ref.length(); i++) {
|
|
char c = name[i];
|
|
char uc = c | 0x20;
|
|
if (uc >= 'a' && uc <= 'z') continue;
|
|
if (c == '$' || c == '_') continue;
|
|
if (c >= '0' && c <= '9') continue;
|
|
return false;
|
|
}
|
|
// Check for clashes with auto-generated names.
|
|
// This isn't perfect: any collision with a function (e.g. "makeSig")
|
|
// or constant (e.g. "kFooRefCode") would also break the generated test,
|
|
// but it doesn't seem feasible to accurately guard against all of those.
|
|
if (name.length() >= 8) {
|
|
if (memcmp(name.begin(), "$segment", 8) == 0) return false;
|
|
}
|
|
if (name.length() >= 7) {
|
|
if (memcmp(name.begin(), "$global", 7) == 0) return false;
|
|
if (memcmp(name.begin(), "$struct", 7) == 0) return false;
|
|
}
|
|
if (name.length() >= 6) {
|
|
if (memcmp(name.begin(), "$array", 6) == 0) return false;
|
|
if (memcmp(name.begin(), "$table", 6) == 0) return false;
|
|
}
|
|
if (name.length() >= 5) {
|
|
if (memcmp(name.begin(), "$data", 5) == 0) return false;
|
|
if (memcmp(name.begin(), "$func", 5) == 0) return false;
|
|
if (memcmp(name.begin(), "kExpr", 5) == 0) return false;
|
|
if (memcmp(name.begin(), "kSig_", 5) == 0) return false;
|
|
if (memcmp(name.begin(), "kWasm", 5) == 0) return false;
|
|
}
|
|
if (name.length() >= 4) {
|
|
if (memcmp(name.begin(), "$mem", 4) == 0) return false;
|
|
if (memcmp(name.begin(), "$sig", 4) == 0) return false;
|
|
if (memcmp(name.begin(), "$tag", 4) == 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PrintMaybeLEB(StringBuilder& out, const char* prefix, uint32_t index,
|
|
OutputContext mode) {
|
|
if (index <= 0x3F || mode == kEmitObjects) {
|
|
out << prefix << index;
|
|
} else {
|
|
out << "...wasmSignedLeb(" << prefix << index << ")";
|
|
}
|
|
}
|
|
|
|
const WasmModule* module_;
|
|
ModuleWireBytes wire_bytes_;
|
|
std::vector<WireBytesRef> function_variable_names_;
|
|
};
|
|
|
|
namespace {
|
|
const char* RawOpcodeName(WasmOpcode opcode) {
|
|
switch (opcode) {
|
|
#define DECLARE_NAME_CASE(name, ...) \
|
|
case kExpr##name: \
|
|
return "kExpr" #name;
|
|
FOREACH_OPCODE(DECLARE_NAME_CASE)
|
|
#undef DECLARE_NAME_CASE
|
|
default:
|
|
break;
|
|
}
|
|
return "Unknown";
|
|
}
|
|
const char* PrefixName(WasmOpcode prefix_opcode) {
|
|
switch (prefix_opcode) {
|
|
#define DECLARE_PREFIX_CASE(name, opcode) \
|
|
case k##name##Prefix: \
|
|
return "k" #name "Prefix";
|
|
FOREACH_PREFIX(DECLARE_PREFIX_CASE)
|
|
#undef DECLARE_PREFIX_CASE
|
|
default:
|
|
return "Unknown prefix";
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
template <typename ValidationTag>
|
|
class MjsunitImmediatesPrinter;
|
|
class MjsunitFunctionDis : public WasmDecoder<Decoder::FullValidationTag> {
|
|
public:
|
|
using ValidationTag = Decoder::FullValidationTag;
|
|
|
|
MjsunitFunctionDis(Zone* zone, const WasmModule* module, uint32_t func_index,
|
|
bool shared, WasmDetectedFeatures* detected,
|
|
const FunctionSig* sig, const uint8_t* start,
|
|
const uint8_t* end, uint32_t offset,
|
|
MjsunitNamesProvider* mjsunit_names,
|
|
Indentation indentation)
|
|
: WasmDecoder<ValidationTag>(zone, module, WasmEnabledFeatures::All(),
|
|
detected, sig, shared, start, end, offset),
|
|
names_(mjsunit_names),
|
|
indentation_(indentation) {}
|
|
|
|
void WriteMjsunit(MultiLineStringBuilder& out);
|
|
|
|
// TODO(jkummerow): Support for compilation hints is missing.
|
|
|
|
void DecodeGlobalInitializer(StringBuilder& out);
|
|
|
|
uint32_t PrintMjsunitImmediatesAndGetLength(StringBuilder& out);
|
|
|
|
private:
|
|
template <typename ValidationTag>
|
|
friend class MjsunitImmediatesPrinter;
|
|
|
|
WasmOpcode PrintPrefixedOpcode(StringBuilder& out, WasmOpcode prefix) {
|
|
auto [prefixed, prefixed_length] = read_u32v<ValidationTag>(pc_ + 1);
|
|
if (failed()) {
|
|
out << PrefixName(prefix) << ", ";
|
|
return prefix;
|
|
}
|
|
int shift = prefixed > 0xFF ? 12 : 8;
|
|
WasmOpcode opcode = static_cast<WasmOpcode>((prefix << shift) | prefixed);
|
|
if (prefixed <= 0x7F) {
|
|
if (opcode != kExprS128Const) {
|
|
out << PrefixName(prefix) << ", ";
|
|
out << RawOpcodeName(opcode) << ",";
|
|
}
|
|
if (opcode == kExprAtomicFence) {
|
|
// Unused zero-byte.
|
|
out << " 0,";
|
|
}
|
|
} else if (prefix == kSimdPrefix) {
|
|
if (prefixed > 0xFF) {
|
|
out << "kSimdPrefix, ..." << RawOpcodeName(opcode) << ",";
|
|
} else {
|
|
out << "...SimdInstr(" << RawOpcodeName(opcode) << "),";
|
|
}
|
|
} else if (prefix == kGCPrefix) {
|
|
out << "...GCInstr(" << RawOpcodeName(opcode) << "),";
|
|
} else {
|
|
// Invalid module.
|
|
out << "0x" << kHexChars[prefix >> 4] << kHexChars[prefix & 0xF] << ", ";
|
|
while (prefixed > 0) {
|
|
uint32_t chunk = prefixed & 0x7F;
|
|
prefixed >>= 7;
|
|
if (prefixed) chunk |= 0x80;
|
|
out << "0x" << kHexChars[chunk >> 4] << kHexChars[chunk & 0xF] << ", ";
|
|
}
|
|
}
|
|
return opcode;
|
|
}
|
|
|
|
MjsunitNamesProvider* names() { return names_; }
|
|
|
|
MjsunitNamesProvider* names_;
|
|
Indentation indentation_;
|
|
WasmOpcode current_opcode_;
|
|
};
|
|
|
|
void MjsunitFunctionDis::WriteMjsunit(MultiLineStringBuilder& out) {
|
|
if (!more()) {
|
|
out << ".addBodyWithEnd([]); // Invalid: missing kExprEnd.";
|
|
return;
|
|
}
|
|
if (end_ == pc_ + 1 && *pc_ == static_cast<uint8_t>(kExprEnd)) {
|
|
out << ".addBody([]);";
|
|
return;
|
|
}
|
|
|
|
// Emit the locals.
|
|
uint32_t locals_length = DecodeLocals(pc_);
|
|
if (failed()) {
|
|
out << "// Failed to decode locals\n";
|
|
return;
|
|
}
|
|
uint32_t num_params = static_cast<uint32_t>(sig_->parameter_count());
|
|
if (num_locals_ > num_params) {
|
|
for (uint32_t pos = num_params, count = 1; pos < num_locals_;
|
|
pos += count, count = 1) {
|
|
ValueType type = local_types_[pos];
|
|
while (pos + count < num_locals_ && local_types_[pos + count] == type) {
|
|
count++;
|
|
}
|
|
if (pos > num_params) out << indentation_;
|
|
out << ".addLocals(";
|
|
names()->PrintValueType(out, type, kEmitObjects);
|
|
out << ", " << count << ") // ";
|
|
names()->PrintLocalName(out, pos);
|
|
if (count > 1) {
|
|
out << " - ";
|
|
names()->PrintLocalName(out, pos + count - 1);
|
|
}
|
|
out.NextLine(0);
|
|
}
|
|
out << indentation_;
|
|
}
|
|
consume_bytes(locals_length);
|
|
|
|
// Emit the function body.
|
|
out << ".addBody([";
|
|
out.NextLine(0);
|
|
indentation_.increase();
|
|
int base_indentation = indentation_.current();
|
|
while (pc_ < end_ && ok()) {
|
|
WasmOpcode opcode = static_cast<WasmOpcode>(*pc_);
|
|
if (WasmOpcodes::IsPrefixOpcode(opcode)) {
|
|
out << indentation_;
|
|
opcode = PrintPrefixedOpcode(out, opcode);
|
|
} else {
|
|
bool decrease_indentation = false;
|
|
bool increase_indentation_after = false;
|
|
bool bailout = false;
|
|
bool print_instruction = true;
|
|
switch (opcode) {
|
|
case kExprEnd:
|
|
// Don't print the final "end", it's implicit in {addBody()}.
|
|
if (pc_ + 1 == end_) {
|
|
bailout = true;
|
|
} else {
|
|
decrease_indentation = true;
|
|
}
|
|
break;
|
|
case kExprDelegate:
|
|
decrease_indentation = true;
|
|
break;
|
|
case kExprElse:
|
|
case kExprCatch:
|
|
case kExprCatchAll:
|
|
decrease_indentation = true;
|
|
increase_indentation_after = true;
|
|
break;
|
|
case kExprBlock:
|
|
case kExprIf:
|
|
case kExprLoop:
|
|
case kExprTry:
|
|
case kExprTryTable:
|
|
increase_indentation_after = true;
|
|
break;
|
|
case kExprI32Const:
|
|
case kExprI64Const:
|
|
case kExprF32Const:
|
|
case kExprF64Const:
|
|
print_instruction = false;
|
|
break;
|
|
default:
|
|
// The other instructions get no special treatment.
|
|
break;
|
|
}
|
|
if (decrease_indentation && indentation_.current() > base_indentation) {
|
|
indentation_.decrease();
|
|
}
|
|
if (bailout) break;
|
|
out << indentation_;
|
|
if (print_instruction) out << RawOpcodeName(opcode) << ",";
|
|
if (increase_indentation_after) indentation_.increase();
|
|
}
|
|
current_opcode_ = opcode;
|
|
pc_ += PrintMjsunitImmediatesAndGetLength(out);
|
|
out.NextLine(0);
|
|
}
|
|
|
|
indentation_.decrease();
|
|
out << indentation_ << "]);";
|
|
out.NextLine(0);
|
|
}
|
|
|
|
void PrintF32Const(StringBuilder& out, ImmF32Immediate& imm) {
|
|
uint32_t bits = base::bit_cast<uint32_t>(imm.value);
|
|
if (bits == 0x80000000) {
|
|
out << "wasmF32Const(-0)";
|
|
return;
|
|
}
|
|
if (std::isnan(imm.value)) {
|
|
out << "[kExprF32Const";
|
|
for (int i = 0; i < 4; i++) {
|
|
uint32_t chunk = bits & 0xFF;
|
|
bits >>= 8;
|
|
out << ", 0x" << kHexChars[chunk >> 4] << kHexChars[chunk & 0xF];
|
|
}
|
|
out << "]";
|
|
return;
|
|
}
|
|
char buffer[100];
|
|
const char* str =
|
|
DoubleToCString(imm.value, base::VectorOf(buffer, sizeof(buffer)));
|
|
out << "wasmF32Const(" << str << ")";
|
|
}
|
|
|
|
void PrintF64Const(StringBuilder& out, ImmF64Immediate& imm) {
|
|
uint64_t bits = base::bit_cast<uint64_t>(imm.value);
|
|
if (bits == base::bit_cast<uint64_t>(-0.0)) {
|
|
out << "wasmF64Const(-0)";
|
|
return;
|
|
}
|
|
if (std::isnan(imm.value)) {
|
|
out << "[kExprF64Const";
|
|
for (int i = 0; i < 8; i++) {
|
|
uint32_t chunk = bits & 0xFF;
|
|
bits >>= 8;
|
|
out << ", 0x" << kHexChars[chunk >> 4] << kHexChars[chunk & 0xF];
|
|
}
|
|
out << "]";
|
|
return;
|
|
}
|
|
char buffer[100];
|
|
const char* str =
|
|
DoubleToCString(imm.value, base::VectorOf(buffer, sizeof(buffer)));
|
|
out << "wasmF64Const(" << str << ")";
|
|
}
|
|
|
|
void PrintI64Const(StringBuilder& out, ImmI64Immediate& imm) {
|
|
out << "wasmI64Const(";
|
|
if (imm.value >= 0) {
|
|
out << static_cast<uint64_t>(imm.value);
|
|
} else {
|
|
out << "-" << ((~static_cast<uint64_t>(imm.value)) + 1);
|
|
}
|
|
out << "n)"; // `n` to make it a BigInt literal.
|
|
}
|
|
|
|
void MjsunitFunctionDis::DecodeGlobalInitializer(StringBuilder& out) {
|
|
// Special: Pretty-print simple constants (that aren't handled by the
|
|
// i32 special case at the caller).
|
|
uint32_t length = static_cast<uint32_t>(end_ - pc_);
|
|
if (*(end_ - 1) == kExprEnd) {
|
|
if (*pc_ == kExprF32Const && length == 6) {
|
|
ImmF32Immediate imm(this, pc_ + 1, validate);
|
|
return PrintF32Const(out, imm);
|
|
}
|
|
if (*pc_ == kExprF64Const && length == 10) {
|
|
ImmF64Immediate imm(this, pc_ + 1, validate);
|
|
return PrintF64Const(out, imm);
|
|
}
|
|
if (*pc_ == kExprI64Const) {
|
|
ImmI64Immediate imm(this, pc_ + 1, validate);
|
|
if (length == 2 + imm.length) {
|
|
return PrintI64Const(out, imm);
|
|
}
|
|
}
|
|
}
|
|
// Regular path.
|
|
out << "[";
|
|
const char* old_cursor = out.cursor();
|
|
while (pc_ < end_ && ok()) {
|
|
WasmOpcode opcode = static_cast<WasmOpcode>(*pc_);
|
|
if (WasmOpcodes::IsPrefixOpcode(opcode)) {
|
|
opcode = PrintPrefixedOpcode(out, opcode);
|
|
} else {
|
|
// Don't print the final "end".
|
|
if (opcode == kExprEnd && pc_ + 1 == end_) break;
|
|
// Constants will decide whether to print the instruction.
|
|
if (opcode != kExprI32Const && opcode != kExprI64Const &&
|
|
opcode != kExprF32Const && opcode != kExprF64Const) {
|
|
out << RawOpcodeName(opcode) << ",";
|
|
}
|
|
}
|
|
current_opcode_ = opcode;
|
|
pc_ += PrintMjsunitImmediatesAndGetLength(out);
|
|
}
|
|
if (out.cursor() != old_cursor) {
|
|
// If anything was written, then it ends with a comma. Erase that to
|
|
// replace it with ']' for conciseness.
|
|
DCHECK_EQ(*(out.cursor() - 1), ',');
|
|
out.backspace();
|
|
}
|
|
out << "]";
|
|
}
|
|
|
|
template <typename ValidationTag>
|
|
class MjsunitImmediatesPrinter {
|
|
public:
|
|
MjsunitImmediatesPrinter(StringBuilder& out, MjsunitFunctionDis* owner)
|
|
: out_(out), owner_(owner) {}
|
|
|
|
MjsunitNamesProvider* names() { return owner_->names_; }
|
|
|
|
void PrintSignature(uint32_t sig_index) {
|
|
out_ << " ";
|
|
if (owner_->module_->has_signature(sig_index)) {
|
|
names()->PrintSigType(out_, sig_index, kEmitWireBytes);
|
|
} else {
|
|
out_ << sig_index << " /* invalid signature */";
|
|
}
|
|
out_ << ",";
|
|
}
|
|
|
|
void BlockType(BlockTypeImmediate& imm) {
|
|
if (imm.sig.all().begin() == nullptr) {
|
|
PrintSignature(imm.sig_index);
|
|
} else if (imm.sig.return_count() == 0) {
|
|
out_ << " kWasmVoid,";
|
|
} else {
|
|
out_ << " ";
|
|
names()->PrintValueType(out_, imm.sig.GetReturn(), kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
}
|
|
|
|
void HeapType(HeapTypeImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintHeapType(out_, imm.type, kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
|
|
// TODO(mliedtke): This is used for br_on_cast[_fail] and currently does not
|
|
// create a valid br_on_cast instruction.
|
|
void ValueType(HeapTypeImmediate& imm, bool is_nullable) {
|
|
out_ << " ";
|
|
names()->PrintValueType(
|
|
out_,
|
|
ValueType::RefMaybeNull(imm.type,
|
|
is_nullable ? kNullable : kNonNullable),
|
|
kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
|
|
void BranchDepth(BranchDepthImmediate& imm) { WriteUnsignedLEB(imm.depth); }
|
|
|
|
void BranchTable(BranchTableImmediate& imm) {
|
|
WriteUnsignedLEB(imm.table_count);
|
|
const uint8_t* pc = imm.table;
|
|
// i == table_count is the default case.
|
|
for (uint32_t i = 0; i <= imm.table_count; i++) {
|
|
auto [target, length] = owner_->read_u32v<ValidationTag>(pc);
|
|
pc += length;
|
|
WriteUnsignedLEB(target);
|
|
}
|
|
}
|
|
|
|
void TryTable(TryTableImmediate& imm) {
|
|
WriteUnsignedLEB(imm.table_count);
|
|
owner_->indentation_.increase();
|
|
owner_->indentation_.increase();
|
|
const uint8_t* pc = imm.table;
|
|
for (uint32_t i = 0; i < imm.table_count; i++) {
|
|
out_ << "\n" << owner_->indentation_;
|
|
uint8_t kind = owner_->read_u8<ValidationTag>(pc++);
|
|
switch (kind) {
|
|
case kCatch:
|
|
out_ << "kCatchNoRef, ";
|
|
break;
|
|
case kCatchRef:
|
|
out_ << "kCatchRef, ";
|
|
break;
|
|
case kCatchAll:
|
|
out_ << "kCatchAllNoRef, ";
|
|
break;
|
|
case kCatchAllRef:
|
|
out_ << "kCatchAllRef, ";
|
|
break;
|
|
default:
|
|
out_ << kind;
|
|
}
|
|
if (kind == kCatch || kind == kCatchRef) {
|
|
auto [tag, length] = owner_->read_u32v<ValidationTag>(pc);
|
|
pc += length;
|
|
names()->PrintTagReferenceLeb(out_, tag);
|
|
out_ << ", ";
|
|
}
|
|
auto [target, length] = owner_->read_u32v<ValidationTag>(pc);
|
|
pc += length;
|
|
out_ << target << ",";
|
|
}
|
|
owner_->indentation_.decrease();
|
|
owner_->indentation_.decrease();
|
|
}
|
|
|
|
void CallIndirect(CallIndirectImmediate& imm) {
|
|
PrintSignature(imm.sig_imm.index);
|
|
TableIndex(imm.table_imm);
|
|
}
|
|
|
|
void SelectType(SelectTypeImmediate& imm) {
|
|
out_ << " 1, "; // One type.
|
|
names()->PrintValueType(out_, imm.type, kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
|
|
void MemoryAccess(MemoryAccessImmediate& imm) {
|
|
uint32_t align = imm.alignment;
|
|
if (imm.mem_index != 0) {
|
|
align |= 0x40;
|
|
WriteUnsignedLEB(align);
|
|
WriteUnsignedLEB(imm.mem_index);
|
|
} else {
|
|
WriteUnsignedLEB(align);
|
|
}
|
|
if (imm.mem_index < owner_->module_->memories.size() &&
|
|
owner_->module_->memories[imm.mem_index].is_memory64) {
|
|
WriteLEB64(imm.offset);
|
|
} else {
|
|
DCHECK_LE(imm.offset, std::numeric_limits<uint32_t>::max());
|
|
WriteUnsignedLEB(static_cast<uint32_t>(imm.offset));
|
|
}
|
|
}
|
|
|
|
void SimdLane(SimdLaneImmediate& imm) { out_ << " " << imm.lane << ","; }
|
|
|
|
void Field(FieldImmediate& imm) {
|
|
TypeIndex(imm.struct_imm);
|
|
WriteUnsignedLEB(imm.field_imm.index);
|
|
}
|
|
|
|
void Length(IndexImmediate& imm) { WriteUnsignedLEB(imm.index); }
|
|
|
|
void TagIndex(TagIndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintTagReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void FunctionIndex(IndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintFunctionReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void TypeIndex(IndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintTypeIndex(out_, imm.index, kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
|
|
void LocalIndex(IndexImmediate& imm) {
|
|
WriteUnsignedLEB(imm.index);
|
|
out_ << " // ";
|
|
names()->PrintLocalName(out_, imm.index);
|
|
}
|
|
|
|
void GlobalIndex(IndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintGlobalReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void TableIndex(TableIndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintTableReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void MemoryIndex(MemoryIndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintMemoryReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void DataSegmentIndex(IndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintDataSegmentReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void ElemSegmentIndex(IndexImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintElementSegmentReferenceLeb(out_, imm.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void I32Const(ImmI32Immediate& imm) {
|
|
if (imm.value >= 0 && imm.value <= 0x3F) {
|
|
out_ << "kExprI32Const, " << imm.value << ",";
|
|
} else {
|
|
out_ << "...wasmI32Const(" << imm.value << "),";
|
|
}
|
|
}
|
|
|
|
void I64Const(ImmI64Immediate& imm) {
|
|
if (imm.value >= 0 && imm.value <= 0x3F) {
|
|
out_ << "kExprI64Const, " << static_cast<uint32_t>(imm.value) << ',';
|
|
return;
|
|
}
|
|
out_ << "...";
|
|
PrintI64Const(out_, imm);
|
|
out_ << ",";
|
|
}
|
|
|
|
void F32Const(ImmF32Immediate& imm) {
|
|
out_ << "...";
|
|
PrintF32Const(out_, imm);
|
|
out_ << ",";
|
|
}
|
|
|
|
void F64Const(ImmF64Immediate& imm) {
|
|
out_ << "...";
|
|
PrintF64Const(out_, imm);
|
|
out_ << ",";
|
|
}
|
|
|
|
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_ << "...wasmS128Const([";
|
|
for (int i = 0; i < 16; i++) {
|
|
if (i > 0) out_ << ", ";
|
|
out_ << uint32_t{imm.value[i]};
|
|
}
|
|
out_ << "]),";
|
|
}
|
|
}
|
|
|
|
void StringConst(StringConstImmediate& imm) {
|
|
// TODO(jkummerow): Support for string constants is incomplete, we never
|
|
// emit a strings section.
|
|
WriteUnsignedLEB(imm.index);
|
|
}
|
|
|
|
void MemoryInit(MemoryInitImmediate& imm) {
|
|
DataSegmentIndex(imm.data_segment);
|
|
WriteUnsignedLEB(imm.memory.index);
|
|
}
|
|
|
|
void MemoryCopy(MemoryCopyImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintMemoryReferenceLeb(out_, imm.memory_dst.index);
|
|
out_ << ", ";
|
|
names()->PrintMemoryReferenceLeb(out_, imm.memory_src.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void TableInit(TableInitImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintElementSegmentReferenceLeb(out_, imm.element_segment.index);
|
|
out_ << ", ";
|
|
names()->PrintTableReferenceLeb(out_, imm.table.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void TableCopy(TableCopyImmediate& imm) {
|
|
out_ << " ";
|
|
names()->PrintTableReferenceLeb(out_, imm.table_dst.index);
|
|
out_ << ", ";
|
|
names()->PrintTableReferenceLeb(out_, imm.table_src.index);
|
|
out_ << ",";
|
|
}
|
|
|
|
void ArrayCopy(IndexImmediate& dst, IndexImmediate& src) {
|
|
out_ << " ";
|
|
names()->PrintTypeIndex(out_, dst.index, kEmitWireBytes);
|
|
out_ << ", ";
|
|
names()->PrintTypeIndex(out_, src.index, kEmitWireBytes);
|
|
out_ << ",";
|
|
}
|
|
|
|
private:
|
|
void WriteUnsignedLEB(uint32_t value) {
|
|
if (value < 128) {
|
|
out_ << " " << value << ",";
|
|
} else {
|
|
out_ << " ...wasmUnsignedLeb(" << value << "),";
|
|
}
|
|
}
|
|
void WriteLEB64(uint64_t value) {
|
|
if (value < 128) {
|
|
out_ << " " << value << ",";
|
|
} else {
|
|
// TODO(jkummerow): Technically we should use an unsigned version,
|
|
// but the module builder doesn't offer one yet.
|
|
out_ << " ...wasmSignedLeb64(" << value << "n),";
|
|
}
|
|
}
|
|
|
|
StringBuilder& out_;
|
|
MjsunitFunctionDis* owner_;
|
|
};
|
|
|
|
// For opcodes that produce constants (such as `kExprI32Const`), this prints
|
|
// more than just the immediate: it also decides whether to use
|
|
// "kExprI32Const, 0," or "...wasmI32Const(1234567)".
|
|
uint32_t MjsunitFunctionDis::PrintMjsunitImmediatesAndGetLength(
|
|
StringBuilder& out) {
|
|
using Printer = MjsunitImmediatesPrinter<ValidationTag>;
|
|
Printer imm_printer(out, this);
|
|
return WasmDecoder::OpcodeLength<Printer>(this, this->pc_, imm_printer);
|
|
}
|
|
|
|
class MjsunitModuleDis {
|
|
public:
|
|
MjsunitModuleDis(MultiLineStringBuilder& out, const WasmModule* module,
|
|
NamesProvider* names, const ModuleWireBytes wire_bytes,
|
|
AccountingAllocator* allocator, bool has_error = false)
|
|
: out_(out),
|
|
module_(module),
|
|
names_provider_(names),
|
|
mjsunit_names_(module, wire_bytes),
|
|
wire_bytes_(wire_bytes),
|
|
zone_(allocator, "disassembler"),
|
|
has_error_(has_error) {
|
|
offsets_.CollectOffsets(module, wire_bytes.module_bytes());
|
|
}
|
|
|
|
void PrintModule() {
|
|
tzset();
|
|
time_t current_time = time(nullptr);
|
|
struct tm current_localtime;
|
|
#ifdef V8_OS_WIN
|
|
localtime_s(¤t_localtime, ¤t_time);
|
|
#else
|
|
localtime_r(¤t_time, ¤t_localtime);
|
|
#endif
|
|
int year = 1900 + current_localtime.tm_year;
|
|
|
|
out_ << "// Copyright " << year
|
|
<< " the V8 project authors. All rights reserved.\n"
|
|
"// Use of this source code is governed by a BSD-style license "
|
|
"that can be\n"
|
|
"// found in the LICENSE file.\n"
|
|
"\n"
|
|
"// Flags: --wasm-staging\n"
|
|
"\n"
|
|
"d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');\n"
|
|
"\n"
|
|
"const builder = new WasmModuleBuilder();";
|
|
out_.NextLine(0);
|
|
|
|
// Module name, if present.
|
|
if (module_->name.is_set()) {
|
|
out_ << "builder.setName('";
|
|
PrintName(module_->name);
|
|
out_ << "');";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Types.
|
|
// TODO(14616): Support shared types.
|
|
|
|
// Support self-referential and mutually-recursive types.
|
|
std::vector<uint32_t> needed_at(module_->types.size(), kMaxUInt32);
|
|
auto MarkAsNeededHere = [&needed_at](ValueType vt, uint32_t here) {
|
|
if (!vt.is_object_reference()) return;
|
|
HeapType ht = vt.heap_type();
|
|
if (!ht.is_index()) return;
|
|
if (ht.ref_index() < here) return;
|
|
if (needed_at[ht.ref_index()] < here) return;
|
|
needed_at[ht.ref_index()] = here;
|
|
};
|
|
for (uint32_t i = 0; i < module_->types.size(); i++) {
|
|
if (module_->has_struct(i)) {
|
|
const StructType* struct_type = module_->types[i].struct_type;
|
|
for (uint32_t fi = 0; fi < struct_type->field_count(); fi++) {
|
|
MarkAsNeededHere(struct_type->field(fi), i);
|
|
}
|
|
} else if (module_->has_array(i)) {
|
|
MarkAsNeededHere(module_->types[i].array_type->element_type(), i);
|
|
} else {
|
|
DCHECK(module_->has_signature(i));
|
|
const FunctionSig* sig = module_->types[i].function_sig;
|
|
for (size_t pi = 0; pi < sig->parameter_count(); pi++) {
|
|
MarkAsNeededHere(sig->GetParam(pi), i);
|
|
}
|
|
for (size_t ri = 0; ri < sig->return_count(); ri++) {
|
|
MarkAsNeededHere(sig->GetReturn(ri), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t recgroup_index = 0;
|
|
OffsetsProvider::RecGroup recgroup = offsets_.recgroup(recgroup_index++);
|
|
bool in_explicit_recgroup = false;
|
|
for (uint32_t i = 0; i < module_->types.size(); i++) {
|
|
while (i == recgroup.start_type_index) {
|
|
out_ << "builder.startRecGroup();";
|
|
out_.NextLine(0);
|
|
if (V8_UNLIKELY(recgroup.end_type_index == i)) {
|
|
// Empty recgroup.
|
|
out_ << "builder.endRecGroup();";
|
|
out_.NextLine(0);
|
|
DCHECK(!in_explicit_recgroup);
|
|
recgroup = offsets_.recgroup(recgroup_index++);
|
|
continue;
|
|
} else {
|
|
in_explicit_recgroup = true;
|
|
break;
|
|
}
|
|
}
|
|
uint32_t end_index =
|
|
recgroup.end_type_index != OffsetsProvider::RecGroup::kInvalid
|
|
? recgroup.end_type_index
|
|
: i + 1;
|
|
for (uint32_t pre = i; pre < end_index; pre++) {
|
|
if (needed_at[pre] == i) {
|
|
out_ << "let ";
|
|
names()->PrintTypeVariableName(out_, pre);
|
|
if (pre == i) {
|
|
out_ << " = builder.nextTypeIndex();";
|
|
} else {
|
|
out_ << " = builder.nextTypeIndex() + " << (pre - i) << ";";
|
|
}
|
|
out_.NextLine(0);
|
|
}
|
|
}
|
|
uint32_t supertype = module_->types[i].supertype;
|
|
bool is_final = module_->types[i].is_final;
|
|
if (needed_at[i] == kMaxUInt32) {
|
|
out_ << "let ";
|
|
names()->PrintTypeVariableName(out_, i);
|
|
out_ << " = ";
|
|
} else {
|
|
out_ << "/* ";
|
|
names()->PrintTypeVariableName(out_, i);
|
|
out_ << " */ ";
|
|
}
|
|
if (module_->has_struct(i)) {
|
|
const StructType* struct_type = module_->types[i].struct_type;
|
|
out_ << "builder.addStruct([";
|
|
for (uint32_t fi = 0; fi < struct_type->field_count(); fi++) {
|
|
if (fi > 0) out_ << ", ";
|
|
out_ << "makeField(";
|
|
names()->PrintValueType(out_, struct_type->field(fi), kEmitObjects);
|
|
out_ << ", " << (struct_type->mutability(fi) ? "true" : "false");
|
|
out_ << ")";
|
|
}
|
|
out_ << "], ";
|
|
if (supertype != kNoSuperType) {
|
|
names()->PrintTypeIndex(out_, supertype, kEmitObjects);
|
|
} else {
|
|
out_ << "kNoSuperType";
|
|
}
|
|
out_ << ", " << (is_final ? "true" : "false") << ");";
|
|
out_.NextLine(0);
|
|
} else if (module_->has_array(i)) {
|
|
const ArrayType* array_type = module_->types[i].array_type;
|
|
out_ << "builder.addArray(";
|
|
names()->PrintValueType(out_, array_type->element_type(), kEmitObjects);
|
|
out_ << ", ";
|
|
out_ << (array_type->mutability() ? "true" : "false") << ", ";
|
|
if (supertype != kNoSuperType) {
|
|
names()->PrintTypeIndex(out_, supertype, kEmitObjects);
|
|
} else {
|
|
out_ << "kNoSuperType";
|
|
}
|
|
out_ << ", " << (is_final ? "true" : "false") << ");";
|
|
out_.NextLine(0);
|
|
} else {
|
|
DCHECK(module_->has_signature(i));
|
|
const FunctionSig* sig = module_->types[i].function_sig;
|
|
out_ << "builder.addType(";
|
|
names()->PrintMakeSignature(out_, sig);
|
|
if (!is_final || supertype != kNoSuperType) {
|
|
out_ << ", ";
|
|
if (supertype != kNoSuperType) {
|
|
names()->PrintTypeIndex(out_, supertype, kEmitObjects);
|
|
} else {
|
|
out_ << "kNoSuperType";
|
|
}
|
|
if (!is_final) out_ << ", false";
|
|
}
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
if (in_explicit_recgroup && i == recgroup.end_type_index - 1) {
|
|
in_explicit_recgroup = false;
|
|
out_ << "builder.endRecGroup();";
|
|
out_.NextLine(0);
|
|
recgroup = offsets_.recgroup(recgroup_index++);
|
|
}
|
|
}
|
|
while (recgroup.valid()) {
|
|
// There could be empty recgroups at the end of the type section.
|
|
DCHECK_GE(recgroup.start_type_index, module_->types.size());
|
|
DCHECK_EQ(recgroup.start_type_index, recgroup.end_type_index);
|
|
out_ << "builder.startRecgroup();\nbuilder.endRecGroup();";
|
|
out_.NextLine(0);
|
|
recgroup = offsets_.recgroup(recgroup_index++);
|
|
}
|
|
|
|
// Imports.
|
|
for (const WasmImport& imported : module_->import_table) {
|
|
out_ << "let ";
|
|
switch (imported.kind) {
|
|
case kExternalFunction:
|
|
names()->PrintFunctionVariableName(out_, imported.index);
|
|
out_ << " = builder.addImport('" << V(imported.module_name);
|
|
out_ << "', '" << V(imported.field_name) << "', ";
|
|
names()->PrintTypeIndex(
|
|
out_, module_->functions[imported.index].sig_index, kEmitObjects);
|
|
break;
|
|
|
|
case kExternalTable: {
|
|
names()->PrintTableName(out_, imported.index);
|
|
out_ << " = builder.addImportedTable('" << V(imported.module_name);
|
|
out_ << "', '" << V(imported.field_name) << "', ";
|
|
const WasmTable& table = module_->tables[imported.index];
|
|
out_ << table.initial_size << ", ";
|
|
if (table.has_maximum_size) {
|
|
out_ << table.maximum_size << ", ";
|
|
} else {
|
|
out_ << "undefined, ";
|
|
}
|
|
names()->PrintValueType(out_, table.type, kEmitObjects);
|
|
break;
|
|
}
|
|
case kExternalGlobal: {
|
|
names()->PrintGlobalName(out_, imported.index);
|
|
out_ << " = builder.addImportedGlobal('" << V(imported.module_name);
|
|
out_ << "', '" << V(imported.field_name) << "', ";
|
|
const WasmGlobal& global = module_->globals[imported.index];
|
|
names()->PrintValueType(out_, global.type, kEmitObjects);
|
|
if (global.mutability || global.shared) {
|
|
out_ << ", " << (global.mutability ? "true" : "false");
|
|
}
|
|
if (global.shared) out_ << ", true";
|
|
break;
|
|
}
|
|
case kExternalMemory: {
|
|
names()->PrintMemoryName(out_, imported.index);
|
|
out_ << " = builder.addImportedMemory('" << V(imported.module_name);
|
|
out_ << "', '" << V(imported.field_name) << "', ";
|
|
const WasmMemory& memory = module_->memories[imported.index];
|
|
out_ << memory.initial_pages << ", ";
|
|
if (memory.has_maximum_pages) {
|
|
out_ << memory.maximum_pages << ", ";
|
|
} else {
|
|
out_ << "undefined, ";
|
|
}
|
|
out_ << (memory.is_shared ? "true" : "false");
|
|
if (memory.is_memory64) out_ << ", true";
|
|
break;
|
|
}
|
|
case kExternalTag: {
|
|
names()->PrintTagName(out_, imported.index);
|
|
out_ << " = builder.addImportedTag('" << V(imported.module_name);
|
|
out_ << "', '" << V(imported.field_name) << "', ";
|
|
names()->PrintTypeIndex(out_, module_->tags[imported.index].sig_index,
|
|
kEmitObjects);
|
|
break;
|
|
}
|
|
}
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Declare functions (without bodies).
|
|
//
|
|
// TODO(jkummerow): We need function variables to be defined in case they
|
|
// are used by init expressions, element segments, or in function bodies.
|
|
// For now, we just declare all functions up front. We could do this
|
|
// selectively (in the interest of conciseness), if we performed a pre-scan
|
|
// of the module to find functions that are referenced by index anywhere.
|
|
//
|
|
// For testing, we ensure that the order of exports remains the same.
|
|
// So when there are non-function imports, we don't annotate functions
|
|
// as exported right away, but postpone that until the exports section.
|
|
// This behavior is not required for correctness, it just helps with
|
|
// differential testing (roundtripping a module through `wami --mjsunit`
|
|
// and `d8 --dump-wasm-module`).
|
|
static constexpr bool kMaintainExportOrder = true;
|
|
bool export_functions_late = false;
|
|
if constexpr (kMaintainExportOrder) {
|
|
for (const WasmExport& ex : module_->export_table) {
|
|
if (ex.kind != kExternalFunction ||
|
|
module_->functions[ex.index].imported) {
|
|
export_functions_late = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (const WasmFunction& func : module_->functions) {
|
|
if (func.imported) continue;
|
|
uint32_t index = func.func_index;
|
|
out_ << "let ";
|
|
names()->PrintFunctionVariableName(out_, index);
|
|
out_ << " = builder.addFunction(";
|
|
if (names()->HasFunctionName(index)) {
|
|
out_ << '"';
|
|
names()->PrintFunctionName(out_, index);
|
|
out_ << '"';
|
|
} else {
|
|
out_ << "undefined";
|
|
}
|
|
out_ << ", ";
|
|
out_ << "$sig" << func.sig_index;
|
|
out_ << ")";
|
|
if (func.exported && !export_functions_late) {
|
|
for (const WasmExport& ex : module_->export_table) {
|
|
if (ex.kind != kExternalFunction || ex.index != index) continue;
|
|
if (names()->FunctionNameEquals(index, ex.name)) {
|
|
out_ << ".exportFunc();";
|
|
} else {
|
|
out_ << ".exportAs('";
|
|
PrintName(ex.name);
|
|
out_ << "');";
|
|
}
|
|
}
|
|
} else {
|
|
out_ << ";";
|
|
}
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Start function.
|
|
if (module_->start_function_index >= 0) {
|
|
out_ << "builder.addStart(";
|
|
names()->PrintFunctionReference(out_, module_->start_function_index);
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Memories.
|
|
for (const WasmMemory& memory : module_->memories) {
|
|
if (memory.imported) continue;
|
|
out_ << "let ";
|
|
names()->PrintMemoryName(out_, memory.index);
|
|
if (memory.is_memory64) {
|
|
out_ << " = builder.addMemory64(";
|
|
} else {
|
|
out_ << " = builder.addMemory(";
|
|
}
|
|
out_ << memory.initial_pages;
|
|
if (memory.has_maximum_pages) {
|
|
out_ << ", " << memory.maximum_pages;
|
|
} else {
|
|
out_ << ", undefined";
|
|
}
|
|
if (memory.is_shared) {
|
|
out_ << ", true";
|
|
}
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Data segments.
|
|
for (uint32_t i = 0; i < module_->data_segments.size(); i++) {
|
|
const WasmDataSegment& segment = module_->data_segments[i];
|
|
base::Vector<const uint8_t> data = wire_bytes_.module_bytes().SubVector(
|
|
segment.source.offset(), segment.source.end_offset());
|
|
out_ << "let ";
|
|
names()->PrintDataSegmentName(out_, i);
|
|
if (segment.active) {
|
|
out_ << " = builder.addActiveDataSegment(" << segment.memory_index
|
|
<< ", ";
|
|
DecodeAndAppendInitExpr(segment.dest_addr, kWasmI32);
|
|
out_ << ", ";
|
|
} else {
|
|
out_ << " = builder.addPassiveDataSegment(";
|
|
}
|
|
out_ << "[";
|
|
uint32_t num_bytes = static_cast<uint32_t>(data.size());
|
|
if (num_bytes > 0) out_ << uint32_t{data[0]};
|
|
for (uint32_t i = 1; i < num_bytes; i++) {
|
|
out_ << ", " << uint32_t{data[i]};
|
|
}
|
|
out_ << "]";
|
|
if (segment.shared) out_ << ", true";
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Globals.
|
|
for (uint32_t i = module_->num_imported_globals;
|
|
i < module_->globals.size(); i++) {
|
|
const WasmGlobal& global = module_->globals[i];
|
|
out_ << "let ";
|
|
names()->PrintGlobalName(out_, i);
|
|
out_ << " = builder.addGlobal(";
|
|
names()->PrintValueType(out_, global.type, kEmitObjects);
|
|
out_ << ", " << (global.mutability ? "true" : "false") << ", ";
|
|
out_ << (global.shared ? "true" : "false") << ", ";
|
|
DecodeAndAppendInitExpr(global.init, global.type);
|
|
if (!kMaintainExportOrder && global.exported) {
|
|
out_ << ").exportAs('";
|
|
PrintExportName(kExternalGlobal, i);
|
|
out_ << "'";
|
|
}
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Tables.
|
|
for (uint32_t i = module_->num_imported_tables; i < module_->tables.size();
|
|
i++) {
|
|
const WasmTable& table = module_->tables[i];
|
|
out_ << "let ";
|
|
names()->PrintTableName(out_, i);
|
|
out_ << " = builder.addTable(";
|
|
names()->PrintValueType(out_, table.type, kEmitObjects);
|
|
out_ << ", " << table.initial_size << ", ";
|
|
if (table.has_maximum_size) {
|
|
out_ << table.maximum_size;
|
|
} else {
|
|
out_ << "undefined";
|
|
}
|
|
if (table.initial_value.is_set()) {
|
|
out_ << ", ";
|
|
DecodeAndAppendInitExpr(table.initial_value, table.type);
|
|
} else if (table.shared) {
|
|
out_ << ", undefined";
|
|
}
|
|
if (table.shared) out_ << ", true";
|
|
if (!kMaintainExportOrder && table.exported) {
|
|
out_ << ").exportAs('";
|
|
PrintExportName(kExternalTable, i);
|
|
out_ << "'";
|
|
}
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Element segments.
|
|
for (uint32_t i = 0; i < module_->elem_segments.size(); i++) {
|
|
const WasmElemSegment& segment = module_->elem_segments[i];
|
|
out_ << "let ";
|
|
names()->PrintElementSegmentName(out_, i);
|
|
if (segment.status == WasmElemSegment::kStatusActive) {
|
|
out_ << " = builder.addActiveElementSegment(";
|
|
names()->PrintTableReference(out_, segment.table_index);
|
|
out_ << ", ";
|
|
DecodeAndAppendInitExpr(segment.offset, kWasmI32);
|
|
out_ << ", ";
|
|
} else if (segment.status == WasmElemSegment::kStatusPassive) {
|
|
out_ << " = builder.addPassiveElementSegment(";
|
|
} else {
|
|
DCHECK_EQ(segment.status, WasmElemSegment::kStatusDeclarative);
|
|
out_ << " = builder.addDeclarativeElementSegment(";
|
|
}
|
|
out_ << "[";
|
|
ModuleDecoderImpl decoder(WasmEnabledFeatures::All(),
|
|
wire_bytes_.module_bytes(),
|
|
ModuleOrigin::kWasmOrigin);
|
|
// This implementation detail is load-bearing: if we simply let the
|
|
// {decoder} start at this offset, it could produce WireBytesRefs that
|
|
// start at offset 0, which violates DCHECK-guarded assumptions.
|
|
decoder.consume_bytes(segment.elements_wire_bytes_offset);
|
|
for (uint32_t j = 0; j < segment.element_count; j++) {
|
|
if (j > 0) out_ << ", ";
|
|
ConstantExpression expr = decoder.consume_element_segment_entry(
|
|
const_cast<WasmModule*>(module_), segment);
|
|
if (segment.element_type == WasmElemSegment::kExpressionElements) {
|
|
DecodeAndAppendInitExpr(expr, segment.type);
|
|
} else {
|
|
names()->PrintFunctionReference(out_, expr.index());
|
|
}
|
|
}
|
|
out_ << "]";
|
|
if (segment.element_type == WasmElemSegment::kExpressionElements) {
|
|
out_ << ", ";
|
|
names()->PrintValueType(out_, segment.type, kEmitObjects);
|
|
}
|
|
if (segment.shared) out_ << ", true";
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Tags.
|
|
for (uint32_t i = module_->num_imported_tags; i < module_->tags.size();
|
|
i++) {
|
|
const WasmTag& tag = module_->tags[i];
|
|
out_ << "let ";
|
|
names()->PrintTagName(out_, i);
|
|
out_ << " = builder.addTag(";
|
|
// The signature was already emitted as one of the types.
|
|
// TODO(jkummerow): For conciseness, consider pre-scanning signatures
|
|
// that are only used by tags, and using {PrintMakeSignature(
|
|
// tag.ToFunctionSig())} here.
|
|
names()->PrintSigType(out_, tag.sig_index, kEmitObjects);
|
|
out_ << ");";
|
|
out_.NextLine(0);
|
|
}
|
|
|
|
// Functions.
|
|
for (const WasmFunction& func : module_->functions) {
|
|
if (func.imported) continue;
|
|
uint32_t index = func.func_index;
|
|
|
|
// Header and signature.
|
|
out_.NextLine(0);
|
|
out_ << "// func ";
|
|
names_provider_->PrintFunctionName(out_, index, NamesProvider::kDevTools);
|
|
names()->PrintSignatureComment(out_, func.sig);
|
|
out_.NextLine(0);
|
|
|
|
names()->PrintFunctionVariableName(out_, index);
|
|
|
|
base::Vector<const uint8_t> func_code =
|
|
wire_bytes_.GetFunctionBytes(&func);
|
|
|
|
// Locals and body.
|
|
bool shared = module_->types[func.sig_index].is_shared;
|
|
WasmDetectedFeatures detected;
|
|
MjsunitFunctionDis d(&zone_, module_, index, shared, &detected, func.sig,
|
|
func_code.begin(), func_code.end(),
|
|
func.code.offset(), &mjsunit_names_,
|
|
Indentation{2, 2});
|
|
d.WriteMjsunit(out_);
|
|
if (d.failed()) has_error_ = true;
|
|
}
|
|
out_.NextLine(0);
|
|
|
|
// Exports.
|
|
bool added_any_export = false;
|
|
for (const WasmExport& ex : module_->export_table) {
|
|
switch (ex.kind) {
|
|
case kExternalFunction:
|
|
if (!export_functions_late &&
|
|
!module_->functions[ex.index].imported) {
|
|
continue; // Handled above.
|
|
}
|
|
out_ << "builder.addExport('";
|
|
PrintName(ex.name);
|
|
out_ << "', ";
|
|
names()->PrintFunctionReference(out_, ex.index);
|
|
out_ << ");";
|
|
break;
|
|
case kExternalMemory:
|
|
out_ << "builder.exportMemoryAs('";
|
|
PrintName(ex.name);
|
|
out_ << "', ";
|
|
names()->PrintMemoryName(out_, ex.index);
|
|
out_ << ");";
|
|
break;
|
|
case kExternalGlobal:
|
|
if (!kMaintainExportOrder &&
|
|
ex.index >= module_->num_imported_globals) {
|
|
continue;
|
|
}
|
|
out_ << "builder.addExportOfKind('";
|
|
PrintName(ex.name);
|
|
out_ << "', kExternalGlobal, ";
|
|
names()->PrintGlobalReference(out_, ex.index);
|
|
out_ << ");";
|
|
break;
|
|
case kExternalTable:
|
|
if (!kMaintainExportOrder &&
|
|
ex.index >= module_->num_imported_tables) {
|
|
continue;
|
|
}
|
|
out_ << "builder.addExportOfKind('";
|
|
PrintName(ex.name);
|
|
out_ << "', kExternalTable, ";
|
|
names()->PrintTableReference(out_, ex.index);
|
|
out_ << ");";
|
|
break;
|
|
case kExternalTag:
|
|
out_ << "builder.addExportOfKind('";
|
|
PrintName(ex.name);
|
|
out_ << "', kExternalTag, ";
|
|
names()->PrintTagName(out_, ex.index);
|
|
out_ << ");";
|
|
break;
|
|
}
|
|
out_.NextLine(0);
|
|
added_any_export = true;
|
|
}
|
|
|
|
// Instantiate and invoke.
|
|
if (added_any_export) out_.NextLine(0);
|
|
bool compiles = !has_error_;
|
|
if (compiles) {
|
|
out_ << "let kBuiltins = { builtins: ['js-string', 'text-decoder', "
|
|
"'text-encoder'] };\n"
|
|
"const instance = builder.instantiate({}, kBuiltins);\n"
|
|
"try {\n"
|
|
" print(instance.exports.main(1, 2, 3));\n"
|
|
"} catch (e) {\n"
|
|
" print('caught exception', e);\n"
|
|
"}";
|
|
out_.NextLine(0);
|
|
} else {
|
|
out_ << "assertThrows(() => builder.instantiate(), "
|
|
"WebAssembly.CompileError);";
|
|
out_.NextLine(0);
|
|
}
|
|
}
|
|
|
|
private:
|
|
base::Vector<const char> V(WireBytesRef ref) {
|
|
return {reinterpret_cast<const char*>(wire_bytes_.start()) + ref.offset(),
|
|
ref.length()};
|
|
}
|
|
void PrintName(WireBytesRef ref) {
|
|
out_.write(wire_bytes_.start() + ref.offset(), ref.length());
|
|
}
|
|
|
|
void PrintExportName(ImportExportKindCode kind, uint32_t index) {
|
|
for (const WasmExport& ex : module_->export_table) {
|
|
if (ex.kind != kind || ex.index != index) continue;
|
|
PrintName(ex.name);
|
|
}
|
|
}
|
|
|
|
void DecodeAndAppendInitExpr(ConstantExpression init, ValueType expected) {
|
|
switch (init.kind()) {
|
|
case ConstantExpression::kEmpty:
|
|
UNREACHABLE();
|
|
case ConstantExpression::kI32Const:
|
|
out_ << "wasmI32Const(" << init.i32_value() << ")";
|
|
break;
|
|
case ConstantExpression::kRefNull:
|
|
out_ << "[kExprRefNull, ";
|
|
names()->PrintHeapType(out_, HeapType(init.repr()), kEmitWireBytes);
|
|
out_ << "]";
|
|
break;
|
|
case ConstantExpression::kRefFunc:
|
|
out_ << "[kExprRefFunc, ";
|
|
names()->PrintFunctionReferenceLeb(out_, init.index());
|
|
out_ << "]";
|
|
break;
|
|
case ConstantExpression::kWireBytesRef: {
|
|
WireBytesRef ref = init.wire_bytes_ref();
|
|
const uint8_t* start = wire_bytes_.start() + ref.offset();
|
|
const uint8_t* end = start + ref.length();
|
|
auto sig = FixedSizeSignature<ValueType>::Returns(expected);
|
|
WasmDetectedFeatures detected;
|
|
MjsunitFunctionDis d(&zone_, module_, 0, false, &detected, &sig, start,
|
|
end, ref.offset(), &mjsunit_names_,
|
|
Indentation{0, 0});
|
|
d.DecodeGlobalInitializer(out_);
|
|
if (d.failed()) has_error_ = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MjsunitNamesProvider* names() { return &mjsunit_names_; }
|
|
|
|
MultiLineStringBuilder& out_;
|
|
const WasmModule* module_;
|
|
NamesProvider* names_provider_;
|
|
MjsunitNamesProvider mjsunit_names_;
|
|
OffsetsProvider offsets_;
|
|
const ModuleWireBytes wire_bytes_;
|
|
Zone zone_;
|
|
bool has_error_{false};
|
|
};
|
|
|
|
} // namespace v8::internal::wasm
|
|
|
|
#endif // V8_TOOLS_WASM_MJSUNIT_MODULE_DISASSEMBLER_IMPL_H_
|