Commit a1e04ef5 authored by clemensh's avatar clemensh Committed by Commit bot

[wasm] Add tests for breakpoints

Test that setting breakpoints works for wasm, and that they are hit
correctly.
This basically tests all the layers involved: Compiling and running
wasm interpreter entries, passing arguments to the interpreter, storing
break point infos in wasm objects, getting the right BreakLocation from
wasm frames, and getting stack information from interpreted frames.

BUG=v8:5822
R=titzer@chromium.org, yangguo@chromium.org

Review-Url: https://codereview.chromium.org/2629883002
Cr-Commit-Position: refs/heads/master@{#42560}
parent fba90473
......@@ -38,16 +38,15 @@ class InterpreterHandle {
: instance_(debug_info->wasm_instance()->compiled_module()->module()),
interpreter_(GetBytesEnv(&instance_, debug_info), &allocator_),
isolate_(isolate) {
Handle<JSArrayBuffer> mem_buffer =
handle(debug_info->wasm_instance()->memory_buffer(), isolate);
if (mem_buffer->IsUndefined(isolate)) {
DCHECK_EQ(0, instance_.module->min_mem_pages);
instance_.mem_start = nullptr;
instance_.mem_size = 0;
} else {
if (debug_info->wasm_instance()->has_memory_buffer()) {
JSArrayBuffer* mem_buffer = debug_info->wasm_instance()->memory_buffer();
instance_.mem_start =
reinterpret_cast<byte*>(mem_buffer->backing_store());
CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size));
} else {
DCHECK_EQ(0, instance_.module->min_mem_pages);
instance_.mem_start = nullptr;
instance_.mem_size = 0;
}
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/debug/debug-interface.h"
#include "src/property-descriptor.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-objects.h"
......@@ -50,6 +51,93 @@ void CheckLocationsFail(WasmCompiledModule *compiled_module,
CHECK(!success);
}
class BreakHandler {
public:
explicit BreakHandler(Isolate* isolate) : isolate_(isolate) {
current_handler = this;
isolate->debug()->SetMessageHandler(&HandleMessage);
}
~BreakHandler() {
CHECK_EQ(this, current_handler);
current_handler = nullptr;
isolate_->debug()->SetMessageHandler(nullptr);
}
int count() const { return count_; }
private:
Isolate* isolate_;
int count_ = 0;
static BreakHandler* current_handler;
static void HandleMessage(const v8::Debug::Message& message) {
// Ignore responses.
if (!message.IsEvent()) return;
// Ignore everything except break events.
if (message.GetEvent() != v8::DebugEvent::Break) return;
printf("break!\n");
CHECK_NOT_NULL(current_handler);
current_handler->count_ += 1;
// Don't run into an endless loop.
CHECK_GT(100, current_handler->count_);
const char command[] = "{\"type\":\"request\", \"command\":\"continue\"}";
uint16_t command_u16[arraysize(command) - 1];
for (unsigned i = 0; i < arraysize(command) - 1; ++i) {
command_u16[i] = command[i];
}
current_handler->isolate_->debug()->EnqueueCommandMessage(
ArrayVector(command_u16), message.GetClientData());
}
};
// static
BreakHandler* BreakHandler::current_handler = nullptr;
Handle<JSObject> MakeFakeBreakpoint(Isolate* isolate, int position) {
Handle<JSObject> obj =
isolate->factory()->NewJSObject(isolate->object_function());
// Generate an "isTriggered" method that always returns true.
// This can/must be refactored once we remove remaining JS parts from the
// debugger (bug 5530).
Handle<String> source = isolate->factory()->NewStringFromStaticChars("true");
Handle<Context> context(isolate->context(), isolate);
Handle<JSFunction> triggered_fun =
Compiler::GetFunctionFromString(context, source, NO_PARSE_RESTRICTION)
.ToHandleChecked();
PropertyDescriptor desc;
desc.set_value(triggered_fun);
Handle<String> name =
isolate->factory()->InternalizeUtf8String(CStrVector("isTriggered"));
CHECK(
JSObject::DefineOwnProperty(isolate, obj, name, &desc, Object::DONT_THROW)
.FromMaybe(false));
return obj;
}
void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
int expected_set_byte_offset = -1) {
int func_offset =
runner.module().module->functions[function_index].code_start_offset;
int code_offset = func_offset + byte_offset;
if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
Handle<WasmInstanceObject> instance = runner.module().instance_object();
Handle<WasmCompiledModule> compiled_module(instance->compiled_module());
Handle<JSObject> fake_breakpoint_object =
MakeFakeBreakpoint(runner.main_isolate(), code_offset);
CHECK(WasmCompiledModule::SetBreakPoint(compiled_module, &code_offset,
fake_breakpoint_object));
int set_byte_offset = code_offset - func_offset;
CHECK_EQ(expected_set_byte_offset, set_byte_offset);
// Also set breakpoint on the debug info of the instance directly, since the
// instance chain is not setup properly in tests.
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
WasmDebugInfo::SetBreakpoint(debug_info, function_index, set_byte_offset);
}
} // namespace
TEST(CollectPossibleBreakpoints) {
......@@ -59,12 +147,44 @@ TEST(CollectPossibleBreakpoints) {
Handle<WasmInstanceObject> instance = runner.module().instance_object();
std::vector<debug::Location> locations;
// Check all locations for function 0.
CheckLocations(instance->compiled_module(), {0, 0}, {1, 0},
{{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
// Check a range ending at an instruction.
CheckLocations(instance->compiled_module(), {0, 2}, {0, 4}, {{0, 2}});
// Check a range ending one behind an instruction.
CheckLocations(instance->compiled_module(), {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
// Check a range starting at an instruction.
CheckLocations(instance->compiled_module(), {0, 7}, {0, 8}, {{0, 7}});
// Check from an instruction to beginning of next function.
CheckLocations(instance->compiled_module(), {0, 7}, {1, 0}, {{0, 7}});
// Check from end of one function (no valid instruction position) to beginning
// of next function. Must be empty, but not fail.
CheckLocations(instance->compiled_module(), {0, 8}, {1, 0}, {});
// Check from one after the end of the function. Must fail.
CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0});
}
TEST(TestSimpleBreak) {
WasmRunner<int> runner(kExecuteCompiled);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
Handle<JSFunction> main_fun_wrapper =
runner.module().WrapCode(runner.function_index());
SetBreakpoint(runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate);
CHECK_EQ(0, count_breaks.count());
Handle<Object> global(isolate->context()->global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK(!retval.is_null());
int result;
CHECK(retval.ToHandleChecked()->ToInt32(&result));
CHECK_EQ(14, result);
CHECK_EQ(1, count_breaks.count());
}
......@@ -224,14 +224,26 @@ class TestingModule : public ModuleEnv {
Handle<JSFunction> WrapCode(uint32_t index) {
// Wrap the code so it can be called as a JS function.
Handle<WasmInstanceObject> instance_obj(0, isolate_);
Handle<Code> code = instance->function_code[index];
Handle<Code> ret_code =
compiler::CompileJSToWasmWrapper(isolate_, &module_, code, index);
Handle<JSFunction> ret = WasmExportedFunction::New(
isolate_, instance_obj, MaybeHandle<String>(), static_cast<int>(index),
isolate_, instance_object(), MaybeHandle<String>(),
static_cast<int>(index),
static_cast<int>(this->module->functions[index].sig->parameter_count()),
ret_code);
// Add weak reference to exported functions.
Handle<WasmCompiledModule> compiled_module(
instance_object()->compiled_module(), isolate_);
Handle<FixedArray> old_arr = compiled_module->weak_exported_functions();
Handle<FixedArray> new_arr =
isolate_->factory()->NewFixedArray(old_arr->length() + 1);
old_arr->CopyTo(0, *new_arr, 0, old_arr->length());
Handle<WeakCell> weak_fn = isolate_->factory()->NewWeakCell(ret);
new_arr->set(old_arr->length(), *weak_fn);
compiled_module->set_weak_exported_functions(new_arr);
return ret;
}
......@@ -334,6 +346,10 @@ class TestingModule : public ModuleEnv {
// If tests need more (correct) information, add it later.
compiled_module->set_min_mem_pages(0);
compiled_module->set_max_mem_pages(Smi::kMaxValue);
Handle<FixedArray> code_table = isolate_->factory()->NewFixedArray(0);
compiled_module->set_code_table(code_table);
Handle<FixedArray> weak_exported = isolate_->factory()->NewFixedArray(0);
compiled_module->set_weak_exported_functions(weak_exported);
DCHECK(WasmCompiledModule::IsWasmCompiledModule(*compiled_module));
return WasmInstanceObject::New(isolate_, compiled_module);
}
......@@ -541,6 +557,13 @@ class WasmFunctionCompiler : private GraphAndBuilders {
&source_position_table_, start, end);
Handle<Code> code = Compile();
testing_module_->SetFunctionCode(function_index(), code);
// Add to code table.
Handle<WasmCompiledModule> compiled_module(
testing_module_->instance_object()->compiled_module(), isolate());
Handle<FixedArray> code_table = compiled_module->code_table();
code_table = FixedArray::SetAndGrow(code_table, function_index(), code);
compiled_module->set_code_table(code_table);
}
byte AllocateLocal(ValueType type) {
......@@ -659,6 +682,7 @@ class WasmRunnerBase : public HandleAndZoneScope {
return functions_[0]->AllocateLocal(type);
}
uint32_t function_index() { return functions_[0]->function_index(); }
WasmFunction* function() { return functions_[0]->function_; }
WasmInterpreter* interpreter() { return functions_[0]->interpreter_; }
bool possible_nondeterminism() { return possible_nondeterminism_; }
......
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