[wasm] Add multi-return support for Wasm to JS calls

Allows JS functions returning array-like objects to be imported as
multi-return functions in WebAssembly modules. Importing a generator
does not work as required by the specification yet.

R=mstarzinger@chromium.org

Bug: v8:9492
Change-Id: Iaf61a0f718eb50676913aa1486fb39cebecfc090
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1815246
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63965}
This commit is contained in:
Thibaud Michaud 2019-09-25 12:27:55 +02:00 committed by Commit Bot
parent ecb1638a56
commit 63e9a7d9bf
4 changed files with 100 additions and 11 deletions

View File

@ -5887,15 +5887,29 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
SetEffect(call);
SetSourcePosition(call, 0);
// Convert the return value back.
Node* val = sig_->return_count() == 0
? mcgraph()->Int32Constant(0)
: FromJS(call, native_context, sig_->GetReturn());
// Set the ThreadInWasm flag again.
BuildModifyThreadInWasmFlag(true);
Return(val);
// Convert the return value(s) back.
if (sig_->return_count() <= 1) {
Node* val = sig_->return_count() == 0
? mcgraph()->Int32Constant(0)
: FromJS(call, native_context, sig_->GetReturn());
BuildModifyThreadInWasmFlag(true);
Return(val);
} else {
Node* size = graph()->NewNode(
mcgraph()->common()->NumberConstant(sig_->return_count()));
Node* args[] = {call, size};
// TODO(thibaudm): Replace runtime call with TurboFan code.
Node* fixed_array = BuildCallToRuntimeWithContext(
Runtime::kWasmIterableToFixedArray, native_context, args, 2, effect_,
Control());
Vector<Node*> wasm_values = Buffer(sig_->return_count());
for (unsigned i = 0; i < sig_->return_count(); ++i) {
wasm_values[i] = FromJS(LOAD_FIXED_ARRAY_SLOT_ANY(fixed_array, i),
native_context, sig_->GetReturn(i));
}
BuildModifyThreadInWasmFlag(true);
Return(wasm_values);
}
if (ContainsInt64(sig_)) LowerInt64(kCalledFromWasm);
return true;

View File

@ -587,5 +587,26 @@ RUNTIME_FUNCTION(Runtime_WasmNewMultiReturnJSArray) {
fixed_array_handle, PACKED_ELEMENTS);
return *array;
}
RUNTIME_FUNCTION(Runtime_WasmIterableToFixedArray) {
// TODO(thibaudm): The current implementation does not handle generators as
// required by the spec.
DCHECK_EQ(2, args.length());
HandleScope scope(isolate);
CONVERT_ARG_CHECKED(Object, arg, 0);
if (!arg.IsJSReceiver()) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapFuncSigMismatch);
}
Handle<JSReceiver> receiver(JSReceiver::cast(arg), isolate);
CONVERT_INT32_ARG_CHECKED(size, 1);
Handle<FixedArray> fixed_array = isolate->factory()->NewFixedArray(size);
for (int i = 0; i < size; ++i) {
Handle<Object> handle;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, handle, JSReceiver::GetElement(isolate, receiver, i));
fixed_array->set(i, *handle);
}
return *fixed_array;
}
} // namespace internal
} // namespace v8

View File

@ -559,7 +559,8 @@ namespace internal {
F(WasmIsValidFuncRefValue, 1, 1) \
F(WasmCompileLazy, 2, 1) \
F(WasmNewMultiReturnFixedArray, 1, 1) \
F(WasmNewMultiReturnJSArray, 1, 1)
F(WasmNewMultiReturnJSArray, 1, 1) \
F(WasmIterableToFixedArray, 2, 1)
#define FOR_EACH_INTRINSIC_RETURN_PAIR_IMPL(F, I) \
F(DebugBreakOnBytecode, 1, 2) \

View File

@ -320,7 +320,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(instance.exports.main(10), 200);
})();
(function MultiJSReturnTest() {
(function MultiWasmToJSReturnTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_fi_if = makeSig([kWasmI32, kWasmF32], [kWasmF32, kWasmI32]);
@ -350,3 +350,56 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(instance.exports.addsubmul(4), [8, 0, 16]);
assertEquals(instance.exports.addsubmul(5), [10, 0, 25]);
})();
(function MultiJSToWasmReturnTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
function swap(x, y) { return [y, x]; }
function swap_proxy(x, y) {
return new Proxy([y, x], {
get: function(obj, prop) { return Reflect.get(obj, prop); },
});
}
function proxy_throw(x, y) {
return new Proxy([y, x], {
get: function(obj, prop) {
if (prop == 1) {
throw new Error("abc");
}
return Reflect.get(obj, prop); },
});
}
function drop_first(x, y) {
return [y];
}
function repeat(x, y) {
return [x, y, x, y];
}
function not_receiver(x, y) {
return 0;
}
builder.addImport('imports', 'f', kSig_ii_ii);
builder.addFunction("main", kSig_ii_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprCallFunction, 0])
.exportAs("main")
let module = new WebAssembly.Module(builder.toBuffer());
var instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : swap } });
assertEquals(instance.exports.main(1, 2), [2, 1]);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : swap_proxy } });
assertEquals(instance.exports.main(1, 2), [2, 1]);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : drop_first } });
assertEquals(instance.exports.main(1, 2), [2, 0]);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : repeat } });
assertEquals(instance.exports.main(1, 2), [1, 2]);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : proxy_throw } });
assertThrows(() => instance.exports.main(1, 2), Error, "abc");
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : not_receiver } });
assertThrows(() => instance.exports.main(1, 2), WebAssembly.RuntimeError);
})();