Commit 99b75c11 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[wasm] Support any iterable in multi-return wrappers

The current implementation only supports arrays and proxies as
multi-return values in Wasm to JS calls. This adds support for any
iterable including generators, as specified by the multi-value proposal
(https://github.com/WebAssembly/multi-value/).

R=mstarzinger@chromium.org

Bug: v8:9492
Change-Id: I2c9be1f7e03824b1aabba525244e5b7f76a98f99
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1824938
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63996}
parent fd4cc2b2
......@@ -618,6 +618,7 @@ namespace internal {
TFS(IterableToList, kIterable, kIteratorFn) \
TFS(IterableToListWithSymbolLookup, kIterable) \
TFS(IterableToListMayPreserveHoles, kIterable, kIteratorFn) \
TFS(IterableToFixedArrayForWasm, kIterable, kExpectedLength) \
\
/* #sec-createstringlistfromiterable */ \
TFS(StringListFromIterable, kIterable) \
......
......@@ -241,6 +241,41 @@ TF_BUILTIN(IterableToList, IteratorBuiltinsAssembler) {
Return(IterableToList(context, iterable, iterator_fn));
}
TF_BUILTIN(IterableToFixedArrayForWasm, IteratorBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<Object> iterable = CAST(Parameter(Descriptor::kIterable));
TNode<Smi> expected_length = CAST(Parameter(Descriptor::kExpectedLength));
TNode<Object> iterator_fn = GetIteratorMethod(context, iterable);
IteratorRecord iterator_record = GetIterator(context, iterable, iterator_fn);
GrowableFixedArray values(state());
Variable* vars[] = {values.var_array(), values.var_length(),
values.var_capacity()};
Label loop_start(this, 3, vars), compare_length(this), done(this);
Goto(&loop_start);
BIND(&loop_start);
{
TNode<JSReceiver> next =
IteratorStep(context, iterator_record, &compare_length);
TNode<Object> next_value = IteratorValue(context, next);
values.Push(next_value);
Goto(&loop_start);
}
BIND(&compare_length);
GotoIf(WordEqual(SmiUntag(expected_length), values.var_length()->value()),
&done);
Return(CallRuntime(
Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kWasmTrapMultiReturnLengthMismatch)));
BIND(&done);
Return(values.var_array()->value());
}
TNode<JSArray> IteratorBuiltinsAssembler::StringListFromIterable(
TNode<Context> context, TNode<Object> iterable) {
Label done(this);
......
......@@ -539,6 +539,7 @@ namespace internal {
T(WasmTrapFloatUnrepresentable, "float unrepresentable in integer range") \
T(WasmTrapFuncInvalid, "invalid index into function table") \
T(WasmTrapFuncSigMismatch, "function signature mismatch") \
T(WasmTrapMultiReturnLengthMismatch, "multi-return length mismatch") \
T(WasmTrapTypeError, "wasm function signature contains illegal type") \
T(WasmTrapDataSegmentDropped, "data segment has been dropped") \
T(WasmTrapElemSegmentDropped, "element segment has been dropped") \
......
......@@ -5895,14 +5895,20 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
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());
Node* iterable_to_fixed_array = BuildLoadBuiltinFromIsolateRoot(
Builtins::kIterableToFixedArrayForWasm);
IterableToFixedArrayForWasmDescriptor interface_descriptor;
Node* length = BuildChangeUint31ToSmi(
Uint32Constant(static_cast<uint32_t>(sig_->return_count())));
auto call_descriptor = Linkage::GetStubCallDescriptor(
mcgraph()->zone(), interface_descriptor,
interface_descriptor.GetStackParameterCount(),
CallDescriptor::kNoFlags, Operator::kNoProperties,
StubCallMode::kCallCodeObject);
Vector<Node*> wasm_values = Buffer(sig_->return_count());
Node* fixed_array = graph()->NewNode(
mcgraph()->common()->Call(call_descriptor), iterable_to_fixed_array,
call, length, native_context, Effect(), Control());
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));
......
......@@ -597,26 +597,5 @@ 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
......@@ -559,8 +559,7 @@ namespace internal {
F(WasmIsValidFuncRefValue, 1, 1) \
F(WasmCompileLazy, 2, 1) \
F(WasmNewMultiReturnFixedArray, 1, 1) \
F(WasmNewMultiReturnJSArray, 1, 1) \
F(WasmIterableToFixedArray, 2, 1)
F(WasmNewMultiReturnJSArray, 1, 1)
#define FOR_EACH_INTRINSIC_RETURN_PAIR_IMPL(F, I) \
F(DebugBreakOnBytecode, 1, 2) \
......
......@@ -378,6 +378,19 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
function not_receiver(x, y) {
return 0;
}
function not_iterable(x, y) {
a = [x, y];
a[Symbol.iterator] = undefined;
return a;
}
function* generator(x, y) {
yield x;
yield y;
}
function* generator_throw(x, y) {
yield x;
throw new Error("def");
}
builder.addImport('imports', 'f', kSig_ii_ii);
builder.addFunction("main", kSig_ii_ii)
......@@ -393,13 +406,19 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
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 } });
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : generator } });
assertEquals(instance.exports.main(1, 2), [1, 2]);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : drop_first } });
assertThrows(() => instance.exports.main(1, 2), TypeError, "multi-return length mismatch");
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : repeat } });
assertThrows(() => instance.exports.main(1, 2), TypeError, "multi-return length mismatch");
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);
assertThrows(() => instance.exports.main(1, 2), TypeError, /not iterable/);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : not_iterable } });
assertThrows(() => instance.exports.main(1, 2), TypeError, /not iterable/);
instance = new WebAssembly.Instance(module, { 'imports' : { 'f' : generator_throw } });
assertThrows(() => instance.exports.main(1, 2), Error, "def");
})();
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