6572b5622e
These byte pointers (module_start and module_end) were only valid during decoding. During instantiation or execution, they can get invalidated by garbage collection. This CL removes them from the WasmModule struct, and introduces a new ModuleStorage struct as interface to the wasm wire bytes. Since the storage is often needed together with the ModuleEnv, a new ModuleStorageEnv struct holds both a ModuleEnv and a ModuleStorage. The pointers in the ModuleStorage should never escape the live range of this struct, as they might point into a SeqOneByteString or ArrayBuffer. Therefore, the WasmInterpreter needs to create its own copy of the whole module. Runtime functions that previously used the raw pointers in WasmModule (leading to memory errors) now have to use the SeqOneByteString in the WasmCompiledModule. R=titzer@chromium.org BUG=chromium:669518 Review-Url: https://codereview.chromium.org/2540133002 Cr-Commit-Position: refs/heads/master@{#41388}
216 lines
8.1 KiB
C++
216 lines
8.1 KiB
C++
// 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 "test/common/wasm/wasm-module-runner.h"
|
|
|
|
#include "src/handles.h"
|
|
#include "src/isolate.h"
|
|
#include "src/objects.h"
|
|
#include "src/property-descriptor.h"
|
|
#include "src/wasm/module-decoder.h"
|
|
#include "src/wasm/wasm-interpreter.h"
|
|
#include "src/wasm/wasm-js.h"
|
|
#include "src/wasm/wasm-module.h"
|
|
#include "src/wasm/wasm-objects.h"
|
|
#include "src/wasm/wasm-result.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
namespace testing {
|
|
|
|
uint32_t GetMinModuleMemSize(const WasmModule* module) {
|
|
return WasmModule::kPageSize * module->min_mem_pages;
|
|
}
|
|
|
|
const WasmModule* DecodeWasmModuleForTesting(
|
|
Isolate* isolate, ErrorThrower* thrower, const byte* module_start,
|
|
const byte* module_end, ModuleOrigin origin, bool verify_functions) {
|
|
// Decode the module, but don't verify function bodies, since we'll
|
|
// be compiling them anyway.
|
|
ModuleResult decoding_result = DecodeWasmModule(
|
|
isolate, module_start, module_end, verify_functions, origin);
|
|
|
|
if (decoding_result.failed()) {
|
|
// Module verification failed. throw.
|
|
thrower->CompileError("WASM.compileRun() failed: %s",
|
|
decoding_result.error_msg.get());
|
|
}
|
|
|
|
if (thrower->error()) {
|
|
if (decoding_result.val) delete decoding_result.val;
|
|
return nullptr;
|
|
}
|
|
return decoding_result.val;
|
|
}
|
|
|
|
const Handle<WasmInstanceObject> InstantiateModuleForTesting(
|
|
Isolate* isolate, ErrorThrower* thrower, const WasmModule* module,
|
|
const ModuleWireBytes& wire_bytes) {
|
|
DCHECK_NOT_NULL(module);
|
|
if (module->import_table.size() > 0) {
|
|
thrower->CompileError("Not supported: module has imports.");
|
|
}
|
|
|
|
if (thrower->error()) return Handle<WasmInstanceObject>::null();
|
|
|
|
// Although we decoded the module for some pre-validation, run the bytes
|
|
// again through the normal pipeline.
|
|
// TODO(wasm): Use {module} instead of decoding the module bytes again.
|
|
MaybeHandle<WasmModuleObject> module_object = CreateModuleObjectFromBytes(
|
|
isolate, wire_bytes.module_bytes.start(), wire_bytes.module_bytes.end(),
|
|
thrower, ModuleOrigin::kWasmOrigin, Handle<Script>::null(), nullptr,
|
|
nullptr);
|
|
if (module_object.is_null()) {
|
|
thrower->CompileError("Module pre-validation failed.");
|
|
return Handle<WasmInstanceObject>::null();
|
|
}
|
|
MaybeHandle<WasmInstanceObject> maybe_instance = WasmModule::Instantiate(
|
|
isolate, thrower, module_object.ToHandleChecked(),
|
|
Handle<JSReceiver>::null(), Handle<JSArrayBuffer>::null());
|
|
Handle<WasmInstanceObject> instance;
|
|
if (!maybe_instance.ToHandle(&instance)) {
|
|
return Handle<WasmInstanceObject>::null();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
const Handle<WasmInstanceObject> CompileInstantiateWasmModuleForTesting(
|
|
Isolate* isolate, ErrorThrower* thrower, const byte* module_start,
|
|
const byte* module_end, ModuleOrigin origin) {
|
|
std::unique_ptr<const WasmModule> module(DecodeWasmModuleForTesting(
|
|
isolate, thrower, module_start, module_end, origin));
|
|
|
|
if (module == nullptr) {
|
|
thrower->CompileError("Wasm module decoding failed");
|
|
return Handle<WasmInstanceObject>::null();
|
|
}
|
|
return InstantiateModuleForTesting(isolate, thrower, module.get(),
|
|
ModuleWireBytes(module_start, module_end));
|
|
}
|
|
|
|
int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance,
|
|
int argc, Handle<Object> argv[],
|
|
ModuleOrigin origin) {
|
|
ErrorThrower thrower(isolate, "RunWasmModule");
|
|
const char* f_name = origin == ModuleOrigin::kAsmJsOrigin ? "caller" : "main";
|
|
return CallWasmFunctionForTesting(isolate, instance, &thrower, f_name, argc,
|
|
argv, origin);
|
|
}
|
|
|
|
int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
|
|
const byte* module_end, ModuleOrigin origin) {
|
|
HandleScope scope(isolate);
|
|
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
|
|
Handle<JSObject> instance = CompileInstantiateWasmModuleForTesting(
|
|
isolate, &thrower, module_start, module_end, origin);
|
|
if (instance.is_null()) {
|
|
return -1;
|
|
}
|
|
return RunWasmModuleForTesting(isolate, instance, 0, nullptr, origin);
|
|
}
|
|
|
|
int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
|
|
const WasmModule* module,
|
|
const ModuleWireBytes& wire_bytes,
|
|
int function_index, WasmVal* args,
|
|
bool* possible_nondeterminism) {
|
|
DCHECK_NOT_NULL(module);
|
|
Zone zone(isolate->allocator(), ZONE_NAME);
|
|
v8::internal::HandleScope scope(isolate);
|
|
|
|
if (module->import_table.size() > 0) {
|
|
thrower->CompileError("Not supported: module has imports.");
|
|
}
|
|
if (module->export_table.size() == 0) {
|
|
thrower->CompileError("Not supported: module has no exports.");
|
|
}
|
|
|
|
if (thrower->error()) return -1;
|
|
|
|
// The code verifies, we create an instance to run it in the interpreter.
|
|
WasmInstance instance(module);
|
|
instance.context = isolate->native_context();
|
|
instance.mem_size = GetMinModuleMemSize(module);
|
|
// TODO(ahaas): Move memory allocation to wasm-module.cc for better
|
|
// encapsulation.
|
|
instance.mem_start =
|
|
static_cast<byte*>(calloc(GetMinModuleMemSize(module), 1));
|
|
instance.globals_start = nullptr;
|
|
|
|
ModuleBytesEnv env(module, &instance, wire_bytes);
|
|
WasmInterpreter interpreter(env, isolate->allocator());
|
|
|
|
WasmInterpreter::Thread* thread = interpreter.GetThread(0);
|
|
thread->Reset();
|
|
thread->PushFrame(&(module->functions[function_index]), args);
|
|
WasmInterpreter::State interpreter_result = thread->Run();
|
|
if (instance.mem_start) {
|
|
free(instance.mem_start);
|
|
}
|
|
*possible_nondeterminism = thread->PossibleNondeterminism();
|
|
if (interpreter_result == WasmInterpreter::FINISHED) {
|
|
WasmVal val = thread->GetReturnValue();
|
|
return val.to<int32_t>();
|
|
} else if (thread->state() == WasmInterpreter::TRAPPED) {
|
|
return 0xdeadbeef;
|
|
} else {
|
|
thrower->RangeError(
|
|
"Interpreter did not finish execution within its step bound");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int32_t CallWasmFunctionForTesting(Isolate* isolate, Handle<JSObject> instance,
|
|
ErrorThrower* thrower, const char* name,
|
|
int argc, Handle<Object> argv[],
|
|
ModuleOrigin origin) {
|
|
Handle<JSObject> exports_object;
|
|
if (origin == ModuleOrigin::kAsmJsOrigin) {
|
|
exports_object = instance;
|
|
} else {
|
|
Handle<Name> exports = isolate->factory()->InternalizeUtf8String("exports");
|
|
exports_object = Handle<JSObject>::cast(
|
|
JSObject::GetProperty(instance, exports).ToHandleChecked());
|
|
}
|
|
Handle<Name> main_name = isolate->factory()->NewStringFromAsciiChecked(name);
|
|
PropertyDescriptor desc;
|
|
Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
|
|
isolate, exports_object, main_name, &desc);
|
|
if (!property_found.FromMaybe(false)) return -1;
|
|
|
|
Handle<JSFunction> main_export = Handle<JSFunction>::cast(desc.value());
|
|
|
|
// Call the JS function.
|
|
Handle<Object> undefined = isolate->factory()->undefined_value();
|
|
MaybeHandle<Object> retval =
|
|
Execution::Call(isolate, main_export, undefined, argc, argv);
|
|
|
|
// The result should be a number.
|
|
if (retval.is_null()) {
|
|
thrower->RuntimeError("WASM.compileRun() failed: Invocation was null");
|
|
return -1;
|
|
}
|
|
Handle<Object> result = retval.ToHandleChecked();
|
|
if (result->IsSmi()) {
|
|
return Smi::cast(*result)->value();
|
|
}
|
|
if (result->IsHeapNumber()) {
|
|
return static_cast<int32_t>(HeapNumber::cast(*result)->value());
|
|
}
|
|
thrower->RuntimeError(
|
|
"WASM.compileRun() failed: Return value should be number");
|
|
return -1;
|
|
}
|
|
|
|
void SetupIsolateForWasmModule(Isolate* isolate) {
|
|
WasmJs::InstallWasmMapsIfNeeded(isolate, isolate->native_context());
|
|
WasmJs::InstallWasmModuleSymbolIfNeeded(isolate, isolate->global_object(),
|
|
isolate->native_context());
|
|
}
|
|
} // namespace testing
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|