[wasm] Make constructed {WebAssembly.Function} callable.

This makes function objects constructed via the {WebAssembly.Function}
constructor callable directly from JavaScript (not just from within
WebAssembly modules). Semantics are as if the function performed the
transition JS-to-Wasm and then Wasm-to-JS in sequence.

R=clemensh@chromium.org
TEST=mjsunit/wasm/type-reflection
BUG=v8:7742

Change-Id: Ic7dcf36ccfda1b473f2541e49419f4d2ee38bc2c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1720809
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62953}
This commit is contained in:
Michael Starzinger 2019-07-29 12:39:50 +02:00 committed by Commit Bot
parent 695c40f08e
commit ba77172be1
7 changed files with 209 additions and 22 deletions

View File

@ -277,7 +277,7 @@ Node* WasmGraphBuilder::EffectPhi(unsigned count, Node** effects,
}
Node* WasmGraphBuilder::RefNull() {
Node* isolate_root = LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
Node* isolate_root = BuildLoadIsolateRoot();
return LOAD_TAGGED_POINTER(
isolate_root, IsolateData::root_slot_offset(RootIndex::kNullValue));
}
@ -295,6 +295,14 @@ Node* WasmGraphBuilder::NoContextConstant() {
return mcgraph()->IntPtrConstant(0);
}
Node* WasmGraphBuilder::BuildLoadIsolateRoot() {
// The IsolateRoot is loaded from the instance node so that the generated
// code is Isolate independent. This can be overridden by setting a specific
// node in {isolate_root_node_} beforehand.
if (isolate_root_node_.is_set()) return isolate_root_node_.get();
return LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
}
Node* WasmGraphBuilder::Uint32Constant(uint32_t value) {
return mcgraph()->Uint32Constant(value);
}
@ -3326,9 +3334,9 @@ Node* WasmGraphBuilder::BuildCallToRuntimeWithContext(
auto call_descriptor = Linkage::GetRuntimeCallDescriptor(
mcgraph()->zone(), f, fun->nargs, Operator::kNoProperties,
CallDescriptor::kNoFlags);
Node* isolate_root = LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
// The CEntryStub is loaded from the instance_node so that generated code is
// The CEntryStub is loaded from the IsolateRoot so that generated code is
// Isolate independent. At the moment this is only done for CEntryStub(1).
Node* isolate_root = BuildLoadIsolateRoot();
DCHECK_EQ(1, fun->result_size);
auto centry_id =
Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit;
@ -5051,7 +5059,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
? mcgraph()->RelocatableIntPtrConstant(
wasm::WasmCode::kWasmAllocateHeapNumber,
RelocInfo::WASM_STUB_CALL)
: BuildLoadBuiltinFromInstance(Builtins::kAllocateHeapNumber);
: BuildLoadBuiltinFromIsolateRoot(Builtins::kAllocateHeapNumber);
if (!allocate_heap_number_operator_.is_set()) {
auto call_descriptor = Linkage::GetStubCallDescriptor(
mcgraph()->zone(), AllocateHeapNumberDescriptor(), 0,
@ -5108,10 +5116,9 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
return undefined_value_node_.get();
}
Node* BuildLoadBuiltinFromInstance(int builtin_index) {
Node* BuildLoadBuiltinFromIsolateRoot(int builtin_index) {
DCHECK(Builtins::IsBuiltinId(builtin_index));
Node* isolate_root =
LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
Node* isolate_root = BuildLoadIsolateRoot();
return LOAD_TAGGED_POINTER(isolate_root,
IsolateData::builtin_slot_offset(builtin_index));
}
@ -5256,7 +5263,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
(stub_mode_ == StubCallMode::kCallWasmRuntimeStub)
? mcgraph()->RelocatableIntPtrConstant(
wasm::WasmCode::kWasmToNumber, RelocInfo::WASM_STUB_CALL)
: BuildLoadBuiltinFromInstance(Builtins::kToNumber);
: BuildLoadBuiltinFromIsolateRoot(Builtins::kToNumber);
Node* result = SetEffect(
graph()->NewNode(mcgraph()->common()->Call(call_descriptor), stub_code,
@ -5355,7 +5362,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
(stub_mode_ == StubCallMode::kCallWasmRuntimeStub)
? mcgraph()->RelocatableIntPtrConstant(
wasm::WasmCode::kWasmI64ToBigInt, RelocInfo::WASM_STUB_CALL)
: BuildLoadBuiltinFromInstance(Builtins::kI64ToBigInt);
: BuildLoadBuiltinFromIsolateRoot(Builtins::kI64ToBigInt);
return SetEffect(
SetControl(graph()->NewNode(mcgraph()->common()->Call(call_descriptor),
@ -5377,7 +5384,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
(stub_mode_ == StubCallMode::kCallWasmRuntimeStub)
? mcgraph()->RelocatableIntPtrConstant(
wasm::WasmCode::kWasmBigIntToI64, RelocInfo::WASM_STUB_CALL)
: BuildLoadBuiltinFromInstance(Builtins::kBigIntToI64);
: BuildLoadBuiltinFromIsolateRoot(Builtins::kBigIntToI64);
return SetEffect(SetControl(
graph()->NewNode(mcgraph()->common()->Call(call_descriptor), target,
@ -5451,8 +5458,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
void BuildModifyThreadInWasmFlag(bool new_value) {
if (!trap_handler::IsTrapHandlerEnabled()) return;
Node* isolate_root =
LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
Node* isolate_root = BuildLoadIsolateRoot();
Node* thread_in_wasm_flag_address =
LOAD_RAW(isolate_root, Isolate::thread_in_wasm_flag_address_offset(),
@ -5692,8 +5698,8 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
LOAD_RAW(callable_node,
wasm::ObjectAccess::ContextOffsetInTaggedJSFunction(),
MachineType::TypeCompressedTaggedPointer());
args[pos++] =
BuildLoadBuiltinFromInstance(Builtins::kArgumentsAdaptorTrampoline);
args[pos++] = BuildLoadBuiltinFromIsolateRoot(
Builtins::kArgumentsAdaptorTrampoline);
args[pos++] = callable_node; // target callable
args[pos++] = undefined_node; // new target
args[pos++] = mcgraph()->Int32Constant(wasm_count); // argument count
@ -5837,8 +5843,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
MachineType::Pointer());
BuildModifyThreadInWasmFlag(false);
Node* isolate_root =
LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
Node* isolate_root = BuildLoadIsolateRoot();
Node* fp_value = graph()->NewNode(mcgraph()->machine()->LoadFramePointer());
STORE_RAW(isolate_root, Isolate::c_entry_fp_offset(), fp_value,
MachineType::PointerRepresentation(), kNoWriteBarrier);
@ -5969,6 +5974,79 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
if (ContainsInt64(sig_)) LowerInt64();
}
void BuildJSToJSWrapper() {
int wasm_count = static_cast<int>(sig_->parameter_count());
// Build the start and the parameter nodes.
int param_count = 1 /* closure */ + 1 /* receiver */ + wasm_count +
1 /* new.target */ + 1 /* #arg */ + 1 /* context */;
SetEffect(SetControl(Start(param_count)));
Node* closure = Param(Linkage::kJSCallClosureParamIndex);
Node* context = Param(Linkage::GetJSCallContextParamIndex(wasm_count + 1));
// Since JS-to-JS wrappers are specific to one Isolate, it is OK to embed
// values (for undefined and root) directly into the instruction stream.
isolate_root_node_ = jsgraph()->IntPtrConstant(isolate_->isolate_root());
undefined_value_node_ = jsgraph()->UndefinedConstant();
// Throw a TypeError if the signature is incompatible with JavaScript.
if (!wasm::IsJSCompatibleSignature(sig_, enabled_features_.bigint)) {
BuildCallToRuntimeWithContext(Runtime::kWasmThrowTypeError, context,
nullptr, 0, effect_, Control());
Return(jsgraph()->SmiConstant(0));
return;
}
// Load the original callable from the closure.
Node* shared = LOAD_TAGGED_ANY(
closure,
wasm::ObjectAccess::ToTagged(JSFunction::kSharedFunctionInfoOffset));
Node* func_data = LOAD_TAGGED_ANY(
shared,
wasm::ObjectAccess::ToTagged(SharedFunctionInfo::kFunctionDataOffset));
Node* callable = LOAD_TAGGED_ANY(
func_data,
wasm::ObjectAccess::ToTagged(WasmJSFunctionData::kCallableOffset));
// Call the underlying closure.
Node** args = Buffer(wasm_count + 7);
int pos = 0;
args[pos++] =
jsgraph()->Constant(BUILTIN_CODE(isolate_, Call_ReceiverIsAny));
args[pos++] = callable;
args[pos++] = mcgraph()->Int32Constant(wasm_count); // argument count
args[pos++] = jsgraph()->UndefinedConstant(); // receiver
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), CallTrampolineDescriptor{}, wasm_count + 1,
CallDescriptor::kNoFlags, Operator::kNoProperties,
StubCallMode::kCallCodeObject);
// Convert parameter JS values to wasm numbers and back to JS values.
for (int i = 0; i < wasm_count; ++i) {
Node* param = Param(i + 1); // Start from index 1 to skip receiver.
args[pos++] =
ToJS(FromJS(param, context, sig_->GetParam(i)), sig_->GetParam(i));
}
args[pos++] = context;
args[pos++] = Effect();
args[pos++] = Control();
Node* call = SetEffect(graph()->NewNode(
mcgraph()->common()->Call(call_descriptor), pos, args));
// TODO(wasm): Extend this to support multi-return.
DCHECK_LE(sig_->return_count(), 1);
// Convert return JS values to wasm numbers and back to JS values.
Node* jsval =
sig_->return_count() == 0
? jsgraph()->UndefinedConstant()
: ToJS(FromJS(call, context, sig_->GetReturn()), sig_->GetReturn());
Return(jsval);
}
void BuildCWasmEntry() {
// +1 offset for first parameter index being -1.
SetEffect(SetControl(Start(CWasmEntryParameters::kNumParameters + 1)));
@ -6506,6 +6584,54 @@ wasm::WasmCompilationResult CompileWasmInterpreterEntry(
return result;
}
MaybeHandle<Code> CompileJSToJSWrapper(Isolate* isolate,
wasm::FunctionSig* sig) {
std::unique_ptr<Zone> zone =
base::make_unique<Zone>(isolate->allocator(), ZONE_NAME);
Graph* graph = new (zone.get()) Graph(zone.get());
CommonOperatorBuilder common(zone.get());
MachineOperatorBuilder machine(
zone.get(), MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags(),
InstructionSelector::AlignmentRequirements());
JSGraph jsgraph(isolate, graph, &common, nullptr, nullptr, &machine);
Node* control = nullptr;
Node* effect = nullptr;
WasmWrapperGraphBuilder builder(zone.get(), &jsgraph, sig, nullptr,
StubCallMode::kCallCodeObject,
wasm::WasmFeaturesFromIsolate(isolate));
builder.set_control_ptr(&control);
builder.set_effect_ptr(&effect);
builder.BuildJSToJSWrapper();
int wasm_count = static_cast<int>(sig->parameter_count());
CallDescriptor* incoming = Linkage::GetJSCallDescriptor(
zone.get(), false, wasm_count + 1, CallDescriptor::kNoFlags);
// Build a name in the form "js-to-js-wrapper:<params>:<returns>".
static constexpr size_t kMaxNameLen = 128;
auto debug_name = std::unique_ptr<char[]>(new char[kMaxNameLen]);
memcpy(debug_name.get(), "js-to-js-wrapper:", 18);
AppendSignature(debug_name.get(), kMaxNameLen, sig);
// Run the compilation job synchronously.
std::unique_ptr<OptimizedCompilationJob> job(
Pipeline::NewWasmHeapStubCompilationJob(
isolate, incoming, std::move(zone), graph, Code::JS_TO_JS_FUNCTION,
std::move(debug_name), AssemblerOptions::Default(isolate)));
if (job->PrepareJob(isolate) == CompilationJob::FAILED ||
job->ExecuteJob() == CompilationJob::FAILED ||
job->FinalizeJob(isolate) == CompilationJob::FAILED) {
return {};
}
Handle<Code> code = job->compilation_info()->code();
return code;
}
MaybeHandle<Code> CompileCWasmEntry(Isolate* isolate, wasm::FunctionSig* sig) {
std::unique_ptr<Zone> zone =
base::make_unique<Zone>(isolate->allocator(), ZONE_NAME);

View File

@ -140,6 +140,12 @@ V8_EXPORT_PRIVATE wasm::WasmCompilationResult CompileWasmInterpreterEntry(
wasm::WasmEngine*, const wasm::WasmFeatures& enabled_features,
uint32_t func_index, wasm::FunctionSig*);
// Compiles a stub with JS linkage that serves as an adapter for function
// objects constructed via {WebAssembly.Function}. It performs a round-trip
// simulating a JS-to-Wasm-to-JS coercion of parameter and return values.
MaybeHandle<Code> CompileJSToJSWrapper(Isolate* isolate,
wasm::FunctionSig* sig);
enum CWasmEntryParameters {
kCodeEntry,
kObjectRef,
@ -444,6 +450,7 @@ class WasmGraphBuilder {
SetOncePointer<Node> globals_start_;
SetOncePointer<Node> imported_mutable_globals_;
SetOncePointer<Node> stack_check_code_node_;
SetOncePointer<Node> isolate_root_node_;
SetOncePointer<const Operator> stack_check_call_operator_;
Node** cur_buffer_;
@ -461,6 +468,8 @@ class WasmGraphBuilder {
Node* NoContextConstant();
Node* BuildLoadIsolateRoot();
Node* MemBuffer(uint32_t offset);
// BoundsCheckMem receives a uint32 {index} node and returns a ptrsize index.
Node* BoundsCheckMem(uint8_t access_size, Node* index, uint32_t offset,

View File

@ -579,6 +579,8 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
return OPTIMIZED;
case Code::JS_TO_WASM_FUNCTION:
return JS_TO_WASM;
case Code::JS_TO_JS_FUNCTION:
return STUB;
case Code::C_WASM_ENTRY:
return C_WASM_ENTRY;
case Code::WASM_FUNCTION:

View File

@ -1978,6 +1978,10 @@ void ExistingCodeLogger::LogCodeObject(Object object) {
description = "A JavaScript to Wasm adapter";
tag = CodeEventListener::STUB_TAG;
break;
case AbstractCode::JS_TO_JS_FUNCTION:
description = "A WebAssembly.Function adapter";
tag = CodeEventListener::STUB_TAG;
break;
case AbstractCode::WASM_TO_CAPI_FUNCTION:
description = "A Wasm to C-API adapter";
tag = CodeEventListener::STUB_TAG;

View File

@ -45,6 +45,7 @@ class Code : public HeapObject {
V(WASM_TO_CAPI_FUNCTION) \
V(WASM_TO_JS_FUNCTION) \
V(JS_TO_WASM_FUNCTION) \
V(JS_TO_JS_FUNCTION) \
V(WASM_INTERPRETER_ENTRY) \
V(C_WASM_ENTRY)

View File

@ -2298,6 +2298,10 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
if (sig_size > 0) {
serialized_sig->copy_in(0, sig->all().begin(), sig_size);
}
// TODO(mstarzinger): Think about caching and sharing the JS-to-JS wrappers
// per signature instead of compiling a new one for every instantiation.
Handle<Code> wrapper_code =
compiler::CompileJSToJSWrapper(isolate, sig).ToHandleChecked();
Handle<WasmJSFunctionData> function_data =
Handle<WasmJSFunctionData>::cast(isolate->factory()->NewStruct(
WASM_JS_FUNCTION_DATA_TYPE, AllocationType::kOld));
@ -2305,9 +2309,7 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
function_data->set_serialized_parameter_count(parameter_count);
function_data->set_serialized_signature(*serialized_sig);
function_data->set_callable(*callable);
// TODO(7742): Make this callable by using a proper wrapper code.
function_data->set_wrapper_code(
isolate->builtins()->builtin(Builtins::kIllegal));
function_data->set_wrapper_code(*wrapper_code);
Handle<String> name = isolate->factory()->Function_string();
if (callable->IsJSFunction()) {
name = JSFunction::GetName(Handle<JSFunction>::cast(callable));
@ -2316,6 +2318,7 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
NewFunctionArgs args =
NewFunctionArgs::ForWasm(name, function_data, function_map);
Handle<JSFunction> js_function = isolate->factory()->NewFunction(args);
js_function->shared().set_internal_formal_parameter_count(parameter_count);
return Handle<WasmJSFunction>::cast(js_function);
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-type-reflection
// Flags: --experimental-wasm-type-reflection --expose-gc
load('test/mjsunit/wasm/wasm-module-builder.js');
@ -219,8 +219,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertSame(fun.__proto__.__proto__.__proto__, Object.prototype);
assertSame(fun.constructor, WebAssembly.Function);
assertEquals(typeof fun, 'function');
// TODO(7742): Enable once it is callable.
// assertDoesNotThrow(() => fun());
assertDoesNotThrow(() => fun());
})();
(function TestFunctionExportedFunction() {
@ -271,6 +270,49 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
});
})();
(function TestFunctionConstructedCoercions() {
let obj1 = { valueOf: _ => 123.45 };
let obj2 = { toString: _ => "456" };
let gcer = { valueOf: _ => gc() };
let testcases = [
{ params: { sig: ["i32"],
val: [23.5],
exp: [23], },
result: { sig: ["i32"],
val: 42.7,
exp: 42, },
},
{ params: { sig: ["i32", "f32", "f64"],
val: [obj1, obj2, "789"],
exp: [123, 456, 789], },
result: { sig: [],
val: undefined,
exp: undefined, },
},
{ params: { sig: ["i32", "f32", "f64"],
val: [gcer, {}, "xyz"],
exp: [0, NaN, NaN], },
result: { sig: ["f64"],
val: gcer,
exp: NaN, },
},
];
testcases.forEach(function({params, result}) {
let p = params.sig; let r = result.sig; var params_after;
function testFun() { params_after = arguments; return result.val; }
let fun = new WebAssembly.Function({parameters:p, results:r}, testFun);
let result_after = fun.apply(undefined, params.val);
assertArrayEquals(params.exp, params_after);
assertEquals(result.exp, result_after);
});
})();
(function TestFunctionConstructedIncompatibleSig() {
let fun = new WebAssembly.Function({parameters:["i64"], results:[]}, _ => 0);
assertThrows(() => fun(), TypeError,
/wasm function signature contains illegal type/);
})();
(function TestFunctionTableSetAndCall() {
let builder = new WasmModuleBuilder();
let fun1 = new WebAssembly.Function({parameters:[], results:["i32"]}, _ => 7);