b477a6d5d8
{ThreadInWasmScope} in only used in a single location, which is in a test. It does not look like many more such places will show up. R=titzer@chromium.org, Bug: v8:8015 Change-Id: I89f6f7e2bd13e2882e65f7657d73ba59a6c71757 Reviewed-on: https://chromium-review.googlesource.com/1193446 Commit-Queue: Andreas Haas <ahaas@chromium.org> Reviewed-by: Ben Titzer <titzer@chromium.org> Cr-Commit-Position: refs/heads/master@{#55486}
520 lines
18 KiB
C++
520 lines
18 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.
|
|
|
|
#ifndef WASM_RUN_UTILS_H
|
|
#define WASM_RUN_UTILS_H
|
|
|
|
#include <setjmp.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <array>
|
|
#include <memory>
|
|
|
|
#include "src/base/utils/random-number-generator.h"
|
|
#include "src/code-stubs.h"
|
|
#include "src/compiler/compiler-source-position-table.h"
|
|
#include "src/compiler/graph-visualizer.h"
|
|
#include "src/compiler/int64-lowering.h"
|
|
#include "src/compiler/js-graph.h"
|
|
#include "src/compiler/node.h"
|
|
#include "src/compiler/pipeline.h"
|
|
#include "src/compiler/wasm-compiler.h"
|
|
#include "src/compiler/zone-stats.h"
|
|
#include "src/trap-handler/trap-handler.h"
|
|
#include "src/wasm/function-body-decoder.h"
|
|
#include "src/wasm/local-decl-encoder.h"
|
|
#include "src/wasm/wasm-code-manager.h"
|
|
#include "src/wasm/wasm-external-refs.h"
|
|
#include "src/wasm/wasm-interpreter.h"
|
|
#include "src/wasm/wasm-js.h"
|
|
#include "src/wasm/wasm-module.h"
|
|
#include "src/wasm/wasm-objects-inl.h"
|
|
#include "src/wasm/wasm-objects.h"
|
|
#include "src/wasm/wasm-opcodes.h"
|
|
#include "src/wasm/wasm-tier.h"
|
|
#include "src/zone/accounting-allocator.h"
|
|
#include "src/zone/zone.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/cctest/compiler/call-tester.h"
|
|
#include "test/cctest/compiler/graph-builder-tester.h"
|
|
#include "test/common/wasm/flag-utils.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
|
|
constexpr uint32_t kMaxFunctions = 10;
|
|
constexpr uint32_t kMaxGlobalsSize = 128;
|
|
|
|
using compiler::CallDescriptor;
|
|
using compiler::MachineTypeForC;
|
|
using compiler::Node;
|
|
|
|
// 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)
|
|
|
|
#define WASM_WRAPPER_RETURN_VALUE 8754
|
|
|
|
#define BUILD(r, ...) \
|
|
do { \
|
|
byte code[] = {__VA_ARGS__}; \
|
|
r.Build(code, code + arraysize(code)); \
|
|
} while (false)
|
|
|
|
// For tests that must manually import a JSFunction with source code.
|
|
struct ManuallyImportedJSFunction {
|
|
FunctionSig* sig;
|
|
Handle<JSFunction> js_function;
|
|
};
|
|
|
|
// A Wasm module builder. Globals are pre-set, however, memory and code may be
|
|
// progressively added by a test. In turn, we piecemeal update the runtime
|
|
// objects, i.e. {WasmInstanceObject}, {WasmModuleObject} and, if necessary,
|
|
// the interpreter.
|
|
class TestingModuleBuilder {
|
|
public:
|
|
TestingModuleBuilder(Zone*, ManuallyImportedJSFunction*, ExecutionTier,
|
|
RuntimeExceptionSupport, LowerSimd);
|
|
|
|
void ChangeOriginToAsmjs() { test_module_->origin = kAsmJsOrigin; }
|
|
|
|
byte* AddMemory(uint32_t size);
|
|
|
|
size_t CodeTableLength() const { return native_module_->num_functions(); }
|
|
|
|
template <typename T>
|
|
T* AddMemoryElems(uint32_t count) {
|
|
AddMemory(count * sizeof(T));
|
|
return raw_mem_start<T>();
|
|
}
|
|
|
|
template <typename T>
|
|
T* AddGlobal(
|
|
ValueType type = ValueTypes::ValueTypeFor(MachineTypeForC<T>())) {
|
|
const WasmGlobal* global = AddGlobal(type);
|
|
return reinterpret_cast<T*>(globals_data_ + global->offset);
|
|
}
|
|
|
|
byte AddSignature(FunctionSig* sig) {
|
|
DCHECK_EQ(test_module_->signatures.size(),
|
|
test_module_->signature_ids.size());
|
|
test_module_->signatures.push_back(sig);
|
|
auto canonical_sig_num = test_module_->signature_map.FindOrInsert(*sig);
|
|
test_module_->signature_ids.push_back(canonical_sig_num);
|
|
size_t size = test_module_->signatures.size();
|
|
CHECK_GT(127, size);
|
|
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_start_);
|
|
return reinterpret_cast<T*>(mem_start_ + mem_size_);
|
|
}
|
|
|
|
template <typename T>
|
|
T raw_mem_at(int i) {
|
|
DCHECK(mem_start_);
|
|
return ReadMemory(&(reinterpret_cast<T*>(mem_start_)[i]));
|
|
}
|
|
|
|
template <typename T>
|
|
T raw_val_at(int i) {
|
|
return ReadMemory(reinterpret_cast<T*>(mem_start_ + i));
|
|
}
|
|
|
|
template <typename T>
|
|
void WriteMemory(T* p, T val) {
|
|
WriteLittleEndianValue<T>(reinterpret_cast<Address>(p), val);
|
|
}
|
|
|
|
template <typename T>
|
|
T ReadMemory(T* p) {
|
|
return ReadLittleEndianValue<T>(reinterpret_cast<Address>(p));
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
void SetMaxMemPages(uint32_t maximum_pages) {
|
|
test_module_->maximum_pages = maximum_pages;
|
|
if (instance_object()->has_memory_object()) {
|
|
instance_object()->memory_object()->set_maximum_pages(maximum_pages);
|
|
}
|
|
}
|
|
|
|
void SetHasSharedMemory() { test_module_->has_shared_memory = true; }
|
|
|
|
enum FunctionType { kImport, kWasm };
|
|
uint32_t AddFunction(FunctionSig* sig, const char* name, FunctionType type);
|
|
|
|
Handle<JSFunction> WrapCode(uint32_t index);
|
|
|
|
void AddIndirectFunctionTable(const uint16_t* function_indexes,
|
|
uint32_t table_size);
|
|
|
|
void PopulateIndirectFunctionTable();
|
|
|
|
uint32_t AddBytes(Vector<const byte> bytes);
|
|
|
|
WasmFunction* GetFunctionAt(int index) {
|
|
return &test_module_->functions[index];
|
|
}
|
|
|
|
WasmInterpreter* interpreter() const { return interpreter_; }
|
|
bool interpret() const { return interpreter_ != nullptr; }
|
|
LowerSimd lower_simd() const { return lower_simd_; }
|
|
Isolate* isolate() const { return isolate_; }
|
|
Handle<WasmInstanceObject> instance_object() const {
|
|
return instance_object_;
|
|
}
|
|
WasmCode* GetFunctionCode(uint32_t index) const {
|
|
return native_module_->code(index);
|
|
}
|
|
Address globals_start() const {
|
|
return reinterpret_cast<Address>(globals_data_);
|
|
}
|
|
void Link() {
|
|
if (linked_) return;
|
|
linked_ = true;
|
|
native_module_->SetExecutable(true);
|
|
}
|
|
|
|
ModuleEnv CreateModuleEnv();
|
|
|
|
ExecutionTier execution_tier() const { return execution_tier_; }
|
|
|
|
RuntimeExceptionSupport runtime_exception_support() const {
|
|
return runtime_exception_support_;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<WasmModule> test_module_;
|
|
WasmModule* test_module_ptr_;
|
|
Isolate* isolate_;
|
|
WasmFeatures enabled_features_;
|
|
uint32_t global_offset = 0;
|
|
byte* mem_start_ = nullptr;
|
|
uint32_t mem_size_ = 0;
|
|
V8_ALIGNED(16) byte globals_data_[kMaxGlobalsSize];
|
|
WasmInterpreter* interpreter_ = nullptr;
|
|
ExecutionTier execution_tier_;
|
|
Handle<WasmInstanceObject> instance_object_;
|
|
NativeModule* native_module_ = nullptr;
|
|
bool linked_ = false;
|
|
RuntimeExceptionSupport runtime_exception_support_;
|
|
LowerSimd lower_simd_;
|
|
|
|
const WasmGlobal* AddGlobal(ValueType type);
|
|
|
|
Handle<WasmInstanceObject> InitInstanceObject();
|
|
};
|
|
|
|
void TestBuildingGraph(Zone* zone, compiler::JSGraph* jsgraph,
|
|
ModuleEnv* module, FunctionSig* sig,
|
|
compiler::SourcePositionTable* source_position_table,
|
|
const byte* start, const byte* end);
|
|
|
|
class WasmFunctionWrapper : private compiler::GraphAndBuilders {
|
|
public:
|
|
WasmFunctionWrapper(Zone* zone, int num_params);
|
|
|
|
void Init(CallDescriptor* call_descriptor, MachineType return_type,
|
|
Vector<MachineType> param_types);
|
|
|
|
template <typename ReturnType, typename... ParamTypes>
|
|
void Init(CallDescriptor* call_descriptor) {
|
|
std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
|
|
{MachineTypeForC<ParamTypes>()...}};
|
|
Vector<MachineType> param_vec(param_machine_types.data(),
|
|
param_machine_types.size());
|
|
Init(call_descriptor, MachineTypeForC<ReturnType>(), param_vec);
|
|
}
|
|
|
|
void SetInnerCode(WasmCode* code) {
|
|
intptr_t address = static_cast<intptr_t>(code->instruction_start());
|
|
compiler::NodeProperties::ChangeOp(
|
|
inner_code_node_,
|
|
kPointerSize == 8
|
|
? common()->RelocatableInt64Constant(address,
|
|
RelocInfo::JS_TO_WASM_CALL)
|
|
: common()->RelocatableInt32Constant(static_cast<int>(address),
|
|
RelocInfo::JS_TO_WASM_CALL));
|
|
}
|
|
|
|
const compiler::Operator* IntPtrConstant(intptr_t value) {
|
|
return machine()->Is32()
|
|
? common()->Int32Constant(static_cast<int32_t>(value))
|
|
: common()->Int64Constant(static_cast<int64_t>(value));
|
|
}
|
|
|
|
void SetInstance(Handle<WasmInstanceObject> instance) {
|
|
compiler::NodeProperties::ChangeOp(context_address_,
|
|
common()->HeapConstant(instance));
|
|
}
|
|
|
|
Handle<Code> GetWrapperCode();
|
|
|
|
Signature<MachineType>* signature() const { return signature_; }
|
|
|
|
private:
|
|
Node* inner_code_node_;
|
|
Node* context_address_;
|
|
MaybeHandle<Code> code_;
|
|
Signature<MachineType>* signature_;
|
|
};
|
|
|
|
// A helper for compiling wasm functions for testing.
|
|
// It contains the internal state for compilation (i.e. TurboFan graph) and
|
|
// interpretation (by adding to the interpreter manually).
|
|
class WasmFunctionCompiler : public compiler::GraphAndBuilders {
|
|
public:
|
|
~WasmFunctionCompiler();
|
|
|
|
Isolate* isolate() { return builder_->isolate(); }
|
|
CallDescriptor* descriptor() {
|
|
if (descriptor_ == nullptr) {
|
|
descriptor_ = compiler::GetWasmCallDescriptor(zone(), sig);
|
|
}
|
|
return descriptor_;
|
|
}
|
|
uint32_t function_index() { return function_->func_index; }
|
|
|
|
void Build(const byte* start, const byte* end);
|
|
|
|
byte AllocateLocal(ValueType type) {
|
|
uint32_t index = local_decls.AddLocals(1, type);
|
|
byte result = static_cast<byte>(index);
|
|
DCHECK_EQ(index, result);
|
|
return result;
|
|
}
|
|
|
|
void SetSigIndex(int sig_index) { function_->sig_index = sig_index; }
|
|
|
|
private:
|
|
friend class WasmRunnerBase;
|
|
|
|
WasmFunctionCompiler(Zone* zone, FunctionSig* sig,
|
|
TestingModuleBuilder* builder, const char* name);
|
|
|
|
compiler::JSGraph jsgraph;
|
|
FunctionSig* sig;
|
|
// The call descriptor is initialized when the function is compiled.
|
|
CallDescriptor* descriptor_;
|
|
TestingModuleBuilder* builder_;
|
|
WasmFunction* function_;
|
|
LocalDeclEncoder local_decls;
|
|
compiler::SourcePositionTable source_position_table_;
|
|
WasmInterpreter* interpreter_;
|
|
};
|
|
|
|
// A helper class to build a module around Wasm bytecode, generate machine
|
|
// code, and run that code.
|
|
class WasmRunnerBase : public HandleAndZoneScope {
|
|
public:
|
|
WasmRunnerBase(ManuallyImportedJSFunction* maybe_import,
|
|
ExecutionTier execution_tier, int num_params,
|
|
RuntimeExceptionSupport runtime_exception_support,
|
|
LowerSimd lower_simd)
|
|
: zone_(&allocator_, ZONE_NAME),
|
|
builder_(&zone_, maybe_import, execution_tier,
|
|
runtime_exception_support, lower_simd),
|
|
wrapper_(&zone_, num_params) {}
|
|
|
|
// 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) {
|
|
CHECK(!compiled_);
|
|
compiled_ = true;
|
|
functions_[0]->Build(start, end);
|
|
}
|
|
|
|
// Resets the state for building the next function.
|
|
// The main function called will always be the first function.
|
|
template <typename ReturnType, typename... ParamTypes>
|
|
WasmFunctionCompiler& NewFunction(const char* name = nullptr) {
|
|
return NewFunction(CreateSig<ReturnType, ParamTypes...>(), name);
|
|
}
|
|
|
|
// Resets the state for building the next function.
|
|
// The main function called will be the last generated function.
|
|
// Returns the index of the previously built function.
|
|
WasmFunctionCompiler& NewFunction(FunctionSig* sig,
|
|
const char* name = nullptr) {
|
|
functions_.emplace_back(
|
|
new WasmFunctionCompiler(&zone_, sig, &builder_, name));
|
|
return *functions_.back();
|
|
}
|
|
|
|
byte AllocateLocal(ValueType type) {
|
|
return functions_[0]->AllocateLocal(type);
|
|
}
|
|
|
|
uint32_t function_index() { return functions_[0]->function_index(); }
|
|
WasmFunction* function() { return functions_[0]->function_; }
|
|
WasmInterpreter* interpreter() {
|
|
DCHECK(interpret());
|
|
return functions_[0]->interpreter_;
|
|
}
|
|
bool possible_nondeterminism() { return possible_nondeterminism_; }
|
|
TestingModuleBuilder& builder() { return builder_; }
|
|
Zone* zone() { return &zone_; }
|
|
|
|
bool interpret() { return builder_.interpret(); }
|
|
|
|
template <typename ReturnType, typename... ParamTypes>
|
|
FunctionSig* CreateSig() {
|
|
std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
|
|
{MachineTypeForC<ParamTypes>()...}};
|
|
Vector<MachineType> param_vec(param_machine_types.data(),
|
|
param_machine_types.size());
|
|
return CreateSig(MachineTypeForC<ReturnType>(), param_vec);
|
|
}
|
|
|
|
private:
|
|
FunctionSig* CreateSig(MachineType return_type,
|
|
Vector<MachineType> param_types);
|
|
|
|
protected:
|
|
v8::internal::AccountingAllocator allocator_;
|
|
Zone zone_;
|
|
TestingModuleBuilder builder_;
|
|
std::vector<std::unique_ptr<WasmFunctionCompiler>> functions_;
|
|
WasmFunctionWrapper wrapper_;
|
|
bool compiled_ = false;
|
|
bool possible_nondeterminism_ = false;
|
|
|
|
public:
|
|
// This field has to be static. Otherwise, gcc complains about the use in
|
|
// the lambda context below.
|
|
static bool trap_happened;
|
|
};
|
|
|
|
template <typename ReturnType, typename... ParamTypes>
|
|
class WasmRunner : public WasmRunnerBase {
|
|
public:
|
|
WasmRunner(ExecutionTier execution_tier,
|
|
ManuallyImportedJSFunction* maybe_import = nullptr,
|
|
const char* main_fn_name = "main",
|
|
RuntimeExceptionSupport runtime_exception_support =
|
|
kNoRuntimeExceptionSupport,
|
|
LowerSimd lower_simd = kNoLowerSimd)
|
|
: WasmRunnerBase(maybe_import, execution_tier, sizeof...(ParamTypes),
|
|
runtime_exception_support, lower_simd) {
|
|
NewFunction<ReturnType, ParamTypes...>(main_fn_name);
|
|
if (!interpret()) {
|
|
wrapper_.Init<ReturnType, ParamTypes...>(functions_[0]->descriptor());
|
|
}
|
|
}
|
|
|
|
WasmRunner(ExecutionTier execution_tier, LowerSimd lower_simd)
|
|
: WasmRunner(execution_tier, nullptr, "main", kNoRuntimeExceptionSupport,
|
|
lower_simd) {}
|
|
|
|
ReturnType Call(ParamTypes... p) {
|
|
DCHECK(compiled_);
|
|
if (interpret()) return CallInterpreter(p...);
|
|
|
|
ReturnType return_value = static_cast<ReturnType>(0xDEADBEEFDEADBEEF);
|
|
WasmRunnerBase::trap_happened = false;
|
|
|
|
auto trap_callback = []() -> void {
|
|
WasmRunnerBase::trap_happened = true;
|
|
set_trap_callback_for_testing(nullptr);
|
|
};
|
|
set_trap_callback_for_testing(trap_callback);
|
|
|
|
wrapper_.SetInnerCode(builder_.GetFunctionCode(0));
|
|
wrapper_.SetInstance(builder_.instance_object());
|
|
builder_.Link();
|
|
Handle<Code> wrapper_code = wrapper_.GetWrapperCode();
|
|
compiler::CodeRunner<int32_t> runner(CcTest::InitIsolateOnce(),
|
|
wrapper_code, wrapper_.signature());
|
|
int32_t result;
|
|
{
|
|
trap_handler::SetThreadInWasm();
|
|
|
|
result = runner.Call(static_cast<void*>(&p)...,
|
|
static_cast<void*>(&return_value));
|
|
|
|
trap_handler::ClearThreadInWasm();
|
|
}
|
|
CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result);
|
|
return WasmRunnerBase::trap_happened
|
|
? static_cast<ReturnType>(0xDEADBEEFDEADBEEF)
|
|
: return_value;
|
|
}
|
|
|
|
ReturnType CallInterpreter(ParamTypes... p) {
|
|
WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
|
|
thread->Reset();
|
|
std::array<WasmValue, sizeof...(p)> args{{WasmValue(p)...}};
|
|
thread->InitFrame(function(), args.data());
|
|
if (thread->Run() == WasmInterpreter::FINISHED) {
|
|
WasmValue val = thread->GetReturnValue();
|
|
possible_nondeterminism_ |= thread->PossibleNondeterminism();
|
|
return val.to<ReturnType>();
|
|
} else if (thread->state() == WasmInterpreter::TRAPPED) {
|
|
// TODO(titzer): return the correct trap code
|
|
int64_t result = 0xDEADBEEFDEADBEEF;
|
|
return static_cast<ReturnType>(result);
|
|
} else {
|
|
// TODO(titzer): falling off end
|
|
return ReturnType{0};
|
|
}
|
|
}
|
|
|
|
Handle<Code> GetWrapperCode() { return wrapper_.GetWrapperCode(); }
|
|
};
|
|
|
|
// A macro to define tests that run in different engine configurations.
|
|
#define WASM_EXEC_TEST(name) \
|
|
void RunWasm_##name(ExecutionTier execution_tier); \
|
|
TEST(RunWasmTurbofan_##name) { RunWasm_##name(ExecutionTier::kOptimized); } \
|
|
TEST(RunWasmLiftoff_##name) { RunWasm_##name(ExecutionTier::kBaseline); } \
|
|
TEST(RunWasmInterpreter_##name) { \
|
|
RunWasm_##name(ExecutionTier::kInterpreter); \
|
|
} \
|
|
void RunWasm_##name(ExecutionTier execution_tier)
|
|
|
|
#define WASM_COMPILED_EXEC_TEST(name) \
|
|
void RunWasm_##name(ExecutionTier execution_tier); \
|
|
TEST(RunWasmTurbofan_##name) { RunWasm_##name(ExecutionTier::kOptimized); } \
|
|
TEST(RunWasmLiftoff_##name) { RunWasm_##name(ExecutionTier::kBaseline); } \
|
|
void RunWasm_##name(ExecutionTier execution_tier)
|
|
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|
|
|
|
#endif
|