Reland of [wasm] Add tests for JS wrappers to test-run-wasm.
Fix: pass global object as receiver when calling WASM->JS. R=bradnelson@chromium.org, ahaas@chromium.org BUG= Review URL: https://codereview.chromium.org/1581393003 Cr-Commit-Position: refs/heads/master@{#33286}
This commit is contained in:
parent
3743bf4837
commit
d1bc4f0e27
@ -1597,7 +1597,9 @@ void WasmGraphBuilder::BuildWasmToJSWrapper(Handle<JSFunction> function,
|
||||
if (arg_count_before_args) {
|
||||
args[pos++] = jsgraph()->Int32Constant(wasm_count); // argument count
|
||||
}
|
||||
args[pos++] = jsgraph()->UndefinedConstant(); // JS receiver.
|
||||
// JS receiver.
|
||||
Handle<Object> global(function->context()->global_object(), isolate);
|
||||
args[pos++] = jsgraph()->Constant(global);
|
||||
|
||||
// Convert WASM numbers to JS values.
|
||||
for (int i = 0; i < wasm_count; i++) {
|
||||
|
@ -307,18 +307,15 @@ static void InstallFunc(Isolate* isolate, Handle<JSObject> object,
|
||||
|
||||
void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) {
|
||||
// Setup wasm function map.
|
||||
Handle<Map> wasm_function_map = isolate->factory()->NewMap(
|
||||
JS_FUNCTION_TYPE, JSFunction::kSize + kPointerSize);
|
||||
wasm_function_map->set_is_callable();
|
||||
global->native_context()->set_wasm_function_map(*wasm_function_map);
|
||||
Handle<Context> context(global->native_context(), isolate);
|
||||
InstallWasmFunctionMap(isolate, context);
|
||||
|
||||
// Bind the WASM object.
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<String> name = v8_str(isolate, "_WASMEXP_");
|
||||
Handle<JSFunction> cons = factory->NewFunction(name);
|
||||
JSFunction::SetInstancePrototype(
|
||||
cons, Handle<Object>(global->native_context()->initial_object_prototype(),
|
||||
isolate));
|
||||
cons, Handle<Object>(context->initial_object_prototype(), isolate));
|
||||
cons->shared()->set_instance_class_name(*name);
|
||||
Handle<JSObject> wasm_object = factory->NewJSObject(cons, TENURED);
|
||||
PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM);
|
||||
@ -333,5 +330,16 @@ void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) {
|
||||
InstallFunc(isolate, wasm_object, "instantiateModuleFromAsm",
|
||||
InstantiateModuleFromAsm);
|
||||
}
|
||||
|
||||
|
||||
void WasmJs::InstallWasmFunctionMap(Isolate* isolate, Handle<Context> context) {
|
||||
if (!context->get(Context::WASM_FUNCTION_MAP_INDEX)->IsMap()) {
|
||||
Handle<Map> wasm_function_map = isolate->factory()->NewMap(
|
||||
JS_FUNCTION_TYPE, JSFunction::kSize + kPointerSize);
|
||||
wasm_function_map->set_is_callable();
|
||||
context->set_wasm_function_map(*wasm_function_map);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -19,7 +19,9 @@ namespace internal {
|
||||
class WasmJs {
|
||||
public:
|
||||
static void Install(Isolate* isolate, Handle<JSGlobalObject> global_object);
|
||||
static void InstallWasmFunctionMap(Isolate* isolate, Handle<Context> context);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
#endif
|
||||
|
@ -186,8 +186,10 @@
|
||||
'test-weaksets.cc',
|
||||
'trace-extension.cc',
|
||||
'wasm/test-run-wasm.cc',
|
||||
'wasm/test-run-wasm-js.cc',
|
||||
'wasm/test-run-wasm-module.cc',
|
||||
'wasm/test-signatures.h',
|
||||
'wasm/wasm-run-utils.h',
|
||||
],
|
||||
'conditions': [
|
||||
['v8_target_arch=="ia32"', {
|
||||
|
141
test/cctest/wasm/test-run-wasm-js.cc
Normal file
141
test/cctest/wasm/test-run-wasm-js.cc
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2015 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "src/wasm/wasm-macro-gen.h"
|
||||
|
||||
#include "test/cctest/cctest.h"
|
||||
#include "test/cctest/wasm/test-signatures.h"
|
||||
#include "test/cctest/wasm/wasm-run-utils.h"
|
||||
|
||||
using namespace v8::base;
|
||||
using namespace v8::internal;
|
||||
using namespace v8::internal::compiler;
|
||||
using namespace v8::internal::wasm;
|
||||
|
||||
#define BUILD(r, ...) \
|
||||
do { \
|
||||
byte code[] = {__VA_ARGS__}; \
|
||||
r.Build(code, code + arraysize(code)); \
|
||||
} while (false)
|
||||
|
||||
|
||||
static uint32_t AddJsFunction(TestingModule* module, FunctionSig* sig,
|
||||
const char* source) {
|
||||
Handle<JSFunction> jsfunc = Handle<JSFunction>::cast(v8::Utils::OpenHandle(
|
||||
*v8::Local<v8::Function>::Cast(CompileRun(source))));
|
||||
module->AddFunction(sig, Handle<Code>::null());
|
||||
uint32_t index = static_cast<uint32_t>(module->module->functions->size() - 1);
|
||||
Isolate* isolate = CcTest::InitIsolateOnce();
|
||||
Handle<Code> code = CompileWasmToJSWrapper(isolate, module, jsfunc, index);
|
||||
module->function_code->at(index) = code;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
static Handle<JSFunction> WrapCode(ModuleEnv* module, uint32_t index) {
|
||||
Isolate* isolate = module->module->shared_isolate;
|
||||
// Wrap the code so it can be called as a JS function.
|
||||
Handle<String> name = isolate->factory()->NewStringFromStaticChars("main");
|
||||
Handle<JSObject> module_object = Handle<JSObject>(0, isolate);
|
||||
Handle<Code> code = module->function_code->at(index);
|
||||
WasmJs::InstallWasmFunctionMap(isolate, isolate->native_context());
|
||||
return compiler::CompileJSToWasmWrapper(isolate, module, name, code,
|
||||
module_object, index);
|
||||
}
|
||||
|
||||
|
||||
static void EXPECT_CALL(double expected, Handle<JSFunction> jsfunc, double a,
|
||||
double b) {
|
||||
Isolate* isolate = jsfunc->GetIsolate();
|
||||
Handle<Object> buffer[] = {isolate->factory()->NewNumber(a),
|
||||
isolate->factory()->NewNumber(b)};
|
||||
Handle<Object> global(isolate->context()->global_object(), isolate);
|
||||
MaybeHandle<Object> retval =
|
||||
Execution::Call(isolate, jsfunc, global, 2, buffer);
|
||||
|
||||
CHECK(!retval.is_null());
|
||||
Handle<Object> result = retval.ToHandleChecked();
|
||||
if (result->IsSmi()) {
|
||||
CHECK_EQ(expected, Smi::cast(*result)->value());
|
||||
} else {
|
||||
CHECK(result->IsHeapNumber());
|
||||
CHECK_EQ(expected, HeapNumber::cast(*result)->value());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(Run_Int32Sub_jswrapped) {
|
||||
TestSignatures sigs;
|
||||
TestingModule module;
|
||||
WasmFunctionCompiler t(sigs.i_ii());
|
||||
BUILD(t, WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
|
||||
Handle<JSFunction> jsfunc = WrapCode(&module, t.CompileAndAdd(&module));
|
||||
|
||||
EXPECT_CALL(33, jsfunc, 44, 11);
|
||||
EXPECT_CALL(-8723487, jsfunc, -8000000, 723487);
|
||||
}
|
||||
|
||||
|
||||
TEST(Run_Float32Div_jswrapped) {
|
||||
TestSignatures sigs;
|
||||
TestingModule module;
|
||||
WasmFunctionCompiler t(sigs.f_ff());
|
||||
BUILD(t, WASM_F32_DIV(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
|
||||
Handle<JSFunction> jsfunc = WrapCode(&module, t.CompileAndAdd(&module));
|
||||
|
||||
EXPECT_CALL(92, jsfunc, 46, 0.5);
|
||||
EXPECT_CALL(64, jsfunc, -16, -0.25);
|
||||
}
|
||||
|
||||
|
||||
TEST(Run_Float64Add_jswrapped) {
|
||||
TestSignatures sigs;
|
||||
TestingModule module;
|
||||
WasmFunctionCompiler t(sigs.d_dd());
|
||||
BUILD(t, WASM_F64_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
|
||||
Handle<JSFunction> jsfunc = WrapCode(&module, t.CompileAndAdd(&module));
|
||||
|
||||
EXPECT_CALL(3, jsfunc, 2, 1);
|
||||
EXPECT_CALL(-5.5, jsfunc, -5.25, -0.25);
|
||||
}
|
||||
|
||||
|
||||
TEST(Run_I32Popcount_jswrapped) {
|
||||
TestSignatures sigs;
|
||||
TestingModule module;
|
||||
WasmFunctionCompiler t(sigs.i_i());
|
||||
BUILD(t, WASM_I32_POPCNT(WASM_GET_LOCAL(0)));
|
||||
Handle<JSFunction> jsfunc = WrapCode(&module, t.CompileAndAdd(&module));
|
||||
|
||||
EXPECT_CALL(2, jsfunc, 9, 0);
|
||||
EXPECT_CALL(3, jsfunc, 11, 0);
|
||||
EXPECT_CALL(6, jsfunc, 0x3F, 0);
|
||||
|
||||
USE(AddJsFunction);
|
||||
}
|
||||
|
||||
|
||||
#if !V8_TARGET_ARCH_ARM64
|
||||
// TODO(titzer): fix wasm->JS calls on arm64 (wrapper issues)
|
||||
|
||||
TEST(Run_CallJS_Add_jswrapped) {
|
||||
TestSignatures sigs;
|
||||
TestingModule module;
|
||||
WasmFunctionCompiler t(sigs.i_i(), &module);
|
||||
uint32_t js_index =
|
||||
AddJsFunction(&module, sigs.i_i(), "(function(a) { return a + 99; })");
|
||||
BUILD(t, WASM_CALL_FUNCTION(js_index, WASM_GET_LOCAL(0)));
|
||||
|
||||
Handle<JSFunction> jsfunc = WrapCode(&module, t.CompileAndAdd(&module));
|
||||
|
||||
EXPECT_CALL(101, jsfunc, 2, -8);
|
||||
EXPECT_CALL(199, jsfunc, 100, -1);
|
||||
EXPECT_CALL(-666666801, jsfunc, -666666900, -1);
|
||||
}
|
||||
|
||||
#endif
|
@ -6,391 +6,18 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "src/base/utils/random-number-generator.h"
|
||||
|
||||
#include "src/compiler/graph-visualizer.h"
|
||||
#include "src/compiler/js-graph.h"
|
||||
#include "src/compiler/wasm-compiler.h"
|
||||
|
||||
#include "src/wasm/ast-decoder.h"
|
||||
#include "src/wasm/wasm-macro-gen.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
|
||||
#include "test/cctest/cctest.h"
|
||||
#include "test/cctest/compiler/codegen-tester.h"
|
||||
#include "test/cctest/compiler/graph-builder-tester.h"
|
||||
#include "test/cctest/compiler/value-helper.h"
|
||||
|
||||
#include "test/cctest/wasm/test-signatures.h"
|
||||
|
||||
// TODO(titzer): pull WASM_64 up to a common header.
|
||||
#if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64
|
||||
#define WASM_64 1
|
||||
#else
|
||||
#define WASM_64 0
|
||||
#endif
|
||||
|
||||
// TODO(titzer): check traps more robustly in tests.
|
||||
// Currently, in tests, we just return 0xdeadbeef from the function in which
|
||||
// the trap occurs if the runtime context is not available to throw a JavaScript
|
||||
// exception.
|
||||
#define CHECK_TRAP32(x) \
|
||||
CHECK_EQ(0xdeadbeef, (bit_cast<uint32_t>(x)) & 0xFFFFFFFF)
|
||||
#define CHECK_TRAP64(x) \
|
||||
CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF)
|
||||
#define CHECK_TRAP(x) CHECK_TRAP32(x)
|
||||
|
||||
#include "test/cctest/wasm/wasm-run-utils.h"
|
||||
|
||||
using namespace v8::base;
|
||||
using namespace v8::internal;
|
||||
using namespace v8::internal::compiler;
|
||||
using namespace v8::internal::wasm;
|
||||
|
||||
static void init_env(FunctionEnv* env, FunctionSig* sig) {
|
||||
env->module = nullptr;
|
||||
env->sig = sig;
|
||||
env->local_int32_count = 0;
|
||||
env->local_int64_count = 0;
|
||||
env->local_float32_count = 0;
|
||||
env->local_float64_count = 0;
|
||||
env->SumLocals();
|
||||
}
|
||||
|
||||
const uint32_t kMaxGlobalsSize = 128;
|
||||
|
||||
// A helper for module environments that adds the ability to allocate memory
|
||||
// and global variables.
|
||||
class TestingModule : public ModuleEnv {
|
||||
public:
|
||||
TestingModule() : mem_size(0), global_offset(0) {
|
||||
globals_area = 0;
|
||||
mem_start = 0;
|
||||
mem_end = 0;
|
||||
module = nullptr;
|
||||
linker = nullptr;
|
||||
function_code = nullptr;
|
||||
asm_js = false;
|
||||
memset(global_data, 0, sizeof(global_data));
|
||||
}
|
||||
|
||||
~TestingModule() {
|
||||
if (mem_start) {
|
||||
free(raw_mem_start<byte>());
|
||||
}
|
||||
if (function_code) delete function_code;
|
||||
if (module) delete module;
|
||||
}
|
||||
|
||||
byte* AddMemory(size_t size) {
|
||||
CHECK_EQ(0, mem_start);
|
||||
CHECK_EQ(0, mem_size);
|
||||
mem_start = reinterpret_cast<uintptr_t>(malloc(size));
|
||||
CHECK(mem_start);
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
memset(raw, 0, size);
|
||||
mem_end = mem_start + size;
|
||||
mem_size = size;
|
||||
return raw_mem_start<byte>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* AddMemoryElems(size_t count) {
|
||||
AddMemory(count * sizeof(T));
|
||||
return raw_mem_start<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* AddGlobal(MachineType mem_type) {
|
||||
WasmGlobal* global = AddGlobal(mem_type);
|
||||
return reinterpret_cast<T*>(globals_area + global->offset);
|
||||
}
|
||||
|
||||
byte AddSignature(FunctionSig* sig) {
|
||||
AllocModule();
|
||||
if (!module->signatures) {
|
||||
module->signatures = new std::vector<FunctionSig*>();
|
||||
}
|
||||
module->signatures->push_back(sig);
|
||||
size_t size = module->signatures->size();
|
||||
CHECK(size < 127);
|
||||
return static_cast<byte>(size - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* raw_mem_start() {
|
||||
DCHECK(mem_start);
|
||||
return reinterpret_cast<T*>(mem_start);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* raw_mem_end() {
|
||||
DCHECK(mem_end);
|
||||
return reinterpret_cast<T*>(mem_end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T raw_mem_at(int i) {
|
||||
DCHECK(mem_start);
|
||||
return reinterpret_cast<T*>(mem_start)[i];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T raw_val_at(int i) {
|
||||
T val;
|
||||
memcpy(&val, reinterpret_cast<void*>(mem_start + i), sizeof(T));
|
||||
return val;
|
||||
}
|
||||
|
||||
// Zero-initialize the memory.
|
||||
void BlankMemory() {
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
memset(raw, 0, mem_size);
|
||||
}
|
||||
|
||||
// Pseudo-randomly intialize the memory.
|
||||
void RandomizeMemory(unsigned int seed = 88) {
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
byte* end = raw_mem_end<byte>();
|
||||
v8::base::RandomNumberGenerator rng;
|
||||
rng.SetSeed(seed);
|
||||
rng.NextBytes(raw, end - raw);
|
||||
}
|
||||
|
||||
WasmFunction* AddFunction(FunctionSig* sig, Handle<Code> code) {
|
||||
AllocModule();
|
||||
if (module->functions == nullptr) {
|
||||
module->functions = new std::vector<WasmFunction>();
|
||||
function_code = new std::vector<Handle<Code>>();
|
||||
}
|
||||
module->functions->push_back({sig, 0, 0, 0, 0, 0, 0, 0, false, false});
|
||||
function_code->push_back(code);
|
||||
return &module->functions->back();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mem_size;
|
||||
uint32_t global_offset;
|
||||
byte global_data[kMaxGlobalsSize];
|
||||
|
||||
WasmGlobal* AddGlobal(MachineType mem_type) {
|
||||
AllocModule();
|
||||
if (globals_area == 0) {
|
||||
globals_area = reinterpret_cast<uintptr_t>(global_data);
|
||||
module->globals = new std::vector<WasmGlobal>();
|
||||
}
|
||||
byte size = WasmOpcodes::MemSize(mem_type);
|
||||
global_offset = (global_offset + size - 1) & ~(size - 1); // align
|
||||
module->globals->push_back({0, mem_type, global_offset, false});
|
||||
global_offset += size;
|
||||
// limit number of globals.
|
||||
CHECK_LT(global_offset, kMaxGlobalsSize);
|
||||
return &module->globals->back();
|
||||
}
|
||||
void AllocModule() {
|
||||
if (module == nullptr) {
|
||||
module = new WasmModule();
|
||||
module->globals = nullptr;
|
||||
module->functions = nullptr;
|
||||
module->data_segments = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, FunctionEnv* env,
|
||||
const byte* start, const byte* end) {
|
||||
compiler::WasmGraphBuilder builder(zone, jsgraph, env->sig);
|
||||
TreeResult result = BuildTFGraph(&builder, env, start, end);
|
||||
if (result.failed()) {
|
||||
ptrdiff_t pc = result.error_pc - result.start;
|
||||
ptrdiff_t pt = result.error_pt - result.start;
|
||||
std::ostringstream str;
|
||||
str << "Verification failed: " << result.error_code << " pc = +" << pc;
|
||||
if (result.error_pt) str << ", pt = +" << pt;
|
||||
str << ", msg = " << result.error_msg.get();
|
||||
FATAL(str.str().c_str());
|
||||
}
|
||||
if (FLAG_trace_turbo_graph) {
|
||||
OFStream os(stdout);
|
||||
os << AsRPO(*jsgraph->graph());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A helper for compiling functions that are only internally callable WASM code.
|
||||
class WasmFunctionCompiler : public HandleAndZoneScope,
|
||||
private GraphAndBuilders {
|
||||
public:
|
||||
explicit WasmFunctionCompiler(FunctionSig* sig)
|
||||
: GraphAndBuilders(main_zone()),
|
||||
jsgraph(this->isolate(), this->graph(), this->common(), nullptr,
|
||||
nullptr, this->machine()),
|
||||
descriptor_(nullptr) {
|
||||
init_env(&env, sig);
|
||||
}
|
||||
|
||||
JSGraph jsgraph;
|
||||
FunctionEnv env;
|
||||
// The call descriptor is initialized when the function is compiled.
|
||||
CallDescriptor* descriptor_;
|
||||
|
||||
Isolate* isolate() { return main_isolate(); }
|
||||
Graph* graph() const { return main_graph_; }
|
||||
Zone* zone() const { return graph()->zone(); }
|
||||
CommonOperatorBuilder* common() { return &main_common_; }
|
||||
MachineOperatorBuilder* machine() { return &main_machine_; }
|
||||
CallDescriptor* descriptor() { return descriptor_; }
|
||||
|
||||
void Build(const byte* start, const byte* end) {
|
||||
TestBuildingGraph(main_zone(), &jsgraph, &env, start, end);
|
||||
}
|
||||
|
||||
byte AllocateLocal(LocalType type) {
|
||||
int result = static_cast<int>(env.total_locals);
|
||||
env.AddLocals(type, 1);
|
||||
byte b = static_cast<byte>(result);
|
||||
CHECK_EQ(result, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
Handle<Code> Compile(ModuleEnv* module) {
|
||||
descriptor_ = module->GetWasmCallDescriptor(this->zone(), env.sig);
|
||||
CompilationInfo info("wasm compile", this->isolate(), this->zone());
|
||||
Handle<Code> result =
|
||||
Pipeline::GenerateCodeForTesting(&info, descriptor_, this->graph());
|
||||
#ifdef ENABLE_DISASSEMBLER
|
||||
if (!result.is_null() && FLAG_print_opt_code) {
|
||||
OFStream os(stdout);
|
||||
result->Disassemble("wasm code", os);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t CompileAndAdd(TestingModule* module) {
|
||||
uint32_t index = 0;
|
||||
if (module->module && module->module->functions) {
|
||||
index = static_cast<uint32_t>(module->module->functions->size());
|
||||
}
|
||||
module->AddFunction(env.sig, Compile(module));
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A helper class to build graphs from Wasm bytecode, generate machine
|
||||
// code, and run that code.
|
||||
template <typename ReturnType>
|
||||
class WasmRunner {
|
||||
public:
|
||||
WasmRunner(MachineType p0 = MachineType::None(),
|
||||
MachineType p1 = MachineType::None(),
|
||||
MachineType p2 = MachineType::None(),
|
||||
MachineType p3 = MachineType::None())
|
||||
: signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1,
|
||||
GetParameterCount(p0, p1, p2, p3), storage_),
|
||||
compiler_(&signature_),
|
||||
call_wrapper_(p0, p1, p2, p3),
|
||||
compilation_done_(false) {
|
||||
int index = 0;
|
||||
MachineType ret = MachineTypeForC<ReturnType>();
|
||||
if (ret != MachineType::None()) {
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(ret);
|
||||
}
|
||||
if (p0 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p0);
|
||||
if (p1 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p1);
|
||||
if (p2 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p2);
|
||||
if (p3 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p3);
|
||||
}
|
||||
|
||||
|
||||
FunctionEnv* env() { return &compiler_.env; }
|
||||
|
||||
|
||||
// Builds a graph from the given Wasm code, and generates the machine
|
||||
// code and call wrapper for that graph. This method must not be called
|
||||
// more than once.
|
||||
void Build(const byte* start, const byte* end) {
|
||||
DCHECK(!compilation_done_);
|
||||
compilation_done_ = true;
|
||||
// Build the TF graph.
|
||||
compiler_.Build(start, end);
|
||||
// Generate code.
|
||||
Handle<Code> code = compiler_.Compile(env()->module);
|
||||
|
||||
// Construct the call wrapper.
|
||||
Node* inputs[5];
|
||||
int input_count = 0;
|
||||
inputs[input_count++] = call_wrapper_.HeapConstant(code);
|
||||
for (size_t i = 0; i < signature_.parameter_count(); i++) {
|
||||
inputs[input_count++] = call_wrapper_.Parameter(i);
|
||||
}
|
||||
|
||||
call_wrapper_.Return(call_wrapper_.AddNode(
|
||||
call_wrapper_.common()->Call(compiler_.descriptor()), input_count,
|
||||
inputs));
|
||||
}
|
||||
|
||||
|
||||
ReturnType Call() { return call_wrapper_.Call(); }
|
||||
|
||||
|
||||
template <typename P0>
|
||||
ReturnType Call(P0 p0) {
|
||||
return call_wrapper_.Call(p0);
|
||||
}
|
||||
|
||||
|
||||
template <typename P0, typename P1>
|
||||
ReturnType Call(P0 p0, P1 p1) {
|
||||
return call_wrapper_.Call(p0, p1);
|
||||
}
|
||||
|
||||
|
||||
template <typename P0, typename P1, typename P2>
|
||||
ReturnType Call(P0 p0, P1 p1, P2 p2) {
|
||||
return call_wrapper_.Call(p0, p1, p2);
|
||||
}
|
||||
|
||||
|
||||
template <typename P0, typename P1, typename P2, typename P3>
|
||||
ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
|
||||
return call_wrapper_.Call(p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
|
||||
byte AllocateLocal(LocalType type) {
|
||||
int result = static_cast<int>(env()->total_locals);
|
||||
env()->AddLocals(type, 1);
|
||||
byte b = static_cast<byte>(result);
|
||||
CHECK_EQ(result, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
LocalType storage_[5];
|
||||
FunctionSig signature_;
|
||||
WasmFunctionCompiler compiler_;
|
||||
BufferedRawMachineAssemblerTester<ReturnType> call_wrapper_;
|
||||
bool compilation_done_;
|
||||
|
||||
static size_t GetParameterCount(MachineType p0, MachineType p1,
|
||||
MachineType p2, MachineType p3) {
|
||||
if (p0 == MachineType::None()) return 0;
|
||||
if (p1 == MachineType::None()) return 1;
|
||||
if (p2 == MachineType::None()) return 2;
|
||||
if (p3 == MachineType::None()) return 3;
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
#define BUILD(r, ...) \
|
||||
do { \
|
||||
byte code[] = {__VA_ARGS__}; \
|
||||
@ -2896,7 +2523,7 @@ TEST(Run_WasmCall_Float64Sub) {
|
||||
} while (false)
|
||||
|
||||
|
||||
void Run_WasmMixedCall_N(int start) {
|
||||
static void Run_WasmMixedCall_N(int start) {
|
||||
const int kExpected = 6333;
|
||||
const int kElemSize = 8;
|
||||
TestSignatures sigs;
|
||||
|
391
test/cctest/wasm/wasm-run-utils.h
Normal file
391
test/cctest/wasm/wasm-run-utils.h
Normal file
@ -0,0 +1,391 @@
|
||||
// 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 WASM_RUN_UTILS_H
|
||||
#define WASM_RUN_UTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "src/base/utils/random-number-generator.h"
|
||||
|
||||
#include "src/compiler/graph-visualizer.h"
|
||||
#include "src/compiler/js-graph.h"
|
||||
#include "src/compiler/wasm-compiler.h"
|
||||
|
||||
#include "src/wasm/ast-decoder.h"
|
||||
#include "src/wasm/wasm-js.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
|
||||
#include "test/cctest/cctest.h"
|
||||
#include "test/cctest/compiler/codegen-tester.h"
|
||||
#include "test/cctest/compiler/graph-builder-tester.h"
|
||||
|
||||
// TODO(titzer): pull WASM_64 up to a common header.
|
||||
#if !V8_TARGET_ARCH_32_BIT || V8_TARGET_ARCH_X64
|
||||
#define WASM_64 1
|
||||
#else
|
||||
#define WASM_64 0
|
||||
#endif
|
||||
|
||||
// TODO(titzer): check traps more robustly in tests.
|
||||
// Currently, in tests, we just return 0xdeadbeef from the function in which
|
||||
// the trap occurs if the runtime context is not available to throw a JavaScript
|
||||
// exception.
|
||||
#define CHECK_TRAP32(x) \
|
||||
CHECK_EQ(0xdeadbeef, (bit_cast<uint32_t>(x)) & 0xFFFFFFFF)
|
||||
#define CHECK_TRAP64(x) \
|
||||
CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF)
|
||||
#define CHECK_TRAP(x) CHECK_TRAP32(x)
|
||||
|
||||
namespace {
|
||||
using namespace v8::base;
|
||||
using namespace v8::internal;
|
||||
using namespace v8::internal::compiler;
|
||||
using namespace v8::internal::wasm;
|
||||
|
||||
inline void init_env(FunctionEnv* env, FunctionSig* sig) {
|
||||
env->module = nullptr;
|
||||
env->sig = sig;
|
||||
env->local_int32_count = 0;
|
||||
env->local_int64_count = 0;
|
||||
env->local_float32_count = 0;
|
||||
env->local_float64_count = 0;
|
||||
env->SumLocals();
|
||||
}
|
||||
|
||||
const uint32_t kMaxGlobalsSize = 128;
|
||||
|
||||
// A helper for module environments that adds the ability to allocate memory
|
||||
// and global variables.
|
||||
class TestingModule : public ModuleEnv {
|
||||
public:
|
||||
TestingModule() : mem_size(0), global_offset(0) {
|
||||
globals_area = 0;
|
||||
mem_start = 0;
|
||||
mem_end = 0;
|
||||
module = nullptr;
|
||||
linker = nullptr;
|
||||
function_code = nullptr;
|
||||
asm_js = false;
|
||||
memset(global_data, 0, sizeof(global_data));
|
||||
}
|
||||
|
||||
~TestingModule() {
|
||||
if (mem_start) {
|
||||
free(raw_mem_start<byte>());
|
||||
}
|
||||
if (function_code) delete function_code;
|
||||
if (module) delete module;
|
||||
}
|
||||
|
||||
byte* AddMemory(size_t size) {
|
||||
CHECK_EQ(0, mem_start);
|
||||
CHECK_EQ(0, mem_size);
|
||||
mem_start = reinterpret_cast<uintptr_t>(malloc(size));
|
||||
CHECK(mem_start);
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
memset(raw, 0, size);
|
||||
mem_end = mem_start + size;
|
||||
mem_size = size;
|
||||
return raw_mem_start<byte>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* AddMemoryElems(size_t count) {
|
||||
AddMemory(count * sizeof(T));
|
||||
return raw_mem_start<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* AddGlobal(MachineType mem_type) {
|
||||
WasmGlobal* global = AddGlobal(mem_type);
|
||||
return reinterpret_cast<T*>(globals_area + global->offset);
|
||||
}
|
||||
|
||||
byte AddSignature(FunctionSig* sig) {
|
||||
AllocModule();
|
||||
if (!module->signatures) {
|
||||
module->signatures = new std::vector<FunctionSig*>();
|
||||
}
|
||||
module->signatures->push_back(sig);
|
||||
size_t size = module->signatures->size();
|
||||
CHECK(size < 127);
|
||||
return static_cast<byte>(size - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* raw_mem_start() {
|
||||
DCHECK(mem_start);
|
||||
return reinterpret_cast<T*>(mem_start);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* raw_mem_end() {
|
||||
DCHECK(mem_end);
|
||||
return reinterpret_cast<T*>(mem_end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T raw_mem_at(int i) {
|
||||
DCHECK(mem_start);
|
||||
return reinterpret_cast<T*>(mem_start)[i];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T raw_val_at(int i) {
|
||||
T val;
|
||||
memcpy(&val, reinterpret_cast<void*>(mem_start + i), sizeof(T));
|
||||
return val;
|
||||
}
|
||||
|
||||
// Zero-initialize the memory.
|
||||
void BlankMemory() {
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
memset(raw, 0, mem_size);
|
||||
}
|
||||
|
||||
// Pseudo-randomly intialize the memory.
|
||||
void RandomizeMemory(unsigned int seed = 88) {
|
||||
byte* raw = raw_mem_start<byte>();
|
||||
byte* end = raw_mem_end<byte>();
|
||||
v8::base::RandomNumberGenerator rng;
|
||||
rng.SetSeed(seed);
|
||||
rng.NextBytes(raw, end - raw);
|
||||
}
|
||||
|
||||
WasmFunction* AddFunction(FunctionSig* sig, Handle<Code> code) {
|
||||
AllocModule();
|
||||
if (module->functions == nullptr) {
|
||||
module->functions = new std::vector<WasmFunction>();
|
||||
function_code = new std::vector<Handle<Code>>();
|
||||
}
|
||||
module->functions->push_back({sig, 0, 0, 0, 0, 0, 0, 0, false, false});
|
||||
function_code->push_back(code);
|
||||
return &module->functions->back();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mem_size;
|
||||
uint32_t global_offset;
|
||||
byte global_data[kMaxGlobalsSize];
|
||||
|
||||
WasmGlobal* AddGlobal(MachineType mem_type) {
|
||||
AllocModule();
|
||||
if (globals_area == 0) {
|
||||
globals_area = reinterpret_cast<uintptr_t>(global_data);
|
||||
module->globals = new std::vector<WasmGlobal>();
|
||||
}
|
||||
byte size = WasmOpcodes::MemSize(mem_type);
|
||||
global_offset = (global_offset + size - 1) & ~(size - 1); // align
|
||||
module->globals->push_back({0, mem_type, global_offset, false});
|
||||
global_offset += size;
|
||||
// limit number of globals.
|
||||
CHECK_LT(global_offset, kMaxGlobalsSize);
|
||||
return &module->globals->back();
|
||||
}
|
||||
void AllocModule() {
|
||||
if (module == nullptr) {
|
||||
module = new WasmModule();
|
||||
module->shared_isolate = CcTest::InitIsolateOnce();
|
||||
module->globals = nullptr;
|
||||
module->functions = nullptr;
|
||||
module->data_segments = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, FunctionEnv* env,
|
||||
const byte* start, const byte* end) {
|
||||
compiler::WasmGraphBuilder builder(zone, jsgraph, env->sig);
|
||||
TreeResult result = BuildTFGraph(&builder, env, start, end);
|
||||
if (result.failed()) {
|
||||
ptrdiff_t pc = result.error_pc - result.start;
|
||||
ptrdiff_t pt = result.error_pt - result.start;
|
||||
std::ostringstream str;
|
||||
str << "Verification failed: " << result.error_code << " pc = +" << pc;
|
||||
if (result.error_pt) str << ", pt = +" << pt;
|
||||
str << ", msg = " << result.error_msg.get();
|
||||
FATAL(str.str().c_str());
|
||||
}
|
||||
if (FLAG_trace_turbo_graph) {
|
||||
OFStream os(stdout);
|
||||
os << AsRPO(*jsgraph->graph());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A helper for compiling functions that are only internally callable WASM code.
|
||||
class WasmFunctionCompiler : public HandleAndZoneScope,
|
||||
private GraphAndBuilders {
|
||||
public:
|
||||
explicit WasmFunctionCompiler(FunctionSig* sig, ModuleEnv* module = nullptr)
|
||||
: GraphAndBuilders(main_zone()),
|
||||
jsgraph(this->isolate(), this->graph(), this->common(), nullptr,
|
||||
nullptr, this->machine()),
|
||||
descriptor_(nullptr) {
|
||||
init_env(&env, sig);
|
||||
env.module = module;
|
||||
}
|
||||
|
||||
JSGraph jsgraph;
|
||||
FunctionEnv env;
|
||||
// The call descriptor is initialized when the function is compiled.
|
||||
CallDescriptor* descriptor_;
|
||||
|
||||
Isolate* isolate() { return main_isolate(); }
|
||||
Graph* graph() const { return main_graph_; }
|
||||
Zone* zone() const { return graph()->zone(); }
|
||||
CommonOperatorBuilder* common() { return &main_common_; }
|
||||
MachineOperatorBuilder* machine() { return &main_machine_; }
|
||||
CallDescriptor* descriptor() { return descriptor_; }
|
||||
|
||||
void Build(const byte* start, const byte* end) {
|
||||
TestBuildingGraph(main_zone(), &jsgraph, &env, start, end);
|
||||
}
|
||||
|
||||
byte AllocateLocal(LocalType type) {
|
||||
int result = static_cast<int>(env.total_locals);
|
||||
env.AddLocals(type, 1);
|
||||
byte b = static_cast<byte>(result);
|
||||
CHECK_EQ(result, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
Handle<Code> Compile(ModuleEnv* module) {
|
||||
descriptor_ = module->GetWasmCallDescriptor(this->zone(), env.sig);
|
||||
CompilationInfo info("wasm compile", this->isolate(), this->zone());
|
||||
Handle<Code> result =
|
||||
Pipeline::GenerateCodeForTesting(&info, descriptor_, this->graph());
|
||||
#ifdef ENABLE_DISASSEMBLER
|
||||
if (!result.is_null() && FLAG_print_opt_code) {
|
||||
OFStream os(stdout);
|
||||
result->Disassemble("wasm code", os);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t CompileAndAdd(TestingModule* module) {
|
||||
uint32_t index = 0;
|
||||
if (module->module && module->module->functions) {
|
||||
index = static_cast<uint32_t>(module->module->functions->size());
|
||||
}
|
||||
module->AddFunction(env.sig, Compile(module));
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A helper class to build graphs from Wasm bytecode, generate machine
|
||||
// code, and run that code.
|
||||
template <typename ReturnType>
|
||||
class WasmRunner {
|
||||
public:
|
||||
WasmRunner(MachineType p0 = MachineType::None(),
|
||||
MachineType p1 = MachineType::None(),
|
||||
MachineType p2 = MachineType::None(),
|
||||
MachineType p3 = MachineType::None())
|
||||
: signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1,
|
||||
GetParameterCount(p0, p1, p2, p3), storage_),
|
||||
compiler_(&signature_),
|
||||
call_wrapper_(p0, p1, p2, p3),
|
||||
compilation_done_(false) {
|
||||
int index = 0;
|
||||
MachineType ret = MachineTypeForC<ReturnType>();
|
||||
if (ret != MachineType::None()) {
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(ret);
|
||||
}
|
||||
if (p0 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p0);
|
||||
if (p1 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p1);
|
||||
if (p2 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p2);
|
||||
if (p3 != MachineType::None())
|
||||
storage_[index++] = WasmOpcodes::LocalTypeFor(p3);
|
||||
}
|
||||
|
||||
|
||||
FunctionEnv* env() { return &compiler_.env; }
|
||||
|
||||
|
||||
// Builds a graph from the given Wasm code, and generates the machine
|
||||
// code and call wrapper for that graph. This method must not be called
|
||||
// more than once.
|
||||
void Build(const byte* start, const byte* end) {
|
||||
DCHECK(!compilation_done_);
|
||||
compilation_done_ = true;
|
||||
// Build the TF graph.
|
||||
compiler_.Build(start, end);
|
||||
// Generate code.
|
||||
Handle<Code> code = compiler_.Compile(env()->module);
|
||||
|
||||
// Construct the call wrapper.
|
||||
Node* inputs[5];
|
||||
int input_count = 0;
|
||||
inputs[input_count++] = call_wrapper_.HeapConstant(code);
|
||||
for (size_t i = 0; i < signature_.parameter_count(); i++) {
|
||||
inputs[input_count++] = call_wrapper_.Parameter(i);
|
||||
}
|
||||
|
||||
call_wrapper_.Return(call_wrapper_.AddNode(
|
||||
call_wrapper_.common()->Call(compiler_.descriptor()), input_count,
|
||||
inputs));
|
||||
}
|
||||
|
||||
ReturnType Call() { return call_wrapper_.Call(); }
|
||||
|
||||
template <typename P0>
|
||||
ReturnType Call(P0 p0) {
|
||||
return call_wrapper_.Call(p0);
|
||||
}
|
||||
|
||||
template <typename P0, typename P1>
|
||||
ReturnType Call(P0 p0, P1 p1) {
|
||||
return call_wrapper_.Call(p0, p1);
|
||||
}
|
||||
|
||||
template <typename P0, typename P1, typename P2>
|
||||
ReturnType Call(P0 p0, P1 p1, P2 p2) {
|
||||
return call_wrapper_.Call(p0, p1, p2);
|
||||
}
|
||||
|
||||
template <typename P0, typename P1, typename P2, typename P3>
|
||||
ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
|
||||
return call_wrapper_.Call(p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
byte AllocateLocal(LocalType type) {
|
||||
int result = static_cast<int>(env()->total_locals);
|
||||
env()->AddLocals(type, 1);
|
||||
byte b = static_cast<byte>(result);
|
||||
CHECK_EQ(result, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
private:
|
||||
LocalType storage_[5];
|
||||
FunctionSig signature_;
|
||||
WasmFunctionCompiler compiler_;
|
||||
BufferedRawMachineAssemblerTester<ReturnType> call_wrapper_;
|
||||
bool compilation_done_;
|
||||
|
||||
static size_t GetParameterCount(MachineType p0, MachineType p1,
|
||||
MachineType p2, MachineType p3) {
|
||||
if (p0 == MachineType::None()) return 0;
|
||||
if (p1 == MachineType::None()) return 1;
|
||||
if (p2 == MachineType::None()) return 2;
|
||||
if (p3 == MachineType::None()) return 3;
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user