Commit d249efd7 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Disassemble wasm code from script

This stores the wasm object and the function index in the script, and
adds functions to get the disassembled wasm code as well as the offset
table mapping from byte position to line and column in the disassembly
solely from the script.
This will be used to show "ui source code" in DevTools, and map raw
locations from the stack trace into this code view.

R=yangguo@chromium.org, ahaas@chromium.org, titzer@chromium.org
BUG=chromium:613110

patch from issue 2063013004 at patchset 80001 (http://crrev.com/2063013004#ps80001)

Review-Url: https://codereview.chromium.org/2105303002
Cr-Commit-Position: refs/heads/master@{#37430}
parent f0a430e5
......@@ -66,7 +66,8 @@ Debug.StepAction = { StepOut: 0,
// The different types of scripts matching enum ScriptType in objects.h.
Debug.ScriptType = { Native: 0,
Extension: 1,
Normal: 2 };
Normal: 2,
Wasm: 3};
// The different types of script compilations matching enum
// Script::CompilationType in objects.h.
......@@ -487,7 +488,7 @@ Debug.findScript = function(func_or_script_name) {
if (IS_FUNCTION(func_or_script_name)) {
return %FunctionGetScript(func_or_script_name);
} else if (IS_REGEXP(func_or_script_name)) {
var scripts = Debug.scripts();
var scripts = this.scripts();
var last_result = null;
var result_count = 0;
for (var i in scripts) {
......@@ -633,15 +634,12 @@ Debug.setBreakPointByScriptIdAndPosition = function(script_id, position,
if (!enabled) {
break_point.disable();
}
var scripts = this.scripts();
var position_alignment = IS_UNDEFINED(opt_position_alignment)
? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
for (var i = 0; i < scripts.length; i++) {
if (script_id == scripts[i].id) {
break_point.actual_position = %SetScriptBreakPoint(scripts[i], position,
position_alignment, break_point);
break;
}
var script = scriptById(script_id);
if (script) {
var position_alignment = IS_UNDEFINED(opt_position_alignment)
? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
break_point.actual_position = %SetScriptBreakPoint(script, position,
position_alignment, break_point);
}
return break_point;
};
......@@ -858,10 +856,31 @@ Debug.scripts = function() {
};
// Get a specific script currently loaded. This is based on scanning the heap.
// TODO(clemensh): Create a runtime function for this.
function scriptById(scriptId) {
var scripts = Debug.scripts();
for (var script of scripts) {
if (script.id == scriptId) return script;
}
return UNDEFINED;
};
Debug.debuggerFlags = function() {
return debugger_flags;
};
Debug.getWasmFunctionOffsetTable = function(scriptId) {
var script = scriptById(scriptId);
return script ? %GetWasmFunctionOffsetTable(script) : UNDEFINED;
}
Debug.disassembleWasmFunction = function(scriptId) {
var script = scriptById(scriptId);
return script ? %DisassembleWasmFunction(script) : UNDEFINED;
}
Debug.MakeMirror = MakeMirror;
function MakeExecutionState(break_id) {
......@@ -2155,7 +2174,7 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
}
// Collect all scripts in the heap.
var scripts = %DebugGetLoadedScripts();
var scripts = Debug.scripts();
response.body = [];
......@@ -2205,14 +2224,7 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(
var script_id = request.arguments.script_id;
var preview_only = !!request.arguments.preview_only;
var scripts = %DebugGetLoadedScripts();
var the_script = null;
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].id == script_id) {
the_script = scripts[i];
}
}
var the_script = scriptById(script_id);
if (!the_script) {
response.failed('Script not found');
return;
......
......@@ -78,25 +78,35 @@ int PropertyDetails::field_width_in_words() const {
int holder::name() const { return READ_INT_FIELD(this, offset); } \
void holder::set_##name(int value) { WRITE_INT_FIELD(this, offset, value); }
#define ACCESSORS(holder, name, type, offset) \
type* holder::name() const { return type::cast(READ_FIELD(this, offset)); } \
void holder::set_##name(type* value, WriteBarrierMode mode) { \
WRITE_FIELD(this, offset, value); \
CONDITIONAL_WRITE_BARRIER(GetHeap(), this, offset, value, mode); \
#define ACCESSORS_CHECKED(holder, name, type, offset, condition) \
type* holder::name() const { \
DCHECK(condition); \
return type::cast(READ_FIELD(this, offset)); \
} \
void holder::set_##name(type* value, WriteBarrierMode mode) { \
DCHECK(condition); \
WRITE_FIELD(this, offset, value); \
CONDITIONAL_WRITE_BARRIER(GetHeap(), this, offset, value, mode); \
}
#define ACCESSORS(holder, name, type, offset) \
ACCESSORS_CHECKED(holder, name, type, offset, true)
// Getter that returns a Smi as an int and writes an int as a Smi.
#define SMI_ACCESSORS(holder, name, offset) \
int holder::name() const { \
Object* value = READ_FIELD(this, offset); \
return Smi::cast(value)->value(); \
} \
void holder::set_##name(int value) { \
WRITE_FIELD(this, offset, Smi::FromInt(value)); \
#define SMI_ACCESSORS_CHECKED(holder, name, offset, condition) \
int holder::name() const { \
DCHECK(condition); \
Object* value = READ_FIELD(this, offset); \
return Smi::cast(value)->value(); \
} \
void holder::set_##name(int value) { \
DCHECK(condition); \
WRITE_FIELD(this, offset, Smi::FromInt(value)); \
}
#define SMI_ACCESSORS(holder, name, offset) \
SMI_ACCESSORS_CHECKED(holder, name, offset, true)
#define SYNCHRONIZED_SMI_ACCESSORS(holder, name, offset) \
int holder::synchronized_##name() const { \
Object* value = ACQUIRE_READ_FIELD(this, offset); \
......@@ -5651,12 +5661,18 @@ ACCESSORS(Script, context_data, Object, kContextOffset)
ACCESSORS(Script, wrapper, HeapObject, kWrapperOffset)
SMI_ACCESSORS(Script, type, kTypeOffset)
ACCESSORS(Script, line_ends, Object, kLineEndsOffset)
ACCESSORS(Script, eval_from_shared, Object, kEvalFromSharedOffset)
SMI_ACCESSORS(Script, eval_from_position, kEvalFromPositionOffset)
ACCESSORS_CHECKED(Script, eval_from_shared, Object, kEvalFromSharedOffset,
this->type() != TYPE_WASM)
SMI_ACCESSORS_CHECKED(Script, eval_from_position, kEvalFromPositionOffset,
this->type() != TYPE_WASM)
ACCESSORS(Script, shared_function_infos, Object, kSharedFunctionInfosOffset)
SMI_ACCESSORS(Script, flags, kFlagsOffset)
ACCESSORS(Script, source_url, Object, kSourceUrlOffset)
ACCESSORS(Script, source_mapping_url, Object, kSourceMappingUrlOffset)
ACCESSORS_CHECKED(Script, wasm_object, JSObject, kEvalFromSharedOffset,
this->type() == TYPE_WASM)
SMI_ACCESSORS_CHECKED(Script, wasm_function_index, kEvalFromPositionOffset,
this->type() == TYPE_WASM)
Script::CompilationType Script::compilation_type() {
return BooleanBit::get(flags(), kCompilationTypeBit) ?
......
......@@ -6557,9 +6557,17 @@ class Script: public Struct {
// [source_url]: sourceURL from magic comment
DECL_ACCESSORS(source_url, Object)
// [source_url]: sourceMappingURL magic comment
// [source_mapping_url]: sourceMappingURL magic comment
DECL_ACCESSORS(source_mapping_url, Object)
// [wasm_object]: the wasm object this script belongs to.
// This must only be called if the type of this script is TYPE_WASM.
DECL_ACCESSORS(wasm_object, JSObject)
// [wasm_function_index]: the wasm function index this script belongs to.
// This must only be called if the type of this script is TYPE_WASM.
DECL_INT_ACCESSORS(wasm_function_index)
// [compilation_type]: how the the script was compiled. Encoded in the
// 'flags' field.
inline CompilationType compilation_type();
......
......@@ -15,6 +15,7 @@
#include "src/interpreter/interpreter.h"
#include "src/isolate-inl.h"
#include "src/runtime/runtime.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
......@@ -1786,5 +1787,35 @@ RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
UNIMPLEMENTED();
return NULL;
}
RUNTIME_FUNCTION(Runtime_GetWasmFunctionOffsetTable) {
DCHECK(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_CHECKED(JSValue, script_val, 0);
RUNTIME_ASSERT(script_val->value()->IsScript());
Handle<Script> script = Handle<Script>(Script::cast(script_val->value()));
Handle<wasm::WasmDebugInfo> debug_info(
wasm::GetDebugInfo(script->wasm_object()), isolate);
Handle<FixedArray> elements = wasm::WasmDebugInfo::GetFunctionOffsetTable(
debug_info, script->wasm_function_index());
return *isolate->factory()->NewJSArrayWithElements(elements);
}
RUNTIME_FUNCTION(Runtime_DisassembleWasmFunction) {
DCHECK(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_CHECKED(JSValue, script_val, 0);
RUNTIME_ASSERT(script_val->value()->IsScript());
Handle<Script> script = Handle<Script>(Script::cast(script_val->value()));
Handle<wasm::WasmDebugInfo> debug_info(
wasm::GetDebugInfo(script->wasm_object()), isolate);
return *wasm::WasmDebugInfo::DisassembleFunction(
debug_info, script->wasm_function_index());
}
} // namespace internal
} // namespace v8
......@@ -191,7 +191,9 @@ namespace internal {
F(DebugPopPromise, 0, 1) \
F(DebugAsyncTaskEvent, 1, 1) \
F(DebugIsActive, 0, 1) \
F(DebugBreakInOptimizedCode, 0, 1)
F(DebugBreakInOptimizedCode, 0, 1) \
F(GetWasmFunctionOffsetTable, 1, 1) \
F(DisassembleWasmFunction, 1, 1)
#define FOR_EACH_INTRINSIC_FORIN(F) \
F(ForInDone, 2, 1) \
......
......@@ -1521,6 +1521,7 @@ bool PrintAst(base::AccountingAllocator* allocator, const FunctionBody& body,
os << " " << count << " " << WasmOpcodes::TypeName(type);
}
os << std::endl;
++line_nr;
for (const byte* locals = body.start; locals < pc; locals++) {
os << (locals == body.start ? "0x" : " 0x") << AsHex(*locals, 2) << ",";
......
......@@ -120,11 +120,6 @@ JSObject *WasmDebugInfo::wasm_object() {
return JSObject::cast(get(kWasmDebugInfoWasmObj));
}
bool WasmDebugInfo::SetBreakPoint(int byte_offset) {
// TODO(clemensh): Implement this.
return false;
}
Script *WasmDebugInfo::GetFunctionScript(Handle<WasmDebugInfo> debug_info,
int func_index) {
Isolate *isolate = debug_info->GetIsolate();
......@@ -149,6 +144,16 @@ Script *WasmDebugInfo::GetFunctionScript(Handle<WasmDebugInfo> debug_info,
scripts->set(func_index, *script);
script->set_type(Script::TYPE_WASM);
script->set_wasm_object(debug_info->wasm_object());
script->set_wasm_function_index(func_index);
int hash = 0;
debug_info->get(kWasmDebugInfoWasmBytesHash)->ToInt32(&hash);
char buffer[32];
SNPrintF(ArrayVector(buffer), "wasm://%08x/%d", hash, func_index);
Handle<String> source_url =
isolate->factory()->NewStringFromAsciiChecked(buffer, TENURED);
script->set_source_url(*source_url);
int func_bytes_len =
GetFunctionOffsetAndLength(debug_info, func_index).second;
......@@ -157,7 +162,7 @@ Script *WasmDebugInfo::GetFunctionScript(Handle<WasmDebugInfo> debug_info,
line_ends->set_map(isolate->heap()->fixed_cow_array_map());
script->set_line_ends(*line_ends);
// TODO(clemensh): Register this new script at the debugger.
isolate->debug()->OnAfterCompile(script);
return *script;
}
......
// 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.
// Flags: --expose-wasm --expose-debug-as debug
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
Debug = debug.Debug
// Initialized in setup().
var exception;
var break_count;
var num_wasm_scripts;
var module;
function listener(event, exec_state, event_data, data) {
try {
if (event == Debug.DebugEvent.Break) {
++break_count;
// Request frame details. This should trigger creation of the Script
// objects for all frames on the stack.
var num_frames = exec_state.frameCount();
for (var i = 0; i < num_frames; ++i) {
var frame = exec_state.frame(i);
var details = frame.details();
var script = details.script();
if (script.type == Debug.ScriptType.Wasm) {
var pos = frame.sourcePosition();
var name = script.nameOrSourceURL();
var disassembly = Debug.disassembleWasmFunction(script.id);
var offset_table = Debug.getWasmFunctionOffsetTable(script.id);
assertEquals(0, offset_table.length % 3);
var lineNr = null;
var columnNr = null;
for (var p = 0; p < offset_table.length; p += 3) {
if (offset_table[p] != pos) continue;
lineNr = offset_table[p+1];
columnNr = offset_table[p+2];
}
assertNotNull(lineNr, "position should occur in offset table");
assertNotNull(columnNr, "position should occur in offset table");
var line = disassembly.split("\n")[lineNr];
assertTrue(!!line, "line number must occur in disassembly");
assertTrue(line.length > columnNr, "column number must be valid");
var expected_string;
if (name.endsWith("/0")) {
// Function 0 calls the imported function.
expected_string = "kExprCallImport,";
} else if (name.endsWith("/1")) {
// Function 1 calls function 0.
expected_string = "kExprCallFunction,";
} else {
assertTrue(false, "Unexpected wasm script: " + name);
}
assertTrue(line.substr(columnNr).startsWith(expected_string),
"offset " + columnNr + " should start with '" + expected_string
+ "': " + line);
}
}
} else if (event == Debug.DebugEvent.AfterCompile) {
var script = event_data.script();
if (script.scriptType() == Debug.ScriptType.Wasm) {
++num_wasm_scripts;
}
}
} catch (e) {
print("exception: " + e);
exception = e;
}
};
var builder = new WasmModuleBuilder();
builder.addImport("func", kSig_v_v);
builder.addFunction("call_import", kSig_v_v)
.addBody([kExprCallImport, kArity0, 0])
.exportFunc();
// Add a bit of unneccessary code to increase the byte offset.
builder.addFunction("call_call_import", kSig_v_v)
.addLocals({i32_count: 2})
.addBody([
kExprI32Const, 27, kExprSetLocal, 0,
kExprI32Const, (-7 & 0x7f), kExprSetLocal, 1,
kExprGetLocal, 0, kExprGetLocal, 1, kExprI32Add, kExprI64UConvertI32,
kExprI64Const, 0,
kExprI64Ne, kExprIf,
kExprCallFunction, kArity0, 0,
kExprEnd
])
.exportFunc();
function call_debugger() {
debugger;
}
function setup() {
module = builder.instantiate({func: call_debugger});
exception = null;
break_count = 0;
num_wasm_scripts = 0;
}
(function testRegisteredWasmScripts1() {
setup();
Debug.setListener(listener);
// Initially 0 scripts.
assertEquals(0, num_wasm_scripts);
// Call the "call_import" function -> 1 script.
module.exports.call_import();
assertEquals(1, num_wasm_scripts);
// Call "call_import" again -> still just 1 script.
module.exports.call_import();
assertEquals(1, num_wasm_scripts);
// Call "call_call_import" -> now 2 scripts.
module.exports.call_call_import();
assertEquals(2, num_wasm_scripts);
Debug.setListener(null);
assertEquals(3, break_count);
if (exception) throw exception;
})();
(function testRegisteredWasmScripts2() {
setup();
Debug.setListener(listener);
// Initially 0 scripts.
assertEquals(0, num_wasm_scripts);
// Call the "call_call_import" function -> 2 scripts should be registered.
module.exports.call_call_import();
assertEquals(2, num_wasm_scripts);
Debug.setListener(null);
assertEquals(1, break_count);
if (exception) throw exception;
})();
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