[wasm] Implement new JS Promise Integration API

Implement the WebAssembly.Function-based API.
With the old API, wrapping an import and export with JS Promise
Integration looked like:

  WebAssembly.returnPromiseOnSuspend(<wasm_export>);
  WebAssembly.suspendOnReturnedPromise(
    new WebAssembly.Function(<sig>, <js_import>));

With the new API:

  new WebAssembly.Function(<sig>, <wasm_export>, {promising: 'first'})
  new WebAssembly.Function(<sig>, <js_import>, {suspending: 'first'})

For details, see
https://github.com/WebAssembly/js-promise-integration/pull/8/files

R=ahaas@chromium.org

Bug: v8:12191
Change-Id: Iaefaac5304a038fc39283db165b637af7e48b009
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3804669
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82183}
This commit is contained in:
Thibaud Michaud 2022-08-03 18:32:08 +02:00 committed by V8 LUCI CQ
parent f62fadc614
commit 29db563159
11 changed files with 346 additions and 292 deletions

View File

@ -7828,8 +7828,7 @@ WasmImportData ResolveWasmImportCall(
if (WasmJSFunction::IsWasmJSFunction(*callable)) {
auto js_function = Handle<WasmJSFunction>::cast(callable);
suspend = js_function->GetSuspend();
if ((suspend && !js_function->MatchesSignatureForSuspend(expected_sig)) ||
(!suspend && !js_function->MatchesSignature(expected_sig))) {
if (!js_function->MatchesSignature(expected_sig)) {
return {WasmImportCallKind::kLinkError, callable, wasm::kNoSuspend};
}
// Resolve the short-cut to the underlying callable and continue.

View File

@ -2033,6 +2033,7 @@ void WasmInstanceObject::WasmInstanceObjectPrint(std::ostream& os) {
void WasmFunctionData::WasmFunctionDataPrint(std::ostream& os) {
os << "\n - internal: " << Brief(internal());
os << "\n - wrapper_code: " << Brief(TorqueGeneratedClass::wrapper_code());
os << "\n - js_promise_flags: " << js_promise_flags();
}
void WasmExportedFunctionData::WasmExportedFunctionDataPrint(std::ostream& os) {
@ -2042,7 +2043,6 @@ void WasmExportedFunctionData::WasmExportedFunctionDataPrint(std::ostream& os) {
os << "\n - function_index: " << function_index();
os << "\n - signature: " << Brief(signature());
os << "\n - wrapper_budget: " << wrapper_budget();
os << "\n - suspend: " << suspend();
os << "\n";
}

View File

@ -1671,7 +1671,8 @@ Handle<WasmInternalFunction> Factory::NewWasmInternalFunction(
Handle<WasmJSFunctionData> Factory::NewWasmJSFunctionData(
Address opt_call_target, Handle<JSReceiver> callable, int return_count,
int parameter_count, Handle<PodArray<wasm::ValueType>> serialized_sig,
Handle<CodeT> wrapper_code, Handle<Map> rtt, wasm::Suspend suspend) {
Handle<CodeT> wrapper_code, Handle<Map> rtt, wasm::Suspend suspend,
wasm::Promise promise) {
Handle<WasmApiFunctionRef> ref =
NewWasmApiFunctionRef(callable, suspend, Handle<WasmInstanceObject>());
Handle<WasmInternalFunction> internal =
@ -1686,7 +1687,8 @@ Handle<WasmJSFunctionData> Factory::NewWasmJSFunctionData(
result.set_serialized_return_count(return_count);
result.set_serialized_parameter_count(parameter_count);
result.set_serialized_signature(*serialized_sig);
result.set_suspend(suspend);
result.set_js_promise_flags(WasmFunctionData::SuspendField::encode(suspend) |
WasmFunctionData::PromiseField::encode(promise));
return handle(result, isolate());
}
@ -1705,7 +1707,7 @@ Handle<WasmExportedFunctionData> Factory::NewWasmExportedFunctionData(
Handle<CodeT> export_wrapper, Handle<WasmInstanceObject> instance,
Address call_target, Handle<Object> ref, int func_index,
Address sig_address, int wrapper_budget, Handle<Map> rtt,
wasm::Suspend suspend) {
wasm::Promise promise) {
Handle<Foreign> sig_foreign = NewForeign(sig_address);
Handle<WasmInternalFunction> internal =
NewWasmInternalFunction(call_target, Handle<HeapObject>::cast(ref), rtt);
@ -1728,7 +1730,9 @@ Handle<WasmExportedFunctionData> Factory::NewWasmExportedFunctionData(
*BUILTIN_CODE(isolate(), Illegal),
V8_EXTERNAL_CODE_SPACE_BOOL ? UPDATE_WRITE_BARRIER : SKIP_WRITE_BARRIER);
result.set_packed_args_size(0);
result.set_suspend(suspend);
result.set_js_promise_flags(
WasmFunctionData::SuspendField::encode(wasm::kNoSuspend) |
WasmFunctionData::PromiseField::encode(promise));
return handle(result, isolate());
}
@ -1749,6 +1753,9 @@ Handle<WasmCapiFunctionData> Factory::NewWasmCapiFunctionData(
result.set_wrapper_code(*wrapper_code);
result.set_embedder_data(*embedder_data);
result.set_serialized_signature(*serialized_sig);
result.set_js_promise_flags(
WasmFunctionData::SuspendField::encode(wasm::kNoSuspend) |
WasmFunctionData::PromiseField::encode(wasm::kNoPromise));
return handle(result, isolate());
}

View File

@ -84,6 +84,7 @@ struct WasmElemSegment;
class WasmValue;
enum class OnResume : int;
enum Suspend : uint8_t;
enum Promise : uint8_t;
} // namespace wasm
#endif
@ -643,7 +644,7 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<CodeT> export_wrapper, Handle<WasmInstanceObject> instance,
Address call_target, Handle<Object> ref, int func_index,
Address sig_address, int wrapper_budget, Handle<Map> rtt,
wasm::Suspend suspend);
wasm::Promise promise);
Handle<WasmApiFunctionRef> NewWasmApiFunctionRef(
Handle<JSReceiver> callable, wasm::Suspend suspend,
Handle<WasmInstanceObject> instance);
@ -652,7 +653,8 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<WasmJSFunctionData> NewWasmJSFunctionData(
Address opt_call_target, Handle<JSReceiver> callable, int return_count,
int parameter_count, Handle<PodArray<wasm::ValueType>> serialized_sig,
Handle<CodeT> wrapper_code, Handle<Map> rtt, wasm::Suspend suspend);
Handle<CodeT> wrapper_code, Handle<Map> rtt, wasm::Suspend suspend,
wasm::Promise promise);
Handle<WasmResumeData> NewWasmResumeData(
Handle<WasmSuspenderObject> suspender, wasm::OnResume on_resume);
Handle<WasmStruct> NewWasmStruct(const wasm::StructType* type,

View File

@ -743,8 +743,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// function. Use CWasmEntry instead.
start_function_ = WasmExportedFunction::New(
isolate_, instance, start_index,
static_cast<int>(function.sig->parameter_count()), wrapper_code,
kNoSuspend);
static_cast<int>(function.sig->parameter_count()), wrapper_code);
if (function.imported) {
ImportedFunctionEntry entry(instance, module_->start_function_index);

View File

@ -244,16 +244,6 @@ i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(
return i::wasm::ModuleWireBytes(start, start + length);
}
i::MaybeHandle<i::JSFunction> GetFirstArgumentAsJSFunction(
const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) {
i::Handle<i::Object> arg0 = Utils::OpenHandle(*args[0]);
if (!arg0->IsJSFunction()) {
thrower->TypeError("Argument 0 must be a function");
return {};
}
return i::Handle<i::JSFunction>::cast(arg0);
}
namespace {
i::MaybeHandle<i::JSReceiver> ImportsAsMaybeReceiver(Local<Value> ffi) {
if (ffi->IsUndefined()) return {};
@ -1889,6 +1879,76 @@ void WebAssemblyException(const v8::FunctionCallbackInfo<v8::Value>& args) {
Utils::ToLocal(i::Handle<i::Object>::cast(runtime_exception)));
}
namespace {
bool HasJSPromiseIntegrationFlag(Isolate* isolate, Local<Object> usage_obj,
ErrorThrower* thrower, const char* flag_name) {
Local<Context> context = isolate->GetCurrentContext();
Local<String> flag_str = v8_str(isolate, flag_name);
Local<String> first_str = v8_str(isolate, "first");
Local<String> last_str = v8_str(isolate, "last");
Local<String> none_str = v8_str(isolate, "none");
v8::MaybeLocal<v8::Value> maybe_flag = usage_obj->Get(context, flag_str);
v8::Local<Value> flag_value;
v8::Local<String> flag_value_str;
if (maybe_flag.ToLocal(&flag_value) && !flag_value->IsUndefined() &&
flag_value->ToString(context).ToLocal(&flag_value_str)) {
if (!flag_value_str->StringEquals(first_str) &&
!flag_value_str->StringEquals(last_str) &&
!flag_value_str->StringEquals(none_str)) {
thrower->TypeError(
"JS Promise Integration: Expected suspender "
"position to be \"first\", \"last\" or \"none\"");
return false;
} else if (flag_value_str->StringEquals(last_str)) {
// TODO(thibaudm): Support the "last" position.
UNIMPLEMENTED();
} else if (flag_value_str->StringEquals(first_str)) {
return true;
}
}
return false;
}
// Given {inner_sig}: [ti*] -> [to*]
// {outer_sig} must be: [externref ti*] -> [to*]
bool IsSuspendingSignature(const i::wasm::FunctionSig* inner_sig,
const i::wasm::FunctionSig* outer_sig) {
if (inner_sig->parameter_count() + 1 != outer_sig->parameter_count()) {
return false;
}
if (inner_sig->return_count() != outer_sig->return_count()) {
return false;
}
if (outer_sig->GetParam(0) != i::wasm::kWasmExternRef) return false;
for (size_t i = 1; i < outer_sig->parameter_count(); ++i) {
if (outer_sig->GetParam(i) != inner_sig->GetParam(i - 1)) return false;
}
for (size_t i = 0; i < outer_sig->return_count(); ++i) {
if (outer_sig->GetReturn(i) != inner_sig->GetReturn(i)) return false;
}
return true;
}
// Given {inner_sig}: externref ti* -> to
// {outer_sig} must be: ti* -> externref
bool IsPromisingSignature(const i::wasm::FunctionSig* inner_sig,
const i::wasm::FunctionSig* outer_sig) {
if (inner_sig->parameter_count() != outer_sig->parameter_count() + 1) {
return false;
}
if (inner_sig->return_count() != 1 || outer_sig->return_count() != 1) {
return false;
}
if (inner_sig->GetParam(0) != i::wasm::kWasmExternRef) return false;
for (size_t i = 0; i < outer_sig->parameter_count(); ++i) {
if (outer_sig->GetParam(i) != inner_sig->GetParam(i + 1)) return false;
}
if (outer_sig->GetReturn(0) != i::wasm::kWasmExternRef) return false;
return true;
}
} // namespace
// WebAssembly.Function
void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
@ -1983,7 +2043,35 @@ void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::JSReceiver> callable =
Utils::OpenHandle(*args[1].As<Function>());
if (i::WasmExportedFunction::IsWasmExportedFunction(*callable)) {
i::wasm::Suspend suspend = i::wasm::kNoSuspend;
i::wasm::Promise promise = i::wasm::kNoPromise;
if (i::FLAG_experimental_wasm_stack_switching) {
// Optional third argument for JS Promise Integration.
if (!args[2]->IsNullOrUndefined() && !args[2]->IsObject()) {
thrower.TypeError(
"Expected argument 3 to be an object with a "
"'suspending' or 'promising' property");
return;
}
if (args[2]->IsObject()) {
Local<Object> usage_obj = Local<Object>::Cast(args[2]);
if (HasJSPromiseIntegrationFlag(isolate, usage_obj, &thrower,
"suspending")) {
suspend = i::wasm::kSuspend;
}
if (HasJSPromiseIntegrationFlag(isolate, usage_obj, &thrower,
"promising")) {
promise = i::wasm::kPromise;
}
}
}
bool is_wasm_exported_function =
i::WasmExportedFunction::IsWasmExportedFunction(*callable);
bool is_wasm_js_function = i::WasmJSFunction::IsWasmJSFunction(*callable);
if (is_wasm_exported_function && !suspend && !promise) {
if (*i::Handle<i::WasmExportedFunction>::cast(callable)->sig() == *sig) {
args.GetReturnValue().Set(Utils::ToLocal(callable));
return;
@ -1995,7 +2083,7 @@ void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
if (i::WasmJSFunction::IsWasmJSFunction(*callable)) {
if (is_wasm_js_function && !suspend && !promise) {
if (i::Handle<i::WasmJSFunction>::cast(callable)->MatchesSignature(sig)) {
args.GetReturnValue().Set(Utils::ToLocal(callable));
return;
@ -2007,8 +2095,49 @@ void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
if (is_wasm_exported_function && suspend) {
// TODO(thibaudm): Support wasm-to-wasm calls with suspending behavior, and
// also with combined promising+suspending behavior.
UNIMPLEMENTED();
}
if (is_wasm_exported_function && promise) {
auto wasm_exported_function = i::WasmExportedFunction::cast(*callable);
i::SharedFunctionInfo sfi = wasm_exported_function.shared();
i::WasmExportedFunctionData data = sfi.wasm_exported_function_data();
if (!IsPromisingSignature(data.sig(), sig)) {
thrower.TypeError("Incompatible signature for promising function");
return;
}
i::Handle<i::WasmInstanceObject> instance(
i::WasmInstanceObject::cast(data.internal().ref()), i_isolate);
int func_index = data.function_index();
i::Handle<i::CodeT> wrapper =
BUILTIN_CODE(i_isolate, WasmReturnPromiseOnSuspend);
i::Handle<i::JSFunction> result = i::WasmExportedFunction::New(
i_isolate, instance, func_index,
static_cast<int>(data.sig()->parameter_count()), wrapper);
args.GetReturnValue().Set(Utils::ToLocal(result));
return;
}
if (is_wasm_js_function && promise) {
// TODO(thibaudm): This case has no practical use. The generated suspender
// would be unusable since the stack would always contain at least one JS
// frame. But for now the spec would require us to add specific JS-to-JS and
// wasm-to-JS wrappers to support this case. Leave this unimplemented for
// now.
UNIMPLEMENTED();
}
if (is_wasm_js_function && suspend) {
auto wasm_js_function = i::WasmJSFunction::cast(*callable);
const i::wasm::FunctionSig* inner_sig =
wasm_js_function.GetSignature(&zone);
if (!IsSuspendingSignature(inner_sig, sig)) {
thrower.TypeError("Incompatible signature for suspending function");
return;
}
}
i::Handle<i::JSFunction> result =
i::WasmJSFunction::New(i_isolate, sig, callable, i::wasm::kNoSuspend);
i::WasmJSFunction::New(i_isolate, sig, callable, suspend);
args.GetReturnValue().Set(Utils::ToLocal(result));
}
@ -2029,8 +2158,8 @@ void WebAssemblyFunctionType(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::WasmExportedFunctionData> data =
handle(sfi->wasm_exported_function_data(), i_isolate);
sig = wasm_exported_function->sig();
if (data->suspend()) {
// If this export is suspendable, the first parameter of the original
if (i::WasmFunctionData::PromiseField::decode(data->js_promise_flags())) {
// If this export is "promising", the first parameter of the original
// function is an externref (suspender) which does not appear in the
// wrapper function's signature. The wrapper function also returns a
// promise as an externref instead of the original return type.
@ -2046,22 +2175,6 @@ void WebAssemblyFunctionType(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
} else if (i::WasmJSFunction::IsWasmJSFunction(*arg0)) {
sig = i::Handle<i::WasmJSFunction>::cast(arg0)->GetSignature(&zone);
auto wasm_js_function = i::Handle<i::WasmJSFunction>::cast(arg0);
if (wasm_js_function->shared().wasm_js_function_data().suspend()) {
// If this function is the result of calling
// WebAssembly.suspendOnReturnedPromise(), it takes an extra suspender
// parameter which will be consumed by the wasm-to-JS wrapper.
size_t param_count = sig->parameter_count();
i::wasm::FunctionSig::Builder builder(&zone, 1, param_count + 1);
builder.AddParam(internal::wasm::kWasmExternRef);
for (size_t i = 0; i < param_count; ++i) {
builder.AddParam(sig->GetParam(i));
}
DCHECK_EQ(1, sig->return_count());
DCHECK_EQ(i::wasm::kWasmExternRef, sig->GetReturn(0));
builder.AddReturn(i::wasm::kWasmExternRef);
sig = builder.Build();
}
} else {
thrower.TypeError("Argument 0 must be a WebAssembly.Function");
return;
@ -2744,88 +2857,6 @@ void WebAssemblyGlobalType(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(Utils::ToLocal(type));
}
// WebAssembly.returnPromiseOnSuspend(WebAssembly.Function) ->
// WebAssembly.Function
void WebAssemblyReturnPromiseOnSuspend(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate,
"WebAssembly.returnPromiseOnSuspend()");
if (args.Length() == 0) {
thrower.TypeError("Argument 0 is required");
return;
}
auto maybe_function = GetFirstArgumentAsJSFunction(args, &thrower);
if (thrower.error()) return;
i::Handle<i::JSFunction> function = maybe_function.ToHandleChecked();
i::SharedFunctionInfo sfi = function->shared();
if (!sfi.HasWasmExportedFunctionData()) {
thrower.TypeError("Argument 0 must be a wasm function");
}
i::WasmExportedFunctionData data = sfi.wasm_exported_function_data();
if (data.sig()->return_count() != 1) {
thrower.TypeError(
"Expected a WebAssembly.Function with exactly one return type");
}
if (data.sig()->parameter_count() == 0 ||
data.sig()->GetParam(0) != internal::wasm::kWasmExternRef) {
thrower.TypeError("Expected at least one parameter of type %s",
i::wasm::kWasmExternRef.name().c_str());
}
if (thrower.error()) return;
int index = data.function_index();
i::Handle<i::WasmInstanceObject> instance(
i::WasmInstanceObject::cast(data.internal().ref()), i_isolate);
i::Handle<i::CodeT> wrapper =
BUILTIN_CODE(i_isolate, WasmReturnPromiseOnSuspend);
// Upcast to JSFunction to re-use the existing ToLocal helper below.
i::Handle<i::JSFunction> result =
i::Handle<i::WasmExternalFunction>::cast(i::WasmExportedFunction::New(
i_isolate, instance, index,
static_cast<int>(data.sig()->parameter_count()), wrapper,
internal::wasm::kSuspend));
args.GetReturnValue().Set(Utils::ToLocal(result));
}
// WebAssembly.suspendOnReturnedPromise(Function) -> Function
void WebAssemblySuspendOnReturnedPromise(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate,
"WebAssembly.suspendOnReturnedPromise()");
if (!args[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a WebAssembly.Function");
return;
}
i::Zone zone(i_isolate->allocator(), ZONE_NAME);
const i::wasm::FunctionSig* sig;
i::Handle<i::Object> arg0 = Utils::OpenHandle(*args[0]);
if (i::WasmExportedFunction::IsWasmExportedFunction(*arg0)) {
// TODO(thibaudm): Suspend on wrapped wasm-to-wasm calls too.
UNIMPLEMENTED();
} else if (!i::WasmJSFunction::IsWasmJSFunction(*arg0)) {
thrower.TypeError("Argument 0 must be a WebAssembly.Function");
return;
}
sig = i::Handle<i::WasmJSFunction>::cast(arg0)->GetSignature(&zone);
if (sig->return_count() != 1 ||
sig->GetReturn(0) != i::wasm::kWasmExternRef) {
thrower.TypeError("Expected a WebAssembly.Function with return type %s",
i::wasm::kWasmExternRef.name().c_str());
return;
}
auto callable = handle(
i::Handle<i::WasmJSFunction>::cast(arg0)->GetCallable(), i_isolate);
i::Handle<i::JSFunction> result =
i::WasmJSFunction::New(i_isolate, sig, callable, i::wasm::kSuspend);
args.GetReturnValue().Set(Utils::ToLocal(result));
}
} // namespace
// TODO(titzer): we use the API to create the function template because the
@ -3123,10 +3154,6 @@ void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
SetupConstructor(isolate, suspender_constructor,
i::WASM_SUSPENDER_OBJECT_TYPE,
WasmSuspenderObject::kHeaderSize, "WebAssembly.Suspender");
InstallFunc(isolate, webassembly, "returnPromiseOnSuspend",
WebAssemblyReturnPromiseOnSuspend, 1);
InstallFunc(isolate, webassembly, "suspendOnReturnedPromise",
WebAssemblySuspendOnReturnedPromise, 1);
}
// Setup Function

View File

@ -1431,8 +1431,7 @@ WasmInstanceObject::GetOrCreateWasmInternalFunction(
}
auto external = Handle<WasmExternalFunction>::cast(WasmExportedFunction::New(
isolate, instance, function_index,
static_cast<int>(function.sig->parameter_count()), wrapper,
wasm::kNoSuspend));
static_cast<int>(function.sig->parameter_count()), wrapper));
result =
WasmInternalFunction::FromExternal(external, isolate).ToHandleChecked();
@ -1972,7 +1971,7 @@ int WasmExportedFunction::function_index() {
Handle<WasmExportedFunction> WasmExportedFunction::New(
Isolate* isolate, Handle<WasmInstanceObject> instance, int func_index,
int arity, Handle<CodeT> export_wrapper, wasm::Suspend suspend) {
int arity, Handle<CodeT> export_wrapper) {
DCHECK(
CodeKind::JS_TO_WASM_FUNCTION == export_wrapper->kind() ||
(export_wrapper->is_builtin() &&
@ -1998,11 +1997,15 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
} else {
rtt = factory->wasm_internal_function_map();
}
wasm::Promise promise =
export_wrapper->builtin_id() == Builtin::kWasmReturnPromiseOnSuspend
? wasm::kPromise
: wasm::kNoPromise;
Handle<WasmExportedFunctionData> function_data =
factory->NewWasmExportedFunctionData(
export_wrapper, instance, call_target, ref, func_index,
reinterpret_cast<Address>(sig), wasm::kGenericWrapperBudget, rtt,
suspend);
promise);
MaybeHandle<String> maybe_name;
bool is_asm_js_module = instance->module_object().is_asm_js();
@ -2130,7 +2133,7 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
Handle<Map> rtt = factory->wasm_internal_function_map();
Handle<WasmJSFunctionData> function_data = factory->NewWasmJSFunctionData(
call_target, callable, return_count, parameter_count, serialized_sig,
wrapper_code, rtt, suspend);
wrapper_code, rtt, suspend, wasm::kNoPromise);
if (wasm::WasmFeatures::FromIsolate(isolate).has_typed_funcref()) {
using CK = compiler::WasmImportCallKind;
@ -2197,28 +2200,6 @@ const wasm::FunctionSig* WasmJSFunction::GetSignature(Zone* zone) {
return zone->New<wasm::FunctionSig>(return_count, parameter_count, types);
}
bool WasmJSFunction::MatchesSignatureForSuspend(const wasm::FunctionSig* sig) {
DCHECK_LE(sig->all().size(), kMaxInt);
int sig_size = static_cast<int>(sig->all().size());
int parameter_count = static_cast<int>(sig->parameter_count());
DisallowHeapAllocation no_alloc;
WasmJSFunctionData function_data = shared().wasm_js_function_data();
// The suspender parameter is not forwarded to the JS function so the
// parameter count should differ by one.
if (parameter_count != function_data.serialized_parameter_count() + 1) {
return false;
}
if (sig_size == 0) return true; // Prevent undefined behavior.
// This function is only called for functions wrapped by
// WebAssembly.suspendOnReturnedPromise, so the return type has to be
// externref.
CHECK_EQ(function_data.serialized_return_count(), 1);
CHECK_EQ(function_data.serialized_signature().get(0), wasm::kWasmExternRef);
const wasm::ValueType* expected = sig->parameters().begin() + 1;
return function_data.serialized_signature().matches(1, expected,
parameter_count - 1);
}
// TODO(9495): Update this if function type variance is introduced.
bool WasmJSFunction::MatchesSignature(const wasm::FunctionSig* sig) {
DCHECK_LE(sig->all().size(), kMaxInt);

View File

@ -2,6 +2,7 @@
// this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/base/bit-field.h"
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
@ -79,6 +80,7 @@ namespace wasm {
// https://github.com/llvm/llvm-project/issues/56560. See also
// crbug.com/1344641.
enum Suspend : uint8_t { kSuspend = 1, kNoSuspend = 0 };
enum Promise : uint8_t { kPromise = 1, kNoPromise = 0 };
enum class OnResume : int { kContinue, kThrow };
} // namespace wasm
@ -610,7 +612,7 @@ class WasmExportedFunction : public JSFunction {
V8_EXPORT_PRIVATE static Handle<WasmExportedFunction> New(
Isolate* isolate, Handle<WasmInstanceObject> instance, int func_index,
int arity, Handle<CodeT> export_wrapper, wasm::Suspend suspend);
int arity, Handle<CodeT> export_wrapper);
Address GetWasmCallTarget();
@ -644,8 +646,6 @@ class WasmJSFunction : public JSFunction {
// that lifetime of the signature is hence directly coupled to the zone.
const wasm::FunctionSig* GetSignature(Zone* zone);
bool MatchesSignature(const wasm::FunctionSig* sig);
// Special typing rule for imports wrapped by a Suspender.
bool MatchesSignatureForSuspend(const wasm::FunctionSig* sig);
DECL_CAST(WasmJSFunction)
OBJECT_CONSTRUCTORS(WasmJSFunction, JSFunction);
@ -715,6 +715,9 @@ class WasmFunctionData
using BodyDescriptor = FlexibleBodyDescriptor<kStartOfStrongFieldsOffset>;
using SuspendField = base::BitField<wasm::Suspend, 0, 1>;
using PromiseField = base::BitField<wasm::Promise, 1, 1>;
TQ_OBJECT_CONSTRUCTORS(WasmFunctionData)
};

View File

@ -55,6 +55,8 @@ extern class WasmFunctionData extends HeapObject {
// Used for calling this function from JavaScript.
@if(V8_EXTERNAL_CODE_SPACE) wrapper_code: CodeDataContainer;
@ifnot(V8_EXTERNAL_CODE_SPACE) wrapper_code: Code;
// Encode the {promising} and {suspending} flags in a single smi.
js_promise_flags: Smi;
}
extern class WasmExportedFunctionData extends WasmFunctionData {
@ -70,18 +72,12 @@ extern class WasmExportedFunctionData extends WasmFunctionData {
@if(V8_EXTERNAL_CODE_SPACE) c_wrapper_code: CodeDataContainer;
@ifnot(V8_EXTERNAL_CODE_SPACE) c_wrapper_code: Code;
packed_args_size: Smi;
// Functions returned by suspender.returnPromiseOnSuspend() have this field
// set to the host suspender object.
suspend: Smi; // Boolean.
}
extern class WasmJSFunctionData extends WasmFunctionData {
serialized_return_count: Smi;
serialized_parameter_count: Smi;
serialized_signature: PodArrayOfWasmValueType;
// Whether this function is the result of wrapping another function with
// WebAssembly.suspendOnReturnedPromise.
suspend: Smi;
}
extern class WasmCapiFunctionData extends WasmFunctionData {

View File

@ -12,6 +12,20 @@
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
function ToPromising(wasm_export) {
let sig = WebAssembly.Function.type(wasm_export);
assertTrue(sig.parameters.length > 0);
assertEquals('externref', sig.parameters[0]);
assertEquals(1, sig.results.length);
let wrapper_sig = {
parameters: sig.parameters.slice(1),
results: ['externref']
};
return new WebAssembly.Function(
wrapper_sig, wasm_export, {promising: 'first'});
}
(function testGenericWrapper0Param() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
@ -30,7 +44,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main());
assertEquals(20, x);
})();
@ -46,7 +60,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
.exportFunc();
let instance = builder.instantiate();
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertTraps(kTrapUnreachable, main);
})();
@ -62,7 +76,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
.exportFunc();
let instance = builder.instantiate();
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertTraps(kTrapUnreachable, main);
})();
@ -88,7 +102,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(5));
assertEquals(17, x);
})();
@ -116,7 +130,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let y = { valueOf: () => { print("Hello!"); gc(); return 24; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(y));
assertEquals(36, x);
})();
@ -149,7 +163,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let param2 = { valueOf: () => { gc(); return 6; } };
let param3 = { valueOf: () => { gc(); return 3; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(9, param2, param3, 0));
assertEquals(60, x);
// Now we test if the evaluation order of the parameters is correct.
@ -203,7 +217,7 @@ let kSig_r_riiiiiiii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
let param6 = { valueOf: () => { gc(); return 10; } };
let param8 = { valueOf: () => { gc(); return 12; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(param1, 6, 7, param4, 9, param6, 11, param8));
assertEquals(360, x);
})();
@ -236,7 +250,7 @@ let kSig_r_riiiiiiii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
let param2 = { valueOf: () => { gc(); return 3; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(5, param2));
assertEquals(20, x);
})();
@ -270,7 +284,7 @@ let kSig_r_riiiiiiii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
let param2 = { valueOf: () => { gc(); return 3; } };
let param3 = { valueOf: () => { gc(); return 6; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(5, param2, param3, 7, 200, 300, 400));
assertEquals(33, x);
})();
@ -297,7 +311,7 @@ let kSig_r_riiiiiiii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(17, main(5));
})();
@ -324,7 +338,7 @@ let kSig_r_riiiiiiii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(2147483645, main(5));
})();
@ -357,7 +371,7 @@ let kSig_i_rlili = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI64, kWasmI
let param2 = { valueOf: () => { gc(); return 6; } };
let param3 = { valueOf: () => { gc(); return 3n; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(60, main(9n, param2, param3, 0));
})();
@ -391,7 +405,7 @@ let kSig_r_riiili = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32, kWasm
let param2 = { valueOf: () => { gc(); return 6; } };
let param3 = { valueOf: () => { gc(); return 3; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(9, param2, param3, 0n, 2));
assertEquals(72, x);
})();
@ -428,7 +442,7 @@ let kSig_r_riiilii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32,
let param2 = { valueOf: () => { gc(); return 6; } };
let param3 = { valueOf: () => { gc(); return 3; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(9, param2, param3, 0n, 2, 3));
assertEquals(93, x);
})();
@ -471,7 +485,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
let param6 = { valueOf: () => { gc(); return 10; } };
let param8 = { valueOf: () => { gc(); return 12; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(360, main(param1, 6, 7, param4, 9n, param6, 11, param8, 0n));
})();
@ -497,7 +511,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertThrows(() => { main(17) }, TypeError);
})();
@ -521,7 +535,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(10000000000n, main());
})();
@ -545,7 +559,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(0.5, main());
})();
@ -569,7 +583,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(0.25, main());
})();
@ -595,7 +609,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(12.5));
assertEquals(25, x);
})();
@ -622,7 +636,7 @@ let kSig_r_rliilliiil = makeSig([kWasmExternRef, kWasmI64, kWasmI32, kWasmI32,
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(12.5));
assertEquals(25, x);
})();
@ -664,7 +678,7 @@ let kSig_r_rffddddff = makeSig([kWasmExternRef, kWasmF32, kWasmF32, kWasmF64,
let param6 = { valueOf: () => { gc(); return 6.5; } };
let param8 = { valueOf: () => { gc(); return 8.5; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(param1, 2.5, 3.5, param4, 5.5, param6, 7.5,
param8));
assertEquals(234, x);
@ -712,7 +726,7 @@ let kSig_r_riiliffddlfdff = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI6
}
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(5, 6, 7n, 8, 1.5, 2.5, 3.5, 4.5, 11n, 5.5, 6.5,
7.5, 8.5));
assertEquals(137, x);
@ -763,7 +777,7 @@ let kSig_r_riiliiiffddli = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI64
let param6 = { valueOf: () => { gc(); return 10; } };
let param8 = { valueOf: () => { gc(); return 12; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(undefined, main(param1, 6, 7n, param4, 9, param6,
1.5, 2.5, 3.6, 4.4, 11n, param8));
assertEquals(360, x);
@ -826,7 +840,7 @@ let kSig_f_riiliiiffddlifffdi = makeSig([kWasmExternRef, kWasmI32, kWasmI32,
let paramf3 = { valueOf: () => { gc(); return 5.5; } };
let param9 = { valueOf: () => { gc(); return 0; } };
let instance = builder.instantiate({ mod: { func: import_func } });
let main = WebAssembly.returnPromiseOnSuspend(instance.exports.main);
let main = ToPromising(instance.exports.main);
assertEquals(223, main(param1, 6, 7n, param4, 9, param6, 1.5, 2.5, paramd1,
4.5, 11n, param8, paramf3, 6.5, 7.5, 8.5, param9));
assertEquals(360, x);
@ -841,7 +855,7 @@ let kSig_f_riiliiiffddlifffdi = makeSig([kWasmExternRef, kWasmI32, kWasmI32,
instance = builder.instantiate();
function js_caller() {
return WebAssembly.returnPromiseOnSuspend(instance.exports.wasm_fn)();
return ToPromising(instance.exports.wasm_fn);
}
%PrepareFunctionForOptimization(js_caller);
js_caller();
@ -862,7 +876,7 @@ let kSig_f_riiliiiffddlifffdi = makeSig([kWasmExternRef, kWasmI32, kWasmI32,
.exportFunc();
let instance = builder.instantiate();
let f1 = WebAssembly.returnPromiseOnSuspend(instance.exports.f1);
let f1 = ToPromising(instance.exports.f1);
assertEquals(15, f1());
})();
@ -884,8 +898,8 @@ let kSig_f_riiliiiffddlifffdi = makeSig([kWasmExternRef, kWasmI32, kWasmI32,
%DeoptimizeFunction(caller);
}
let main = WebAssembly.returnPromiseOnSuspend(
builder.instantiate({q: {func: deopt}}).exports.main);
let instance = builder.instantiate({q: {func: deopt}});
let main = ToPromising(instance.exports.main);
function caller() {
main(1, 2, 3, 4, 5);
main(1, 2, 3, 4);
@ -915,7 +929,7 @@ let kSig_f_riiliiiffddlifffdi = makeSig([kWasmExternRef, kWasmI32, kWasmI32,
let module = new WebAssembly.Module(builder.toBuffer());
let instance = new WebAssembly.Instance(module);
let func0 = WebAssembly.returnPromiseOnSuspend(instance.exports.func0);
let func0 = ToPromising(instance.exports.func0);
let res = func0(1, 2, "3", "4", "5", "6", "7", "8", 9, 10, 11, 12, 13);
assertEquals("8", res);
})();

View File

@ -15,6 +15,20 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
/WebAssembly.Suspender must be invoked with 'new'/);
})();
function ToPromising(wasm_export) {
let sig = WebAssembly.Function.type(wasm_export);
assertTrue(sig.parameters.length > 0);
assertEquals('externref', sig.parameters[0]);
assertEquals(1, sig.results.length);
let wrapper_sig = {
parameters: sig.parameters.slice(1),
results: ['externref']
};
return new WebAssembly.Function(
wrapper_sig, wasm_export, {promising: 'first'});
}
(function TestSuspenderTypes() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
@ -36,34 +50,44 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
}
// Wrap the import, instantiate the module, and wrap the export.
let wasm_js_import = new WebAssembly.Function(
{parameters: ['i32'], results: ['externref']}, js_import);
let import_wrapper = WebAssembly.suspendOnReturnedPromise(wasm_js_import);
let import_wrapper = new WebAssembly.Function(
{parameters: ['externref', 'i32'], results: []},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({'m': {'import': import_wrapper}});
let export_wrapper =
WebAssembly.returnPromiseOnSuspend(instance.exports.export);
let export_wrapper = ToPromising(instance.exports.export);
// Check type errors.
wasm_js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']}, js_import);
assertThrows(() => WebAssembly.suspendOnReturnedPromise(wasm_js_import),
TypeError, /Expected a WebAssembly.Function with return type externref/);
assertThrows(() => WebAssembly.returnPromiseOnSuspend(instance.exports.wrong1),
assertThrows(() => new WebAssembly.Function(
{parameters: ['externref'], results: ['externref']},
js_import,
{suspending: 'foo'}),
TypeError,
/Expected a WebAssembly.Function with exactly one return type/);
assertThrows(() => WebAssembly.returnPromiseOnSuspend(instance.exports.wrong2),
/JS Promise Integration: Expected suspender position to be "first", "last" or "none"/);
// Bad inner signature (promising)
for (const f of [instance.exports.wrong1, instance.exports.wrong2, instance.exports.wrong3]) {
assertThrows(() => new WebAssembly.Function(
{parameters: ['i32'], results: ['externref']},
f,
{promising: 'first'}),
TypeError,
/Incompatible signature for promising function/);
}
// Signature mismatch (suspending)
assertThrows(() => new WebAssembly.Function(
{parameters: ['externref'], results: []},
new WebAssembly.Function(
{parameters: [], results: ['i32']}, js_import),
{suspending: 'first'}),
TypeError,
/Expected a WebAssembly.Function with exactly one return type/);
assertThrows(() => WebAssembly.returnPromiseOnSuspend(instance.exports.wrong3),
/Incompatible signature for suspending function/);
// Signature mismatch (promising)
assertThrows(() => new WebAssembly.Function(
{parameters: ['externref', 'i32'], results: ['i32']},
instance.exports.export,
{promising: 'first'}),
TypeError,
/Expected at least one parameter of type externref/);
// Signature mismatch (link error).
let wrong_import = new WebAssembly.Function(
{parameters: ['externref', 'f32'], results: ['externref']}, () => {});
wrong_import = WebAssembly.suspendOnReturnedPromise(wrong_import);
assertThrows(() => builder.instantiate({'m': {'import': wrong_import}}),
WebAssembly.LinkError,
/imported function does not match the expected type/);
/Incompatible signature for promising function/);
// Check the wrapped export's signature.
let export_sig = WebAssembly.Function.type(export_wrapper);
@ -73,7 +97,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
// Check the wrapped import's signature.
let import_sig = WebAssembly.Function.type(import_wrapper);
assertEquals(['externref', 'i32'], import_sig.parameters);
assertEquals(['externref'], import_sig.results);
assertEquals([], import_sig.results);
})();
(function TestStackSwitchSuspenderType() {
@ -83,7 +107,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
.addBody([kExprI32Const, 0]).exportFunc();
let instance = builder.instantiate();
let suspender = new WebAssembly.Suspender();
let wrapper = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wrapper = ToPromising(instance.exports.test);
})();
(function TestStackSwitchNoSuspend() {
@ -96,7 +120,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprGlobalSet, 0,
kExprI32Const, 0]).exportFunc();
let instance = builder.instantiate();
let wrapper = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wrapper = ToPromising(instance.exports.test);
wrapper();
assertEquals(42, instance.exports.g.value);
})();
@ -110,34 +134,34 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprLocalGet, 0,
kExprCallFunction, import_index, // suspend
]).exportFunc();
let js_import = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
() => Promise.resolve(42)));
let js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => Promise.resolve(42),
{suspending: 'first'});
let instance = builder.instantiate({m: {import: js_import}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wrapped_export = ToPromising(instance.exports.test);
let combined_promise = wrapped_export();
combined_promise.then(v => assertEquals(42, v));
assertPromiseResult(combined_promise, v => assertEquals(42, v));
// Also try with a JS function with a mismatching arity.
js_import = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
(unused) => Promise.resolve(42)));
js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
(unused) => Promise.resolve(42),
{suspending: 'first'});
instance = builder.instantiate({m: {import: js_import}});
wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
wrapped_export = ToPromising(instance.exports.test);
combined_promise = wrapped_export();
combined_promise.then(v => assertEquals(42, v));
assertPromiseResult(combined_promise, v => assertEquals(42, v));
// Also try with a proxy.
js_import = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
new Proxy(() => Promise.resolve(42), {})));
js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
new Proxy(() => Promise.resolve(42), {}),
{suspending: "first"});
instance = builder.instantiate({m: {import: js_import}});
wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
wrapped_export = ToPromising(instance.exports.test);
combined_promise = wrapped_export();
combined_promise.then(v => assertEquals(42, v));
assertPromiseResult(combined_promise, v => assertEquals(42, v));
})();
// Check that we can suspend back out of a resumed computation.
@ -175,14 +199,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
return Promise.resolve(++i);
};
let wasm_js_import = new WebAssembly.Function(
{parameters: [], results: ['externref']}, js_import);
let suspending_wasm_js_import =
WebAssembly.suspendOnReturnedPromise(wasm_js_import);
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
{parameters: ['externref'], results: ['i32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: wasm_js_import}});
let wrapped_export = ToPromising(instance.exports.test);
let chained_promise = wrapped_export();
assertEquals(0, instance.exports.g.value);
chained_promise.then(_ => assertEquals(15, instance.exports.g.value));
assertPromiseResult(chained_promise, _ => assertEquals(15, instance.exports.g.value));
})();
// Call the GC in the import call.
@ -196,12 +220,12 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprCallFunction, gc_index,
kExprI32Const, 0
]).exportFunc();
let js_import = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
gc));
let js_import = new WebAssembly.Function(
{parameters: ['externref'], results: []},
gc,
{suspending: 'first'});
let instance = builder.instantiate({'m': {'gc': js_import}});
let wrapper = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wrapper = ToPromising(instance.exports.test);
wrapper();
})();
@ -217,14 +241,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprLocalGet, 1,
kExprCallFunction, import_index,
]).exportFunc();
let js_import = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: ['i32'], results: ['externref']},
(v) => { return Promise.resolve(v) }));
let js_import = new WebAssembly.Function(
{parameters: ['externref', 'i32'], results: ['i32']},
(v) => { return Promise.resolve(v) },
{suspending: 'first'});
let instance = builder.instantiate({'m': {'import': js_import}});
let wrapper = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wrapper = ToPromising(instance.exports.test);
let arg = { valueOf: () => { gc(); return 24; } };
wrapper(arg).then((v) => assertEquals(arg.valueOf(), v));
assertPromiseResult(wrapper(arg), v => assertEquals(arg.valueOf(), v));
})();
// Check that the suspender does not suspend if the import's
@ -244,10 +268,12 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
function js_import() {
return 42
};
let wasm_js_import = new WebAssembly.Function({parameters: [], results: ['externref']}, js_import);
let suspending_wasm_js_import = WebAssembly.suspendOnReturnedPromise(wasm_js_import);
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let wasm_js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: wasm_js_import}});
let wrapped_export = ToPromising(instance.exports.test);
let result = wrapped_export();
assertEquals(42, instance.exports.g.value);
})();
@ -276,17 +302,17 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
return Promise.resolve(reduce(Array.from(arguments)));
};
let wasm_js_import = new WebAssembly.Function(
{parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'f32', 'f32',
'f32', 'f32', 'f32', 'f32', 'f32'], results: ['externref']}, js_import);
let suspending_wasm_js_import =
WebAssembly.suspendOnReturnedPromise(wasm_js_import);
{parameters: ['externref', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'f32',
'f32', 'f32', 'f32', 'f32', 'f32', 'f32'], results: ['i32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let instance = builder.instantiate({m: {import: wasm_js_import}});
let wrapped_export = ToPromising(instance.exports.test);
let args = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
let combined_promise =
wrapped_export.apply(null, args);
combined_promise.then(v => assertEquals(reduce(args), v));
assertPromiseResult(combined_promise, v => assertEquals(reduce(args), v));
})();
(function TestStackSwitchReturnFloat() {
@ -303,14 +329,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
return Promise.resolve(0.5);
};
let wasm_js_import = new WebAssembly.Function(
{parameters: [], results: ['externref']}, js_import);
let suspending_wasm_js_import =
WebAssembly.suspendOnReturnedPromise(wasm_js_import);
{parameters: ['externref'], results: ['f32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let instance = builder.instantiate({m: {import: wasm_js_import}});
let wrapped_export = ToPromising(instance.exports.test);
let combined_promise = wrapped_export();
combined_promise.then(v => assertEquals(0.5, v));
assertPromiseResult(combined_promise, v => assertEquals(0.5, v));
})();
// Throw an exception after the initial prompt.
@ -321,7 +347,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
builder.addFunction("throw", kSig_i_r)
.addBody([kExprThrow, tag]).exportFunc();
let instance = builder.instantiate();
let wrapper = WebAssembly.returnPromiseOnSuspend(instance.exports.throw);
let wrapper = ToPromising(instance.exports.throw);
try {
wrapper();
assertUnreachable();
@ -348,12 +374,12 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
return Promise.resolve(42);
};
let wasm_js_import = new WebAssembly.Function(
{parameters: [], results: ['externref']}, js_import);
let suspending_wasm_js_import =
WebAssembly.suspendOnReturnedPromise(wasm_js_import);
{parameters: ['externref'], results: ['i32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: suspending_wasm_js_import, tag: tag}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}});
let wrapped_export = ToPromising(instance.exports.test);
let combined_promise = wrapped_export();
assertThrowsAsync(combined_promise, WebAssembly.Exception);
})();
@ -376,12 +402,12 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
return Promise.reject(new WebAssembly.Exception(tag, [42]));
};
let wasm_js_import = new WebAssembly.Function(
{parameters: [], results: ['externref']}, js_import);
let suspending_wasm_js_import =
WebAssembly.suspendOnReturnedPromise(wasm_js_import);
{parameters: ['externref'], results: ['i32']},
js_import,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: suspending_wasm_js_import, tag: tag}});
let wrapped_export = WebAssembly.returnPromiseOnSuspend(instance.exports.test);
let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}});
let wrapped_export = ToPromising(instance.exports.test);
let combined_promise = wrapped_export();
assertPromiseResult(combined_promise, v => assertEquals(v, 42));
})();
@ -410,20 +436,20 @@ function TestNestedSuspenders(suspend) {
kExprCallFunction, inner_index
]).exportFunc();
let inner = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
() => suspend ? Promise.resolve(42) : 42));
let inner = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => suspend ? Promise.resolve(42) : 42,
{suspending: 'first'});
let export_inner;
let outer = WebAssembly.suspendOnReturnedPromise(
new WebAssembly.Function(
{parameters: [], results: ['externref']},
() => export_inner()));
let outer = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => export_inner(),
{suspending: 'first'});
let instance = builder.instantiate({m: {inner, outer}});
export_inner = WebAssembly.returnPromiseOnSuspend(instance.exports.inner);
let export_outer = WebAssembly.returnPromiseOnSuspend(instance.exports.outer);
export_inner = ToPromising(instance.exports.inner);
let export_outer = ToPromising(instance.exports.outer);
if (suspend) {
assertPromiseResult(export_outer(), v => assertEquals(42, v));
} else {