summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/JSPI.cpp60
-rw-r--r--test/lit/passes/jspi-args.wast69
2 files changed, 123 insertions, 6 deletions
diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp
index 48f6207ed..aa1546984 100644
--- a/src/passes/JSPI.cpp
+++ b/src/passes/JSPI.cpp
@@ -22,26 +22,71 @@
#include "ir/utils.h"
#include "pass.h"
#include "shared-constants.h"
+#include "support/file.h"
+#include "support/string.h"
#include "wasm-builder.h"
#include "wasm.h"
#include <utility>
//
// Convert a module to be compatible with JavaScript promise integration (JSPI).
-// All exports will be wrapped with a function that will handle storing
+// Promising exports will be wrapped with a function that will handle storing
// the suspsender that is passed in as the first param from a "promising"
-// `WebAssembly.Function`. All imports will also be wrapped, but they will take
-// the stored suspender and pass it as the first param to the imported function
-// that should be created from a "suspending" `WebAssembly.Function`.
+// `WebAssembly.Function`. Suspending imports will also be wrapped, but they
+// will take the stored suspender and pass it as the first param to the imported
+// function that should be created from a "suspending" `WebAssembly.Function`.
//
+// By default all imports and exports will be wrapped, but this can be
+// controlled with the following options:
+//
+// --pass-arg=jspi-imports@module1.base1,module2.base2,module3.base3
+//
+// Wrap each import in the comma-separated list. Wildcards and a separate
+// files are supported. See `asyncify-imports` for more details.
+//
+// --pass-arg=jspi-exports@function_one,function_two,function_three
+//
+// Wrap each export in the comma-separated list. Similar to jspi-imports,
+// wildcards and separate files are supported.
+//
+
namespace wasm {
+static std::string getFullFunctionName(Name module, Name base) {
+ return std::string(module.str) + '.' + base.toString();
+}
+
+static bool canChangeState(std::string name, String::Split stateChangers) {
+ // When no state changers are given default to everything changes state.
+ if (stateChangers.empty()) {
+ return true;
+ }
+ for (auto& stateChanger : stateChangers) {
+ if (String::wildcardMatch(stateChanger, name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
struct JSPI : public Pass {
Type externref = Type(HeapType::ext, Nullable);
void run(Module* module) override {
Builder builder(*module);
+
+ auto& options = getPassOptions();
+ // Find which imports can suspend.
+ auto stateChangingImports = String::trim(read_possible_response_file(
+ options.getArgumentOrDefault("jspi-imports", "")));
+ String::Split listedImports(stateChangingImports, ",");
+
+ // Find which exports should create a promise.
+ auto stateChangingExports = String::trim(read_possible_response_file(
+ options.getArgumentOrDefault("jspi-exports", "")));
+ String::Split listedExports(stateChangingExports, ",");
+
// Create a global to store the suspender that is passed into exported
// functions and will then need to be passed out to the imported functions.
Name suspender = Names::getValidGlobalName(*module, "suspender");
@@ -57,7 +102,8 @@ struct JSPI : public Pass {
// Wrap each exported function in a function that stores the suspender
// and calls the original export.
for (auto& ex : module->exports) {
- if (ex->kind == ExternalKind::Function) {
+ if (ex->kind == ExternalKind::Function &&
+ canChangeState(ex->name.toString(), listedExports)) {
auto* func = module->getFunction(ex->value);
Name wrapperName;
auto iter = wrappedExports.find(func->name);
@@ -79,7 +125,9 @@ struct JSPI : public Pass {
// Wrap each imported function in a function that gets the global suspender
// and passes it on to the imported function.
for (auto* im : originalFunctions) {
- if (im->imported()) {
+ if (im->imported() &&
+ canChangeState(getFullFunctionName(im->module, im->base),
+ listedImports)) {
makeWrapperForImport(im, module, suspender);
}
}
diff --git a/test/lit/passes/jspi-args.wast b/test/lit/passes/jspi-args.wast
new file mode 100644
index 000000000..237b31213
--- /dev/null
+++ b/test/lit/passes/jspi-args.wast
@@ -0,0 +1,69 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s --jspi --pass-arg=jspi-imports@js.sleep_async --pass-arg=jspi-exports@update_state_async -all -S -o - | filecheck %s
+
+(module
+ ;; sleep_async should have a wrapper function built.
+ ;; CHECK: (type $f64_=>_i32 (func (param f64) (result i32)))
+
+ ;; CHECK: (type $externref_f64_=>_i32 (func (param externref f64) (result i32)))
+
+ ;; CHECK: (import "js" "sleep_sync" (func $sleep_sync (param f64) (result i32)))
+ (import "js" "sleep_async" (func $sleep_async (param f64) (result i32)))
+ ;; CHECK: (import "js" "sleep_async" (func $import$sleep_async (param externref f64) (result i32)))
+ (import "js" "sleep_sync" (func $sleep_sync (param f64) (result i32)))
+ ;; CHECK: (global $suspender (mut externref) (ref.null noextern))
+
+ ;; CHECK: (export "update_state_async" (func $export$update_state_async))
+ (export "update_state_async" (func $update_state_async))
+ ;; CHECK: (export "update_state_sync" (func $update_state_sync))
+ (export "update_state_sync" (func $update_state_sync))
+ ;; This function calls an async sleep so a wrapper should be created for it.
+ ;; CHECK: (func $update_state_async (param $param f64) (result i32)
+ ;; CHECK-NEXT: (call $sleep_async
+ ;; CHECK-NEXT: (f64.sub
+ ;; CHECK-NEXT: (f64.const 1.1)
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update_state_async (param $param f64) (result i32)
+ (call $sleep_async (f64.sub (f64.const 1.1) (local.get $param)))
+ )
+ ;; CHECK: (func $update_state_sync (param $param f64) (result i32)
+ ;; CHECK-NEXT: (call $sleep_sync
+ ;; CHECK-NEXT: (f64.sub
+ ;; CHECK-NEXT: (f64.const 1.1)
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update_state_sync (param $param f64) (result i32)
+ (call $sleep_sync (f64.sub (f64.const 1.1) (local.get $param)))
+ )
+)
+;; CHECK: (func $export$update_state_async (param $susp externref) (param $param f64) (result i32)
+;; CHECK-NEXT: (global.set $suspender
+;; CHECK-NEXT: (local.get $susp)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (call $update_state_async
+;; CHECK-NEXT: (local.get $param)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+
+;; CHECK: (func $sleep_async (param $0 f64) (result i32)
+;; CHECK-NEXT: (local $1 externref)
+;; CHECK-NEXT: (local $2 i32)
+;; CHECK-NEXT: (local.set $1
+;; CHECK-NEXT: (global.get $suspender)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $2
+;; CHECK-NEXT: (call $import$sleep_async
+;; CHECK-NEXT: (global.get $suspender)
+;; CHECK-NEXT: (local.get $0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (global.set $suspender
+;; CHECK-NEXT: (local.get $1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.get $2)
+;; CHECK-NEXT: )