diff options
-rw-r--r-- | src/passes/JSPI.cpp | 60 | ||||
-rw-r--r-- | test/lit/passes/jspi-args.wast | 69 |
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: ) |