diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index 1394cd015e2..5d022c6a0d7 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -503,6 +503,8 @@ class ModuleAnalyzer { return info.canChangeState && !info.isTopMostRuntime; } + bool canChangeState(Function* func) { return map[func].canChangeState; } + bool canChangeState(Expression* curr) { // Look inside to see if we call any of the things we know can change the // state. @@ -1150,6 +1152,32 @@ struct Asyncify : public Pass { runner.setValidateGlobally(false); runner.run(); } + // Mark exported asyncified functions. + // + // This creates a custom section for each function that: + // 1) Is exported and, thus, directly accessible by user. + // 2) Might directly or indirectly start an async operation (and unwind). + // + // Each such section will have a name "asyncify" and name of the export as + // its data. + // + // This gives the JavaScript side an opportunity to wrap functions into an + // `async` variant only if it's necessary, and leave others intact. + auto& userSections = module->userSections; + for (auto& exp : module->exports) { + if (exp->kind != ExternalKind::Function) { + continue; + } + auto* func = module->getFunction(exp->value); + if (!analyzer.canChangeState(func)) { + continue; + } + userSections.resize(userSections.size() + 1); + auto& section = userSections.back(); + section.name = BinaryConsts::UserSections::Asyncify; + auto& name = exp->name; + section.data.assign(name.c_str(), name.c_str() + name.size()); + } // Finally, add function support (that should not have been seen by // the previous passes). addFunctions(module); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index ad4d20baee7..20c58585912 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2122,6 +2122,25 @@ struct PrintSExpression : public OverriddenVisitor { doIndent(o, indent); o << ";; custom section \"" << section.name << "\", size " << section.data.size(); + bool isPrintable = true; + for (auto c : section.data) { + if (!isprint(c)) { + isPrintable = false; + break; + } + } + if (isPrintable) { + o << ", contents: "; + // std::quoted is not available in all the supported compilers yet. + o << '"'; + for (auto c : section.data) { + if (c == '\\' || c == '"') { + o << '\\'; + } + o << c; + } + o << '"'; + } o << maybeNewLine; } decIndent(); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 1c5fa774a58..4eaf7deabe9 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -398,6 +398,7 @@ extern const char* Dylink; extern const char* Linking; extern const char* Producers; extern const char* TargetFeatures; +extern const char* Asyncify; extern const char* AtomicsFeature; extern const char* BulkMemoryFeature; diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ec23b5c9468..0bb2e3afe01 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -33,6 +33,7 @@ const char* Dylink = "dylink"; const char* Linking = "linking"; const char* Producers = "producers"; const char* TargetFeatures = "target_features"; +const char* Asyncify = "asyncify"; const char* AtomicsFeature = "atomics"; const char* BulkMemoryFeature = "bulk-memory"; const char* ExceptionHandlingFeature = "exception-handling"; diff --git a/test/fib-dbg.wasm.fromBinary b/test/fib-dbg.wasm.fromBinary index 9645ec67546..297911b8df4 100644 --- a/test/fib-dbg.wasm.fromBinary +++ b/test/fib-dbg.wasm.fromBinary @@ -214,6 +214,6 @@ (local $0 i32) (nop) ) - ;; custom section "sourceMappingURL", size 35 + ;; custom section "sourceMappingURL", size 35, contents: "\"http://localhost:8000/fib.wasm.map" ) diff --git a/test/passes/asyncify.txt b/test/passes/asyncify.txt index 317e6f442dc..7c84221ad73 100644 --- a/test/passes/asyncify.txt +++ b/test/passes/asyncify.txt @@ -5,6 +5,12 @@ (global $sleeping (mut i32) (i32.const 0)) (global $__asyncify_state (mut i32) (i32.const 0)) (global $__asyncify_data (mut i32) (i32.const 0)) + (export "do_sleep" (func $do_sleep)) + (export "work" (func $work)) + (export "stuff" (func $stuff)) + (export "first_event" (func $first_event)) + (export "second_event" (func $second_event)) + (export "never_sleep" (func $never_sleep)) (export "asyncify_start_unwind" (func $asyncify_start_unwind)) (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) (export "asyncify_start_rewind" (func $asyncify_start_rewind)) @@ -366,6 +372,9 @@ (unreachable) ) ) + ;; custom section "asyncify", size 8, contents: "do_sleep" + ;; custom section "asyncify", size 4, contents: "work" + ;; custom section "asyncify", size 11, contents: "first_event" ) (module (type $FUNCSIG$v (func)) @@ -378,6 +387,22 @@ (memory $0 1 2) (global $__asyncify_state (mut i32) (i32.const 0)) (global $__asyncify_data (mut i32) (i32.const 0)) + (export "calls-import" (func $calls-import)) + (export "calls-import2" (func $calls-import2)) + (export "calls-import2-drop" (func $calls-import2-drop)) + (export "calls-nothing" (func $calls-nothing)) + (export "many-locals" (func $many-locals)) + (export "calls-import2-if" (func $calls-import2-if)) + (export "alls-import2-if-else" (func $calls-import2-if-else)) + (export "calls-import2-if-else-oneside" (func $calls-import2-if-else-oneside)) + (export "calls-import2-if-else-oneside2" (func $calls-import2-if-else-oneside2)) + (export "calls-loop" (func $calls-loop)) + (export "calls-loop2" (func $calls-loop2)) + (export "calls-mix" (func $calls-mix)) + (export "boring" (func $boring)) + (export "calls-mix-deep" (func $calls-mix-deep)) + (export "boring-deep" (func $boring-deep)) + (export "import-deep" (func $import-deep)) (export "asyncify_start_unwind" (func $asyncify_start_unwind)) (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) (export "asyncify_start_rewind" (func $asyncify_start_rewind)) @@ -2899,6 +2924,19 @@ (unreachable) ) ) + ;; custom section "asyncify", size 12, contents: "calls-import" + ;; custom section "asyncify", size 13, contents: "calls-import2" + ;; custom section "asyncify", size 18, contents: "calls-import2-drop" + ;; custom section "asyncify", size 11, contents: "many-locals" + ;; custom section "asyncify", size 16, contents: "calls-import2-if" + ;; custom section "asyncify", size 20, contents: "alls-import2-if-else" + ;; custom section "asyncify", size 29, contents: "calls-import2-if-else-oneside" + ;; custom section "asyncify", size 30, contents: "calls-import2-if-else-oneside2" + ;; custom section "asyncify", size 10, contents: "calls-loop" + ;; custom section "asyncify", size 11, contents: "calls-loop2" + ;; custom section "asyncify", size 9, contents: "calls-mix" + ;; custom section "asyncify", size 14, contents: "calls-mix-deep" + ;; custom section "asyncify", size 11, contents: "import-deep" ) (module (memory $0 1 1) diff --git a/test/passes/asyncify.wast b/test/passes/asyncify.wast index 6f4754b7416..9c8a0c30395 100644 --- a/test/passes/asyncify.wast +++ b/test/passes/asyncify.wast @@ -8,7 +8,7 @@ (global $sleeping (mut i32) (i32.const 0)) ;; do a sleep operation: start a sleep if running, or resume after a sleep ;; if we just rewound. - (func $do_sleep + (func $do_sleep (export "do_sleep") (if (i32.eqz (global.get $sleeping)) (block @@ -23,27 +23,27 @@ ) ) ;; a function that does some work and has a sleep (async pause/resume) in the middle - (func $work + (func $work (export "work") (call $stuff) ;; do some work (call $do_sleep) ;; take a break (call $stuff) ;; do some more work ) - (func $stuff) + (func $stuff (export "stuff")) ;; the first event called from the main event loop: just call into $work - (func $first_event + (func $first_event (export "first_event") (call $work) ;; work will sleep, so we exit through here while it is paused ) ;; the second event called from the main event loop: to resume $work, - ;; stop the unwind, then prepare a rewind, and initiate it by doing + ;; stop the unwind, then prepare a rewind, and initiate it by doing ;; the call to rewind the call stack back up to where it was - (func $second_event + (func $second_event (export "second_event") (call $asyncify_stop_unwind) (call $asyncify_start_rewind (i32.const 4)) (call $work) ) ;; a function that can't do a sleep - (func $never_sleep + (func $never_sleep (export "never_sleep") (call $stuff) (call $stuff) (call $stuff) @@ -55,21 +55,21 @@ (import "env" "import" (func $import)) (import "env" "import2" (func $import2 (result i32))) (import "env" "import3" (func $import3 (param i32))) - (func $calls-import + (func $calls-import (export "calls-import") (call $import) ) - (func $calls-import2 (result i32) + (func $calls-import2 (export "calls-import2") (result i32) (local $temp i32) (local.set $temp (call $import2)) (return (local.get $temp)) ) - (func $calls-import2-drop + (func $calls-import2-drop (export "calls-import2-drop") (drop (call $import2)) ) - (func $calls-nothing + (func $calls-nothing (export "calls-nothing") (drop (i32.eqz (i32.const 17))) ) - (func $many-locals (param $x i32) (result i32) + (func $many-locals (export "many-locals") (param $x i32) (result i32) (local $y i32) (loop $l (local.set $x @@ -83,32 +83,32 @@ (call $import) (return (local.get $y)) ) - (func $calls-import2-if (param $x i32) + (func $calls-import2-if (export "calls-import2-if") (param $x i32) (if (local.get $x) (call $import) ) ) - (func $calls-import2-if-else (param $x i32) + (func $calls-import2-if-else (export "alls-import2-if-else") (param $x i32) (if (local.get $x) (call $import3 (i32.const 1)) (call $import3 (i32.const 2)) ) ) - (func $calls-import2-if-else-oneside (param $x i32) (result i32) + (func $calls-import2-if-else-oneside (export "calls-import2-if-else-oneside") (param $x i32) (result i32) (if (local.get $x) (return (i32.const 1)) (call $import3 (i32.const 2)) ) (return (i32.const 3)) ) - (func $calls-import2-if-else-oneside2 (param $x i32) (result i32) + (func $calls-import2-if-else-oneside2 (export "calls-import2-if-else-oneside2") (param $x i32) (result i32) (if (local.get $x) (call $import3 (i32.const 1)) (return (i32.const 2)) ) (return (i32.const 3)) ) - (func $calls-loop (param $x i32) + (func $calls-loop (export "calls-loop") (param $x i32) (loop $l (call $import3 (i32.const 1)) (local.set $x @@ -119,30 +119,30 @@ ) ) ) - (func $calls-loop2 + (func $calls-loop2 (export "calls-loop2") (loop $l (br_if $l (call $import2) ) ) ) - (func $calls-mix + (func $calls-mix (export "calls-mix") (call $boring) (call $import) (call $boring) (call $import) ) - (func $boring) - (func $calls-mix-deep + (func $boring (export "boring")) + (func $calls-mix-deep (export "calls-mix-deep") (call $boring-deep) (call $import-deep) (call $boring) (call $import) ) - (func $boring-deep + (func $boring-deep (export "boring-deep") (call $boring) ) - (func $import-deep + (func $import-deep (export "import-deep") (call $import) ) )