Commit 172f5012 authored by clemensh's avatar clemensh Committed by Commit bot

[wasm] Implement official wasm text format

When disassembling functions for the inspector, we used an internal
text representation before. This CL implements the official text
format like it is understood by the spec interpreter.

Example output:
func $main (param i32) (result i32)
block i32
  get_local 0
  i32.const 2
  i32.lt_u
  if
    i32.const -2
    return
  end
  get_local 0
  call_indirect 0
end

R=rossberg@chromium.org, titzer@chromium.org
BUG=chromium:659715

Review-Url: https://codereview.chromium.org/2520943002
Cr-Commit-Position: refs/heads/master@{#41172}
parent ffe2fbf8
......@@ -1756,6 +1756,8 @@ v8_source_set("v8_base") {
"src/wasm/wasm-opcodes.h",
"src/wasm/wasm-result.cc",
"src/wasm/wasm-result.h",
"src/wasm/wasm-text.cc",
"src/wasm/wasm-text.h",
"src/zone/accounting-allocator.cc",
"src/zone/accounting-allocator.h",
"src/zone/zone-allocator.h",
......
......@@ -36,6 +36,7 @@
#include "src/wasm/ast-decoder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-text.h"
// TODO(titzer): pull WASM_64 up to a common header.
#if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64
......@@ -3359,6 +3360,10 @@ SourcePositionTable* WasmCompilationUnit::BuildGraphForWasmFunction(
OFStream os(stdout);
PrintAst(isolate_->allocator(), body, os, nullptr);
}
if (index >= FLAG_trace_wasm_text_start && index < FLAG_trace_wasm_text_end) {
OFStream os(stdout);
PrintWasmText(module_env_->module, function_->func_index, os, nullptr);
}
if (FLAG_trace_wasm_decode_time) {
*decode_ms = decode_timer.Elapsed().InMillisecondsF();
}
......
......@@ -498,6 +498,10 @@ DEFINE_BOOL(trace_wasm_interpreter, false, "trace interpretation of wasm code")
DEFINE_INT(trace_wasm_ast_start, 0,
"start function for WASM AST trace (inclusive)")
DEFINE_INT(trace_wasm_ast_end, 0, "end function for WASM AST trace (exclusive)")
DEFINE_INT(trace_wasm_text_start, 0,
"start function for WASM text generation (inclusive)")
DEFINE_INT(trace_wasm_text_end, 0,
"end function for WASM text generation (exclusive)")
DEFINE_INT(skip_compiling_wasm_funcs, 0, "start compiling at function N")
DEFINE_BOOL(wasm_break_on_decoder_error, false,
"debug break when wasm decoder encounters an error")
......
......@@ -1294,6 +1294,8 @@
'wasm/wasm-opcodes.h',
'wasm/wasm-result.cc',
'wasm/wasm-result.h',
'wasm/wasm-text.cc',
'wasm/wasm-text.h',
'zone/accounting-allocator.cc',
'zone/accounting-allocator.h',
'zone/zone-segment.cc',
......
......@@ -243,10 +243,6 @@ struct BranchTableOperand {
}
table = pc + 1 + len1;
}
inline uint32_t read_entry(Decoder* decoder, unsigned i) {
DCHECK(i <= table_count);
return table ? decoder->read_u32(table + i * sizeof(uint32_t)) : 0;
}
};
// A helper to iterate over a branch table.
......
......@@ -20,6 +20,7 @@
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-text.h"
#include "src/compiler/wasm-compiler.h"
......@@ -634,15 +635,6 @@ std::pair<int, int> GetFunctionOffsetAndLength(
static_cast<int>(func.code_end_offset - func.code_start_offset)};
}
Vector<const uint8_t> GetFunctionBytes(
Handle<WasmCompiledModule> compiled_module, int func_index) {
int offset, length;
std::tie(offset, length) =
GetFunctionOffsetAndLength(compiled_module, func_index);
return Vector<const uint8_t>(
compiled_module->module_bytes()->GetChars() + offset, length);
}
} // namespace
Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size,
......@@ -1977,19 +1969,16 @@ Handle<Script> wasm::GetScript(Handle<JSObject> instance) {
std::pair<std::string, std::vector<std::tuple<uint32_t, int, int>>>
wasm::DisassembleFunction(Handle<WasmCompiledModule> compiled_module,
int func_index) {
if (func_index < 0 ||
static_cast<uint32_t>(func_index) >=
compiled_module->module()->functions.size())
return {};
std::ostringstream disassembly_os;
std::vector<std::tuple<uint32_t, int, int>> offset_table;
Vector<const uint8_t> func_bytes =
GetFunctionBytes(compiled_module, func_index);
DisallowHeapAllocation no_gc;
if (func_bytes.is_empty()) return {};
AccountingAllocator allocator;
bool ok = PrintAst(
&allocator, FunctionBodyForTesting(func_bytes.start(), func_bytes.end()),
disassembly_os, &offset_table);
CHECK(ok);
PrintWasmText(compiled_module->module(), static_cast<uint32_t>(func_index),
disassembly_os, &offset_table);
return {disassembly_os.str(), std::move(offset_table)};
}
......@@ -2030,6 +2019,7 @@ MaybeHandle<WasmModuleObject> wasm::CreateModuleObjectFromBytes(
thrower->CompileFailed("Wasm decoding failed", result);
return nothing;
}
// The {module_wrapper} will take ownership of the {WasmModule} object,
// and it will be destroyed when the GC reclaims the wrapper object.
Handle<WasmModuleWrapper> module_wrapper =
......
......@@ -390,6 +390,7 @@ Handle<Script> GetScript(Handle<JSObject> instance);
// Returns the disassembly string and a list of <byte_offset, line, column>
// entries, mapping wasm byte offsets to line and column in the disassembly.
// The list is guaranteed to be ordered by the byte_offset.
// Returns an empty string and empty vector if the function index is invalid.
std::pair<std::string, std::vector<std::tuple<uint32_t, int, int>>>
DisassembleFunction(Handle<WasmCompiledModule> compiled_module, int func_index);
......
// Copyright 2016 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-text.h"
#include "src/ostreams.h"
#include "src/vector.h"
#include "src/wasm/ast-decoder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone/zone.h"
using namespace v8::internal;
using namespace v8::internal::wasm;
namespace {
const char *GetOpName(WasmOpcode opcode) {
#define CASE_OP(name, str) \
case kExpr##name: \
return str;
#define CASE_I32_OP(name, str) CASE_OP(I32##name, "i32." str)
#define CASE_I64_OP(name, str) CASE_OP(I64##name, "i64." str)
#define CASE_F32_OP(name, str) CASE_OP(F32##name, "f32." str)
#define CASE_F64_OP(name, str) CASE_OP(F64##name, "f64." str)
#define CASE_INT_OP(name, str) CASE_I32_OP(name, str) CASE_I64_OP(name, str)
#define CASE_FLOAT_OP(name, str) CASE_F32_OP(name, str) CASE_F64_OP(name, str)
#define CASE_ALL_OP(name, str) CASE_FLOAT_OP(name, str) CASE_INT_OP(name, str)
#define CASE_SIGN_OP(TYPE, name, str) \
CASE_##TYPE##_OP(name##S, str "_s") CASE_##TYPE##_OP(name##U, str "_u")
#define CASE_ALL_SIGN_OP(name, str) \
CASE_FLOAT_OP(name, str) CASE_SIGN_OP(INT, name, str)
#define CASE_CONVERT_OP(name, RES, SRC, src_suffix, str) \
CASE_##RES##_OP(U##name##SRC, str "_u/" src_suffix) \
CASE_##RES##_OP(S##name##SRC, str "_s/" src_suffix)
switch (opcode) {
CASE_INT_OP(Eqz, "eqz")
CASE_ALL_OP(Eq, "eq")
CASE_ALL_OP(Ne, "ne")
CASE_ALL_OP(Add, "add")
CASE_ALL_OP(Sub, "sub")
CASE_ALL_OP(Mul, "mul")
CASE_ALL_SIGN_OP(Lt, "lt")
CASE_ALL_SIGN_OP(Gt, "gt")
CASE_ALL_SIGN_OP(Le, "le")
CASE_ALL_SIGN_OP(Ge, "ge")
CASE_INT_OP(Clz, "clz")
CASE_INT_OP(Ctz, "ctz")
CASE_INT_OP(Popcnt, "popcnt")
CASE_ALL_SIGN_OP(Div, "div")
CASE_SIGN_OP(INT, Rem, "rem")
CASE_INT_OP(And, "and")
CASE_INT_OP(Ior, "or")
CASE_INT_OP(Xor, "xor")
CASE_INT_OP(Shl, "shl")
CASE_SIGN_OP(INT, Shr, "shr")
CASE_INT_OP(Rol, "rol")
CASE_INT_OP(Ror, "ror")
CASE_FLOAT_OP(Abs, "abs")
CASE_FLOAT_OP(Neg, "neg")
CASE_FLOAT_OP(Ceil, "ceil")
CASE_FLOAT_OP(Floor, "floor")
CASE_FLOAT_OP(Trunc, "trunc")
CASE_FLOAT_OP(NearestInt, "nearest")
CASE_FLOAT_OP(Sqrt, "sqrt")
CASE_FLOAT_OP(Min, "min")
CASE_FLOAT_OP(Max, "max")
CASE_FLOAT_OP(CopySign, "copysign")
CASE_I32_OP(ConvertI64, "wrap/i64")
CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc")
CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc")
CASE_CONVERT_OP(Convert, I64, I32, "i32", "extend")
CASE_CONVERT_OP(Convert, F32, I32, "i32", "convert")
CASE_CONVERT_OP(Convert, F32, I64, "i64", "convert")
CASE_F32_OP(ConvertF64, "demote/f64")
CASE_CONVERT_OP(Convert, F64, I32, "i32", "convert")
CASE_CONVERT_OP(Convert, F64, I64, "i64", "convert")
CASE_F64_OP(ConvertF32, "promote/f32")
CASE_I32_OP(ReinterpretF32, "reinterpret/f32")
CASE_I64_OP(ReinterpretF64, "reinterpret/f64")
CASE_F32_OP(ReinterpretI32, "reinterpret/i32")
CASE_F64_OP(ReinterpretI64, "reinterpret/i64")
CASE_OP(Unreachable, "unreachable")
CASE_OP(Nop, "nop")
CASE_OP(Return, "return")
CASE_OP(MemorySize, "current_memory")
CASE_OP(GrowMemory, "grow_memory")
CASE_OP(Loop, "loop")
CASE_OP(If, "if")
CASE_OP(Block, "block")
CASE_OP(Try, "try")
CASE_OP(Throw, "throw")
CASE_OP(Catch, "catch")
CASE_OP(Drop, "drop")
CASE_ALL_OP(LoadMem, "load")
CASE_SIGN_OP(INT, LoadMem8, "load8")
CASE_SIGN_OP(INT, LoadMem16, "load16")
CASE_SIGN_OP(I64, LoadMem32, "load32")
CASE_ALL_OP(StoreMem, "store")
CASE_INT_OP(StoreMem8, "store8")
CASE_INT_OP(StoreMem16, "store16")
CASE_I64_OP(StoreMem32, "store32")
CASE_OP(SetLocal, "set_local")
CASE_OP(GetLocal, "get_local")
CASE_OP(TeeLocal, "tee_local")
CASE_OP(GetGlobal, "get_global")
CASE_OP(SetGlobal, "set_global")
CASE_OP(Br, "br")
CASE_OP(BrIf, "br_if")
default:
UNREACHABLE();
return "";
}
}
bool IsValidFunctionName(const Vector<const char> &name) {
if (name.is_empty()) return false;
const char *special_chars = "_.+-*/\\^~=<>!?@#$%&|:'`";
for (char c : name) {
bool valid_char = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || strchr(special_chars, c);
if (!valid_char) return false;
}
return true;
}
} // namespace
void wasm::PrintWasmText(
const WasmModule *module, uint32_t func_index, std::ostream &os,
std::vector<std::tuple<uint32_t, int, int>> *offset_table) {
DCHECK_NOT_NULL(module);
DCHECK_GT(module->functions.size(), func_index);
const WasmFunction *fun = &module->functions[func_index];
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
int line_nr = 0;
int control_depth = 0;
// Print the function signature.
os << "func";
Vector<const char> fun_name(
reinterpret_cast<const char *>(module->module_start + fun->name_offset),
fun->name_length);
if (IsValidFunctionName(fun_name)) {
os << " $";
os.write(fun_name.start(), fun_name.length());
}
size_t param_count = fun->sig->parameter_count();
if (param_count) {
os << " (param";
for (size_t i = 0; i < param_count; ++i)
os << ' ' << WasmOpcodes::TypeName(fun->sig->GetParam(i));
os << ')';
}
size_t return_count = fun->sig->return_count();
if (return_count) {
os << " (result";
for (size_t i = 0; i < return_count; ++i)
os << ' ' << WasmOpcodes::TypeName(fun->sig->GetReturn(i));
os << ')';
}
os << "\n";
++line_nr;
// Print the local declarations.
AstLocalDecls decls(&zone);
const byte *code_start = module->module_start + fun->code_start_offset;
const byte *code_end = module->module_start + fun->code_end_offset;
BytecodeIterator i(code_start, code_end, &decls);
DCHECK_LT(code_start, i.pc());
if (!decls.local_types.empty()) {
os << "(local";
for (auto p : decls.local_types) {
for (unsigned i = 0; i < p.second; ++i)
os << ' ' << WasmOpcodes::TypeName(p.first);
}
os << ")\n";
++line_nr;
}
for (; i.has_next(); i.next()) {
WasmOpcode opcode = i.current();
if (opcode == kExprElse || opcode == kExprEnd) --control_depth;
DCHECK_LE(0, control_depth);
const int kMaxIndentation = 64;
int indentation = std::min(kMaxIndentation, 2 * control_depth);
if (offset_table) {
offset_table->push_back(
std::make_tuple(i.pc_offset(), line_nr, indentation));
}
// 64 whitespaces
const char padding[kMaxIndentation + 1] =
" ";
os.write(padding, indentation);
switch (opcode) {
case kExprLoop:
case kExprIf:
case kExprBlock:
case kExprTry: {
BlockTypeOperand operand(&i, i.pc());
os << GetOpName(opcode);
for (unsigned i = 0; i < operand.arity; i++) {
os << " " << WasmOpcodes::TypeName(operand.read_entry(i));
}
control_depth++;
break;
}
case kExprBr:
case kExprBrIf: {
BreakDepthOperand operand(&i, i.pc());
os << GetOpName(opcode) << ' ' << operand.depth;
break;
}
case kExprElse:
os << "else";
control_depth++;
break;
case kExprEnd:
os << "end";
break;
case kExprBrTable: {
BranchTableOperand operand(&i, i.pc());
BranchTableIterator iterator(&i, operand);
os << "br_table";
while (iterator.has_next()) os << ' ' << iterator.next();
break;
}
case kExprCallIndirect: {
CallIndirectOperand operand(&i, i.pc());
DCHECK_EQ(0U, operand.table_index);
os << "call_indirect " << operand.index;
break;
}
case kExprCallFunction: {
CallFunctionOperand operand(&i, i.pc());
os << "call " << operand.index;
break;
}
case kExprGetLocal:
case kExprSetLocal:
case kExprTeeLocal:
case kExprCatch: {
LocalIndexOperand operand(&i, i.pc());
os << GetOpName(opcode) << ' ' << operand.index;
break;
}
case kExprGetGlobal:
case kExprSetGlobal: {
GlobalIndexOperand operand(&i, i.pc());
os << GetOpName(opcode) << ' ' << operand.index;
break;
}
#define CASE_CONST(type, str, cast_type) \
case kExpr##type##Const: { \
Imm##type##Operand operand(&i, i.pc()); \
os << #str ".const " << static_cast<cast_type>(operand.value); \
break; \
}
CASE_CONST(I8, i8, int32_t)
CASE_CONST(I32, i32, int32_t)
CASE_CONST(I64, i64, int64_t)
CASE_CONST(F32, f32, float)
CASE_CONST(F64, f64, double)
#define CASE_OPCODE(opcode, _, __) case kExpr##opcode:
FOREACH_LOAD_MEM_OPCODE(CASE_OPCODE)
FOREACH_STORE_MEM_OPCODE(CASE_OPCODE) {
MemoryAccessOperand operand(&i, i.pc(), kMaxUInt32);
os << GetOpName(opcode) << " offset=" << operand.offset
<< " align=" << (1ULL << operand.alignment);
break;
}
FOREACH_SIMPLE_OPCODE(CASE_OPCODE)
case kExprUnreachable:
case kExprNop:
case kExprReturn:
case kExprMemorySize:
case kExprGrowMemory:
case kExprDrop:
case kExprThrow:
os << GetOpName(opcode);
break;
// This group is just printed by their internal opcode name, as they
// should never be shown to end-users.
FOREACH_ASMJS_COMPAT_OPCODE(CASE_OPCODE)
// TODO(wasm): Add correct printing for SIMD and atomic opcodes once
// they are publicly available.
FOREACH_SIMD_0_OPERAND_OPCODE(CASE_OPCODE)
FOREACH_SIMD_1_OPERAND_OPCODE(CASE_OPCODE)
FOREACH_ATOMIC_OPCODE(CASE_OPCODE)
os << WasmOpcodes::OpcodeName(opcode);
break;
default:
UNREACHABLE();
break;
}
os << '\n';
++line_nr;
}
DCHECK_EQ(0, control_depth);
DCHECK(i.ok());
}
// Copyright 2016 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_WASM_S_EXPR_H_
#define V8_WASM_S_EXPR_H_
#include <cstdint>
#include <ostream>
#include <tuple>
#include <vector>
namespace v8 {
namespace internal {
namespace wasm {
// Forward declaration.
struct WasmModule;
// Generate disassembly according to official text format.
// Output disassembly to the given output stream, and optionally return an
// offset table of <byte offset, line, column> via the given pointer.
void PrintWasmText(const WasmModule *module, uint32_t func_index,
std::ostream &os,
std::vector<std::tuple<uint32_t, int, int>> *offset_table);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_S_EXPR_H_
......@@ -2,8 +2,8 @@ Check that inspector gets disassembled wasm code
Paused on debugger!
Number of frames: 5
[0] debugger;
[1] kExprCallFunction, 0x00, // function #0
[2] kExprCallIndirect, 0x02, 0x00, // sig #2
[1] call 0
[2] call_indirect 2
[3] instance.exports.main();
[4] testFunction(module_bytes)
Finished.
......@@ -2,8 +2,8 @@ Running testFunction with generated WASM bytes...
Paused on 'debugger;'
Number of frames: 5
- [0] {"functionName":"call_debugger","function_lineNumber":1,"function_columnNumber":24,"lineNumber":2,"columnNumber":4}
- [1] {"functionName":"call_func","lineNumber":3,"columnNumber":0}
- [2] {"functionName":"main","lineNumber":4,"columnNumber":2}
- [1] {"functionName":"call_func","lineNumber":1,"columnNumber":0}
- [2] {"functionName":"main","lineNumber":2,"columnNumber":2}
- [3] {"functionName":"testFunction","function_lineNumber":0,"function_columnNumber":21,"lineNumber":14,"columnNumber":19}
- [4] {"functionName":"","function_lineNumber":0,"function_columnNumber":0,"lineNumber":0,"columnNumber":0}
Getting v8-generated stack trace...
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment