Commit c39c6eba authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] [debug] Implement calling imported wasm functions

The interpreter was not able to call imported wasm functions (hitting
UNIMPLEMENTED). This CL fixes this by creating a "CWasmEntry", which is
signature-specific. It has JS linkage and receives the wasm code object
to call and a buffer containing all arguments (similar to the
interpreter entry). It loads all arguments from the buffer and calls the
given code object.
The c-wasm-entry code objects are cached per instance, such that we
only create them once per signature.

These wasm entry stubs will also allow us to call back to compiled code
from the interpreter, which we might want to do to reduce the slowdown
of executing wasm for debugging.

R=titzer@chromium.org

Bug: chromium:735792
Change-Id: I7fecec3a7bec62a9de40fff115b684759b12a28b
Reviewed-on: https://chromium-review.googlesource.com/600308
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarBen Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47195}
parent b1b595b0
......@@ -2955,6 +2955,75 @@ void WasmGraphBuilder::BuildWasmInterpreterEntry(
if (HasInt64ParamOrReturn(sig_)) LowerInt64();
}
void WasmGraphBuilder::BuildCWasmEntry() {
// Build the start and the JS parameter nodes.
Node* start = Start(CWasmEntryParameters::kNumParameters + 5);
*control_ = start;
*effect_ = start;
// Create parameter nodes (offset by 1 for the receiver parameter).
Node* code_obj = Param(CWasmEntryParameters::kCodeObject + 1);
Node* arg_buffer = Param(CWasmEntryParameters::kArgumentsBuffer + 1);
// Set the ThreadInWasm flag before we do the actual call.
BuildModifyThreadInWasmFlag(true);
int wasm_arg_count = static_cast<int>(sig_->parameter_count());
int arg_count = wasm_arg_count + 3; // args + code, control, effect
Node** args = Buffer(arg_count);
int pos = 0;
args[pos++] = code_obj;
int offset = 0;
for (wasm::ValueType type : sig_->parameters()) {
Node* arg_load =
graph()->NewNode(GetSafeLoadOperator(offset, type), arg_buffer,
Int32Constant(offset), *effect_, *control_);
*effect_ = arg_load;
args[pos++] = arg_load;
offset += 1 << ElementSizeLog2Of(type);
}
args[pos++] = *effect_;
args[pos++] = *control_;
DCHECK_EQ(arg_count, pos);
// Call the wasm code.
CallDescriptor* desc = GetWasmCallDescriptor(jsgraph()->zone(), sig_);
Node* call =
graph()->NewNode(jsgraph()->common()->Call(desc), arg_count, args);
*effect_ = call;
// Clear the ThreadInWasmFlag
BuildModifyThreadInWasmFlag(false);
// Store the return value.
DCHECK_GE(1, sig_->return_count());
if (sig_->return_count() == 1) {
StoreRepresentation store_rep(sig_->GetReturn(), kNoWriteBarrier);
Node* store =
graph()->NewNode(jsgraph()->machine()->Store(store_rep), arg_buffer,
Int32Constant(0), call, *effect_, *control_);
*effect_ = store;
}
Return(jsgraph()->SmiConstant(0));
if (jsgraph()->machine()->Is32() && HasInt64ParamOrReturn(sig_)) {
MachineRepresentation sig_reps[] = {
MachineRepresentation::kWord32, // return value
MachineRepresentation::kTagged, // receiver
MachineRepresentation::kTagged, // arg0 (code)
MachineRepresentation::kTagged // arg1 (buffer)
};
wasm::FunctionSig c_entry_sig(1, 2, sig_reps);
Int64Lowering r(jsgraph()->graph(), jsgraph()->machine(),
jsgraph()->common(), jsgraph()->zone(), &c_entry_sig);
r.LowerGraph();
}
}
Node* WasmGraphBuilder::MemBuffer(uint32_t offset) {
DCHECK_NOT_NULL(module_);
uintptr_t mem_start = reinterpret_cast<uintptr_t>(
......@@ -3186,6 +3255,16 @@ void WasmGraphBuilder::BoundsCheckMem(MachineType memtype, Node* index,
TrapIfFalse(wasm::kTrapMemOutOfBounds, cond, position);
}
const Operator* WasmGraphBuilder::GetSafeLoadOperator(int offset,
wasm::ValueType type) {
int alignment = offset % (1 << ElementSizeLog2Of(type));
MachineType mach_type = wasm::WasmOpcodes::MachineTypeFor(type);
if (alignment == 0 || jsgraph()->machine()->UnalignedLoadSupported(type)) {
return jsgraph()->machine()->Load(mach_type);
}
return jsgraph()->machine()->UnalignedLoad(mach_type);
}
const Operator* WasmGraphBuilder::GetSafeStoreOperator(int offset,
wasm::ValueType type) {
int alignment = offset % (1 << ElementSizeLog2Of(type));
......@@ -4048,6 +4127,63 @@ Handle<Code> CompileWasmInterpreterEntry(Isolate* isolate, uint32_t func_index,
return code;
}
Handle<Code> CompileCWasmEntry(Isolate* isolate, wasm::FunctionSig* sig) {
Zone zone(isolate->allocator(), ZONE_NAME);
Graph graph(&zone);
CommonOperatorBuilder common(&zone);
MachineOperatorBuilder machine(&zone);
JSGraph jsgraph(isolate, &graph, &common, nullptr, nullptr, &machine);
Node* control = nullptr;
Node* effect = nullptr;
WasmGraphBuilder builder(nullptr, &zone, &jsgraph,
CEntryStub(isolate, 1).GetCode(), sig);
builder.set_control_ptr(&control);
builder.set_effect_ptr(&effect);
builder.BuildCWasmEntry();
if (FLAG_trace_turbo_graph) { // Simple textual RPO.
OFStream os(stdout);
os << "-- C Wasm entry graph -- " << std::endl;
os << AsRPO(graph);
}
// Schedule and compile to machine code.
CallDescriptor* incoming = Linkage::GetJSCallDescriptor(
&zone, false, CWasmEntryParameters::kNumParameters + 1,
CallDescriptor::kNoFlags);
Code::Flags flags = Code::ComputeFlags(Code::C_WASM_ENTRY);
// Build a name in the form "c-wasm-entry:<params>:<returns>".
static constexpr size_t kMaxNameLen = 128;
char debug_name[kMaxNameLen] = "c-wasm-entry:";
size_t name_len = strlen(debug_name);
auto append_name_char = [&](char c) {
if (name_len + 1 < kMaxNameLen) debug_name[name_len++] = c;
};
for (wasm::ValueType t : sig->parameters()) {
append_name_char(wasm::WasmOpcodes::ShortNameOf(t));
}
append_name_char(':');
for (wasm::ValueType t : sig->returns()) {
append_name_char(wasm::WasmOpcodes::ShortNameOf(t));
}
debug_name[name_len] = '\0';
Vector<const char> debug_name_vec(debug_name, name_len);
CompilationInfo info(debug_name_vec, isolate, &zone, flags);
Handle<Code> code = Pipeline::GenerateCodeForTesting(&info, incoming, &graph);
#ifdef ENABLE_DISASSEMBLER
if (FLAG_print_opt_code && !code.is_null()) {
OFStream os(stdout);
code->Disassemble(debug_name, os);
}
#endif
return code;
}
SourcePositionTable* WasmCompilationUnit::BuildGraphForWasmFunction(
double* decode_ms) {
base::ElapsedTimer decode_timer;
......
......@@ -127,6 +127,18 @@ Handle<Code> CompileWasmInterpreterEntry(Isolate* isolate, uint32_t func_index,
wasm::FunctionSig* sig,
Handle<WasmInstanceObject> instance);
enum CWasmEntryParameters {
kCodeObject,
kArgumentsBuffer,
// marker:
kNumParameters
};
// Compiles a stub with JS linkage, taking parameters as described by
// {CWasmEntryParameters}. It loads the wasm parameters from the argument
// buffer and calls the wasm function given as first parameter.
Handle<Code> CompileCWasmEntry(Isolate* isolate, wasm::FunctionSig* sig);
// Abstracts details of building TurboFan graph nodes for wasm to separate
// the wasm decoder from the internal details of TurboFan.
typedef ZoneVector<Node*> NodeVector;
......@@ -227,6 +239,7 @@ class WasmGraphBuilder {
int index);
void BuildWasmInterpreterEntry(uint32_t func_index,
Handle<WasmInstanceObject> instance);
void BuildCWasmEntry();
Node* ToJS(Node* node, wasm::ValueType type);
Node* FromJS(Node* node, Node* context, wasm::ValueType type);
......@@ -325,6 +338,7 @@ class WasmGraphBuilder {
Node* MemBuffer(uint32_t offset);
void BoundsCheckMem(MachineType memtype, Node* index, uint32_t offset,
wasm::WasmCodePosition position);
const Operator* GetSafeLoadOperator(int offset, wasm::ValueType type);
const Operator* GetSafeStoreOperator(int offset, wasm::ValueType type);
Node* BuildChangeEndiannessStore(Node* node, MachineType type,
wasm::ValueType wasmtype = wasm::kWasmStmt);
......
......@@ -230,6 +230,9 @@ inline WasmToJsFrame::WasmToJsFrame(StackFrameIteratorBase* iterator)
inline JsToWasmFrame::JsToWasmFrame(StackFrameIteratorBase* iterator)
: StubFrame(iterator) {}
inline CWasmEntryFrame::CWasmEntryFrame(StackFrameIteratorBase* iterator)
: StubFrame(iterator) {}
inline InternalFrame::InternalFrame(StackFrameIteratorBase* iterator)
: StandardFrame(iterator) {
}
......
......@@ -490,6 +490,8 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
return JS_TO_WASM;
case Code::WASM_INTERPRETER_ENTRY:
return WASM_INTERPRETER_ENTRY;
case Code::C_WASM_ENTRY:
return C_WASM_ENTRY;
default:
// All other types should have an explicit marker
break;
......@@ -816,6 +818,7 @@ void StandardFrame::IterateCompiledFrame(RootVisitor* v) const {
case WASM_TO_JS:
case WASM_COMPILED:
case WASM_INTERPRETER_ENTRY:
case C_WASM_ENTRY:
frame_header_size = TypedFrameConstants::kFixedFrameSizeFromFp;
break;
case JAVA_SCRIPT:
......
......@@ -94,6 +94,7 @@ class StackHandler BASE_EMBEDDED {
V(WASM_TO_JS, WasmToJsFrame) \
V(JS_TO_WASM, JsToWasmFrame) \
V(WASM_INTERPRETER_ENTRY, WasmInterpreterEntryFrame) \
V(C_WASM_ENTRY, CWasmEntryFrame) \
V(INTERPRETED, InterpretedFrame) \
V(STUB, StubFrame) \
V(BUILTIN_CONTINUATION, BuiltinContinuationFrame) \
......@@ -1051,6 +1052,17 @@ class JsToWasmFrame : public StubFrame {
friend class StackFrameIteratorBase;
};
class CWasmEntryFrame : public StubFrame {
public:
Type type() const override { return C_WASM_ENTRY; }
protected:
inline explicit CWasmEntryFrame(StackFrameIteratorBase* iterator);
private:
friend class StackFrameIteratorBase;
};
class InternalFrame: public StandardFrame {
public:
Type type() const override { return INTERNAL; }
......
......@@ -1667,6 +1667,10 @@ void Logger::LogCodeObject(Object* object) {
description = "A Wasm to Interpreter adapter";
tag = CodeEventListener::STUB_TAG;
break;
case AbstractCode::C_WASM_ENTRY:
description = "A C to Wasm entry stub";
tag = CodeEventListener::STUB_TAG;
break;
case AbstractCode::NUMBER_OF_KINDS:
UNIMPLEMENTED();
}
......
......@@ -3667,7 +3667,8 @@ class Code: public HeapObject {
V(WASM_FUNCTION) \
V(WASM_TO_JS_FUNCTION) \
V(JS_TO_WASM_FUNCTION) \
V(WASM_INTERPRETER_ENTRY)
V(WASM_INTERPRETER_ENTRY) \
V(C_WASM_ENTRY)
#define IC_KIND_LIST(V) \
V(LOAD_IC) \
......
......@@ -621,6 +621,7 @@ void RedirectCallsitesInInstance(Isolate* isolate, WasmInstanceObject* instance,
for (int i = 0, e = GetNumFunctions(instance); i < e; ++i) {
RedirectCallsitesInCode(Code::cast(code_table->get(i)), map);
}
// TODO(6668): Find instances that imported our code and also patch those.
// Redirect all calls in exported functions.
FixedArray* weak_exported_functions =
......@@ -774,3 +775,44 @@ Handle<JSObject> WasmDebugInfo::GetLocalScopeObject(
auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index);
return interp_handle->GetLocalScopeObject(frame.get(), debug_info);
}
// static
Handle<JSFunction> WasmDebugInfo::GetCWasmEntry(
Handle<WasmDebugInfo> debug_info, FunctionSig* sig) {
Isolate* isolate = debug_info->GetIsolate();
DCHECK_EQ(debug_info->has_c_wasm_entries(),
debug_info->has_c_wasm_entry_map());
if (!debug_info->has_c_wasm_entries()) {
auto entries = isolate->factory()->NewFixedArray(4, TENURED);
debug_info->set_c_wasm_entries(*entries);
auto managed_map =
Managed<wasm::SignatureMap>::New(isolate, new wasm::SignatureMap());
debug_info->set_c_wasm_entry_map(*managed_map);
}
Handle<FixedArray> entries(debug_info->c_wasm_entries(), isolate);
wasm::SignatureMap* map = debug_info->c_wasm_entry_map()->get();
int32_t index = map->Find(sig);
if (index == -1) {
index = static_cast<int32_t>(map->FindOrInsert(sig));
if (index == entries->length()) {
entries = isolate->factory()->CopyFixedArrayAndGrow(
entries, entries->length(), TENURED);
debug_info->set_c_wasm_entries(*entries);
}
DCHECK(entries->get(index)->IsUndefined(isolate));
Handle<Code> new_entry_code = compiler::CompileCWasmEntry(isolate, sig);
Handle<String> name = isolate->factory()->InternalizeOneByteString(
STATIC_CHAR_VECTOR("c-wasm-entry"));
Handle<SharedFunctionInfo> shared =
isolate->factory()->NewSharedFunctionInfo(name, new_entry_code, false);
shared->set_internal_formal_parameter_count(
compiler::CWasmEntryParameters::kNumParameters);
Handle<JSFunction> new_entry = isolate->factory()->NewFunction(
isolate->sloppy_function_map(), name, new_entry_code);
new_entry->set_context(
*debug_info->wasm_instance()->compiled_module()->native_context());
new_entry->set_shared(*shared);
entries->set(index, *new_entry);
}
return handle(JSFunction::cast(entries->get(index)));
}
......@@ -7,6 +7,7 @@
#include "src/wasm/wasm-interpreter.h"
#include "src/assembler-inl.h"
#include "src/compiler/wasm-compiler.h"
#include "src/conversions.h"
#include "src/identity-map.h"
#include "src/objects-inl.h"
......@@ -2073,26 +2074,10 @@ class ThreadImpl {
return {ExternalCallResult::EXTERNAL_RETURNED};
}
ExternalCallResult CallCodeObject(Isolate* isolate, Handle<Code> code,
// TODO(clemensh): Remove this, call JS via existing wasm-to-js wrapper, using
// CallExternalWasmFunction.
ExternalCallResult CallExternalJSFunction(Isolate* isolate, Handle<Code> code,
FunctionSig* signature) {
DCHECK(AllowHandleAllocation::IsAllowed());
DCHECK(AllowHeapAllocation::IsAllowed());
if (code->kind() == Code::WASM_FUNCTION) {
FixedArray* deopt_data = code->deoptimization_data();
DCHECK_EQ(2, deopt_data->length());
WasmInstanceObject* target_instance =
WasmInstanceObject::cast(WeakCell::cast(deopt_data->get(0))->value());
if (target_instance != codemap()->instance()) {
// TODO(wasm): Implement calling functions of other instances/modules.
UNIMPLEMENTED();
}
int target_func_idx = Smi::ToInt(deopt_data->get(1));
DCHECK_LE(0, target_func_idx);
return {ExternalCallResult::INTERNAL,
codemap()->GetCode(target_func_idx)};
}
Handle<HeapObject> target = UnwrapWasmToJSWrapper(isolate, code);
if (target.is_null()) {
......@@ -2133,6 +2118,7 @@ class ThreadImpl {
Handle<Object> retval = maybe_retval.ToHandleChecked();
// Pop arguments off the stack.
sp_ -= num_args;
// Push return values.
if (signature->return_count() > 0) {
// TODO(wasm): Handle multiple returns.
DCHECK_EQ(1, signature->return_count());
......@@ -2141,6 +2127,114 @@ class ThreadImpl {
return {ExternalCallResult::EXTERNAL_RETURNED};
}
ExternalCallResult CallExternalWasmFunction(Isolate* isolate,
Handle<Code> code,
FunctionSig* sig) {
Handle<WasmDebugInfo> debug_info(codemap()->instance()->debug_info(),
isolate);
Handle<JSFunction> wasm_entry =
WasmDebugInfo::GetCWasmEntry(debug_info, sig);
TRACE(" => Calling external wasm function\n");
// Copy the arguments to one buffer.
// TODO(clemensh): Introduce a helper for all argument buffer
// con-/destruction.
int num_args = static_cast<int>(sig->parameter_count());
std::vector<uint8_t> arg_buffer(num_args * 8);
size_t offset = 0;
WasmValue* wasm_args = sp_ - num_args;
for (int i = 0; i < num_args; ++i) {
uint32_t param_size = 1 << ElementSizeLog2Of(sig->GetParam(i));
if (arg_buffer.size() < offset + param_size) {
arg_buffer.resize(std::max(2 * arg_buffer.size(), offset + param_size));
}
switch (sig->GetParam(i)) {
case kWasmI32:
WriteUnalignedValue(arg_buffer.data() + offset,
wasm_args[i].to<uint32_t>());
break;
case kWasmI64:
WriteUnalignedValue(arg_buffer.data() + offset,
wasm_args[i].to<uint64_t>());
break;
case kWasmF32:
WriteUnalignedValue(arg_buffer.data() + offset,
wasm_args[i].to<float>());
break;
case kWasmF64:
WriteUnalignedValue(arg_buffer.data() + offset,
wasm_args[i].to<double>());
break;
default:
UNIMPLEMENTED();
}
offset += param_size;
}
// Wrap the arg_buffer data pointer in a handle. As this is an aligned
// pointer, to the GC it will look like a Smi.
Handle<Object> arg_buffer_obj(reinterpret_cast<Object*>(arg_buffer.data()),
isolate);
DCHECK(!arg_buffer_obj->IsHeapObject());
Handle<Object> args[compiler::CWasmEntryParameters::kNumParameters];
args[compiler::CWasmEntryParameters::kCodeObject] = code;
args[compiler::CWasmEntryParameters::kArgumentsBuffer] = arg_buffer_obj;
Handle<Object> receiver = isolate->factory()->undefined_value();
MaybeHandle<Object> maybe_retval =
Execution::Call(isolate, wasm_entry, receiver, arraysize(args), args);
if (maybe_retval.is_null()) return TryHandleException(isolate);
// Pop arguments off the stack.
sp_ -= num_args;
// Push return values.
if (sig->return_count() > 0) {
// TODO(wasm): Handle multiple returns.
DCHECK_EQ(1, sig->return_count());
switch (sig->GetReturn()) {
case kWasmI32:
Push(WasmValue(ReadUnalignedValue<uint32_t>(arg_buffer.data())));
break;
case kWasmI64:
Push(WasmValue(ReadUnalignedValue<uint64_t>(arg_buffer.data())));
break;
case kWasmF32:
Push(WasmValue(ReadUnalignedValue<float>(arg_buffer.data())));
break;
case kWasmF64:
Push(WasmValue(ReadUnalignedValue<double>(arg_buffer.data())));
break;
default:
UNIMPLEMENTED();
}
}
return {ExternalCallResult::EXTERNAL_RETURNED};
}
ExternalCallResult CallCodeObject(Isolate* isolate, Handle<Code> code,
FunctionSig* signature) {
DCHECK(AllowHandleAllocation::IsAllowed());
DCHECK(AllowHeapAllocation::IsAllowed());
if (code->kind() == Code::WASM_FUNCTION) {
FixedArray* deopt_data = code->deoptimization_data();
DCHECK_EQ(2, deopt_data->length());
WasmInstanceObject* target_instance =
WasmInstanceObject::cast(WeakCell::cast(deopt_data->get(0))->value());
if (target_instance != codemap()->instance()) {
return CallExternalWasmFunction(isolate, code, signature);
}
int target_func_idx = Smi::ToInt(deopt_data->get(1));
DCHECK_LE(0, target_func_idx);
return {ExternalCallResult::INTERNAL,
codemap()->GetCode(target_func_idx)};
}
return CallExternalJSFunction(isolate, code, signature);
}
ExternalCallResult CallImportedFunction(uint32_t function_index) {
// Use a new HandleScope to avoid leaking / accumulating handles in the
// outer scope.
......
......@@ -583,12 +583,16 @@ class WasmDebugInfo : public FixedArray {
DECL_GETTER(wasm_instance, WasmInstanceObject)
DECL_OPTIONAL_ACCESSORS(locals_names, FixedArray)
DECL_OPTIONAL_ACCESSORS(c_wasm_entries, FixedArray)
DECL_OPTIONAL_ACCESSORS(c_wasm_entry_map, Managed<wasm::SignatureMap>)
enum {
kInstanceIndex, // instance object.
kInterpreterHandleIndex, // managed object containing the interpreter.
kInterpretedFunctionsIndex, // array of interpreter entry code objects.
kLocalsNamesIndex, // array of array of local names.
kCWasmEntriesIndex, // array of C_WASM_ENTRY stubs.
kCWasmEntryMapIndex, // maps signature to index into CWasmEntries.
kFieldCount
};
......@@ -597,6 +601,8 @@ class WasmDebugInfo : public FixedArray {
DEF_OFFSET(InterpreterHandle)
DEF_OFFSET(InterpretedFunctions)
DEF_OFFSET(LocalsNames)
DEF_OFFSET(CWasmEntries)
DEF_OFFSET(CWasmEntryMap)
static Handle<WasmDebugInfo> New(Handle<WasmInstanceObject>);
......@@ -663,6 +669,9 @@ class WasmDebugInfo : public FixedArray {
static Handle<JSObject> GetLocalScopeObject(Handle<WasmDebugInfo>,
Address frame_pointer,
int frame_index);
static Handle<JSFunction> GetCWasmEntry(Handle<WasmDebugInfo>,
wasm::FunctionSig*);
};
// TODO(titzer): these should be moved to wasm-objects-inl.h
......@@ -722,6 +731,10 @@ OPTIONAL_ACCESSORS(WasmSharedModuleData, lazy_compilation_orchestrator, Foreign,
kLazyCompilationOrchestratorOffset)
OPTIONAL_ACCESSORS(WasmDebugInfo, locals_names, FixedArray, kLocalsNamesOffset)
OPTIONAL_ACCESSORS(WasmDebugInfo, c_wasm_entries, FixedArray,
kCWasmEntriesOffset)
OPTIONAL_ACCESSORS(WasmDebugInfo, c_wasm_entry_map, Managed<wasm::SignatureMap>,
kCWasmEntryMapOffset)
#undef OPTIONAL_ACCESSORS
#undef DECL_OOL_QUERY
......
......@@ -191,6 +191,7 @@ v8_executable("cctest") {
"trace-extension.h",
"types-fuzz.h",
"unicode-helpers.h",
"wasm/test-c-wasm-entry.cc",
"wasm/test-run-wasm-64.cc",
"wasm/test-run-wasm-asmjs.cc",
"wasm/test-run-wasm-atomics.cc",
......
......@@ -209,6 +209,7 @@
'trace-extension.h',
'types-fuzz.h',
'unicode-helpers.h',
'wasm/test-c-wasm-entry.cc',
'wasm/test-run-wasm.cc',
'wasm/test-run-wasm-64.cc',
'wasm/test-run-wasm-asmjs.cc',
......
// Copyright 2017 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 <cstdint>
#include "src/assembler-inl.h"
#include "src/objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
/**
* We test the interface from C to compiled wasm code by generating a wasm
* function, creating a corresponding signature, compiling the c wasm entry for
* that signature, and then calling that entry using different test values.
* The result is compared against the expected result, computed from a lambda
* passed to the CWasmEntryArgTester.
*/
namespace {
template <typename ReturnType, typename... Args>
class CWasmEntryArgTester {
public:
CWasmEntryArgTester(std::initializer_list<uint8_t> wasm_function_bytes,
std::function<ReturnType(Args...)> expected_fn)
: runner_(kExecuteCompiled),
isolate_(runner_.main_isolate()),
expected_fn_(expected_fn),
sig_(runner_.template CreateSig<ReturnType, Args...>()) {
std::vector<uint8_t> code{wasm_function_bytes};
runner_.Build(code.data(), code.data() + code.size());
wasm_code_ = runner_.module().GetFunctionCode(0);
Handle<WasmInstanceObject> instance(runner_.module().instance_object());
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
c_wasm_entry_fn_ = WasmDebugInfo::GetCWasmEntry(debug_info, sig_);
}
template <typename... Rest>
void WriteToBuffer(uint8_t* buf, Rest... rest) {
static_assert(sizeof...(rest) == 0, "this is the base case");
}
template <typename First, typename... Rest>
void WriteToBuffer(uint8_t* buf, First first, Rest... rest) {
WriteUnalignedValue(buf, first);
WriteToBuffer(buf + sizeof(first), rest...);
}
void CheckCall(Args... args) {
std::vector<uint8_t> arg_buffer(sizeof...(args) * 8);
WriteToBuffer(arg_buffer.data(), args...);
Handle<Object> receiver = isolate_->factory()->undefined_value();
Handle<Object> buffer_obj(reinterpret_cast<Object*>(arg_buffer.data()),
isolate_);
CHECK(!buffer_obj->IsHeapObject());
Handle<Object> call_args[]{wasm_code_, buffer_obj};
static_assert(
arraysize(call_args) == compiler::CWasmEntryParameters::kNumParameters,
"adapt this test");
MaybeHandle<Object> return_obj = Execution::Call(
isolate_, c_wasm_entry_fn_, receiver, arraysize(call_args), call_args);
CHECK(!return_obj.is_null());
CHECK(return_obj.ToHandleChecked()->IsSmi());
CHECK_EQ(0, Smi::ToInt(*return_obj.ToHandleChecked()));
// Check the result.
ReturnType result = ReadUnalignedValue<ReturnType>(arg_buffer.data());
ReturnType expected = expected_fn_(args...);
if (std::is_floating_point<ReturnType>::value) {
CHECK_DOUBLE_EQ(expected, result);
} else {
CHECK_EQ(expected, result);
}
}
private:
WasmRunner<ReturnType, Args...> runner_;
Isolate* isolate_;
std::function<ReturnType(Args...)> expected_fn_;
FunctionSig* sig_;
Handle<JSFunction> c_wasm_entry_fn_;
Handle<Code> wasm_code_;
};
} // namespace
// Pass int32_t, return int32_t.
TEST(TestCWasmEntryArgPassing_int32) {
CWasmEntryArgTester<int32_t, int32_t> tester(
{// Return 2*<0> + 1.
WASM_I32_ADD(WASM_I32_MUL(WASM_I32V_1(2), WASM_GET_LOCAL(0)), WASM_ONE)},
[](int32_t a) { return 2 * a + 1; });
std::vector<int32_t> test_values = compiler::ValueHelper::int32_vector();
for (int32_t v : test_values) tester.CheckCall(v);
}
// Pass int64_t, return double.
TEST(TestCWasmEntryArgPassing_double_int64) {
CWasmEntryArgTester<double, int64_t> tester(
{// Return (double)<0>.
WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(0))},
[](int64_t a) { return static_cast<double>(a); });
std::vector<int64_t> test_values_i64 = compiler::ValueHelper::int64_vector();
for (int64_t v : test_values_i64) {
tester.CheckCall(v);
}
}
// Pass double, return int64_t.
TEST(TestCWasmEntryArgPassing_int64_double) {
CWasmEntryArgTester<int64_t, double> tester(
{// Return (int64_t)<0>.
WASM_I64_SCONVERT_F64(WASM_GET_LOCAL(0))},
[](double d) { return static_cast<int64_t>(d); });
for (int64_t i : compiler::ValueHelper::int64_vector()) {
tester.CheckCall(i);
}
}
// Pass float, return double.
TEST(TestCWasmEntryArgPassing_float_double) {
CWasmEntryArgTester<double, float> tester(
{// Return 2*(double)<0> + 1.
WASM_F64_ADD(
WASM_F64_MUL(WASM_F64(2), WASM_F64_CONVERT_F32(WASM_GET_LOCAL(0))),
WASM_F64(1))},
[](float f) { return 2. * static_cast<double>(f) + 1.; });
std::vector<float> test_values = compiler::ValueHelper::float32_vector();
for (float f : test_values) tester.CheckCall(f);
}
// Pass two doubles, return double.
TEST(TestCWasmEntryArgPassing_double_double) {
CWasmEntryArgTester<double, double, double> tester(
{// Return <0> + <1>.
WASM_F64_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))},
[](double a, double b) { return a + b; });
std::vector<double> test_values = compiler::ValueHelper::float64_vector();
for (double d1 : test_values) {
for (double d2 : test_values) {
tester.CheckCall(d1, d2);
}
}
}
// Pass int32_t, int64_t, float and double, return double.
TEST(TestCWasmEntryArgPassing_AllTypes) {
CWasmEntryArgTester<double, int32_t, int64_t, float, double> tester(
{
// Convert all arguments to double, add them and return the sum.
WASM_F64_ADD( // <0+1+2> + <3>
WASM_F64_ADD( // <0+1> + <2>
WASM_F64_ADD( // <0> + <1>
WASM_F64_SCONVERT_I32(
WASM_GET_LOCAL(0)), // <0> to double
WASM_F64_SCONVERT_I64(
WASM_GET_LOCAL(1))), // <1> to double
WASM_F64_CONVERT_F32(WASM_GET_LOCAL(2))), // <2> to double
WASM_GET_LOCAL(3)) // <3>
},
[](int32_t a, int64_t b, float c, double d) {
return 0. + a + b + c + d;
});
std::vector<int32_t> test_values_i32 = compiler::ValueHelper::int32_vector();
std::vector<int64_t> test_values_i64 = compiler::ValueHelper::int64_vector();
std::vector<float> test_values_f32 = compiler::ValueHelper::float32_vector();
std::vector<double> test_values_f64 = compiler::ValueHelper::float64_vector();
size_t max_len =
std::max(std::max(test_values_i32.size(), test_values_i64.size()),
std::max(test_values_f32.size(), test_values_f64.size()));
for (size_t i = 0; i < max_len; ++i) {
int32_t i32 = test_values_i32[i % test_values_i32.size()];
int64_t i64 = test_values_i64[i % test_values_i64.size()];
float f32 = test_values_f32[i % test_values_f32.size()];
double f64 = test_values_f64[i % test_values_f64.size()];
tester.CheckCall(i32, i64, f32, f64);
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -713,6 +713,15 @@ class WasmRunnerBase : public HandleAndZoneScope {
bool interpret() { return module_.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) {
......@@ -734,15 +743,6 @@ class WasmRunnerBase : public HandleAndZoneScope {
return new (&zone_) FunctionSig(return_count, param_count, sig_types);
}
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);
}
protected:
v8::internal::AccountingAllocator allocator_;
Zone zone_;
......
......@@ -12,6 +12,20 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
// compiled code.
// =============================================================================
// The stack trace contains file path, replace it by "file".
let stripPath = s => s.replace(/[^ (]*interpreter-mixed\.js/g, 'file');
function checkStack(stack, expected_lines) {
print('stack: ' + stack);
let lines = stack.split('\n');
assertEquals(expected_lines.length, lines.length);
for (let i = 0; i < lines.length; ++i) {
let test =
typeof expected_lines[i] == 'string' ? assertEquals : assertMatches;
test(expected_lines[i], lines[i], 'line ' + i);
}
}
(function testGrowMemoryBetweenInterpretedAndCompiled() {
// grow_memory can be called from interpreted or compiled code, and changes
// should be reflected in either execution.
......@@ -76,3 +90,106 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
// Overall, we executed 9 functions in the interpreter.
assertEquals(initial_interpreted + 9, %WasmNumInterpretedCalls(instance));
})();
function createTwoInstancesCallingEachOther(inner_throws = false) {
let builder1 = new WasmModuleBuilder();
let id_imp = builder1.addImport('q', 'id', kSig_i_i);
let plus_one = builder1.addFunction('plus_one', kSig_i_i)
.addBody([
kExprGetLocal, 0, // -
kExprI32Const, 1, // -
kExprI32Add, // -
kExprCallFunction, id_imp
])
.exportFunc();
function imp(i) {
if (inner_throws) throw new Error('i=' + i);
return i;
}
let instance1 = builder1.instantiate({q: {id: imp}});
let builder2 = new WasmModuleBuilder();
let plus_one_imp = builder2.addImport('q', 'plus_one', kSig_i_i);
let plus_two = builder2.addFunction('plus_two', kSig_i_i)
.addBody([
// Call import, add one more.
kExprGetLocal, 0, // -
kExprCallFunction, plus_one_imp, // -
kExprI32Const, 1, // -
kExprI32Add
])
.exportFunc();
let instance2 =
builder2.instantiate({q: {plus_one: instance1.exports.plus_one}});
return [instance1, instance2];
}
function redirectToInterpreter(
instance1, instance2, redirect_plus_one, redirect_plus_two) {
// Redirect functions to the interpreter.
if (redirect_plus_one) {
%RedirectToWasmInterpreter(instance1,
parseInt(instance1.exports.plus_one.name));
}
if (redirect_plus_two) {
%RedirectToWasmInterpreter(instance2,
parseInt(instance2.exports.plus_two.name));
}
}
(function testImportFromOtherInstance() {
print("testImportFromOtherInstance");
// Three runs: Break in instance 1, break in instance 2, or both.
for (let run = 0; run < 3; ++run) {
print(" - run " + run);
let [instance1, instance2] = createTwoInstancesCallingEachOther();
let interpreted_before_1 = %WasmNumInterpretedCalls(instance1);
let interpreted_before_2 = %WasmNumInterpretedCalls(instance2);
// Call plus_two, which calls plus_one.
assertEquals(9, instance2.exports.plus_two(7));
// Nothing interpreted:
assertEquals(interpreted_before_1, %WasmNumInterpretedCalls(instance1));
assertEquals(interpreted_before_2, %WasmNumInterpretedCalls(instance2));
// Now redirect functions to the interpreter.
redirectToInterpreter(instance1, instance2, run != 1, run != 0);
// Call plus_two, which calls plus_one.
assertEquals(9, instance2.exports.plus_two(7));
// TODO(6668): Fix patching of instances which imported others' code.
//assertEquals(interpreted_before_1 + (run == 1 ? 0 : 1),
// %WasmNumInterpretedCalls(instance1));
assertEquals(interpreted_before_2 + (run == 0 ? 0 : 1),
%WasmNumInterpretedCalls(instance2));
}
})();
(function testStackTraceThroughCWasmEntry() {
print("testStackTraceThroughCWasmEntry");
for (let run = 0; run < 3; ++run) {
print(" - run " + run);
let [instance1, instance2] = createTwoInstancesCallingEachOther(true);
redirectToInterpreter(instance1, instance2, run != 1, run != 0);
try {
// Call plus_two, which calls plus_one.
instance2.exports.plus_two(7);
assertUnreachable('should trap because of unreachable instruction');
} catch (e) {
checkStack(stripPath(e.stack), [
'Error: i=8', // -
/^ at imp \(file:\d+:29\)$/, // -
' at plus_one (wasm-function[1]:6)', // -
' at plus_two (wasm-function[1]:3)', // -
/^ at testStackTraceThroughCWasmEntry \(file:\d+:25\)$/, // -
/^ at file:\d+:3$/
]);
}
}
})();
......@@ -328,6 +328,7 @@ FRAME_MARKERS = (
"WASM_TO_JS",
"JS_TO_WASM",
"WASM_INTERPRETER_ENTRY",
"C_WASM_ENTRY",
"INTERPRETED",
"STUB",
"BUILTIN_CONTINUATION",
......
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