From 5e045d9754fd7d77705d3ab4c692f22709fa79a5 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 26 Nov 2025 15:05:43 -0800 Subject: [PATCH] Add asyncify-export-globals argument In the past emscripten would build the main module as relocatable, but these days we build it statically and in that case it makes sense for the main module to define and export these globals, rather than defining them in JS. --- src/passes/Asyncify.cpp | 34 +++++- ...cify_pass-arg=asyncify-export-globals.wast | 11 ++ ...cify_pass-arg=asyncify-import-globals.wast | 10 ++ ...syncify_pass-arg=asyncify-side-module.wast | 114 ------------------ 4 files changed, 52 insertions(+), 117 deletions(-) create mode 100644 test/lit/passes/asyncify_pass-arg=asyncify-export-globals.wast create mode 100644 test/lit/passes/asyncify_pass-arg=asyncify-import-globals.wast delete mode 100644 test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index cadf0871b4d..48f9cc59419 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -323,6 +323,20 @@ // model where you can specify "instrument, but not indirect calls from me" // would likely have little benefit.) // +// In addition, there are arguments for controlling the import/export of the +// internal globals used by Asyncify. These can be useful in dynamic linking. +// By default these globals are are internal and neither imported nor exported. +// +// --pass-arg=import-globals +// +// Import the internal globals used by Asyncify. This allows them to be +// defined in another module. +// +// --pass-arg=export-globals +// +// Export the internal globals used by Asyncify. This allows them to be +// imported into anther module built with --pass-arg=import-globals +// // TODO When wasm has GC, extending the live ranges of locals can keep things // alive unnecessarily. We may want to set locals to null at the end // of their original range. @@ -1759,7 +1773,11 @@ struct Asyncify : public Pass { String::Split::NewLineOr(",")); auto asserts = hasArgument("asyncify-asserts"); auto verbose = hasArgument("asyncify-verbose"); - auto relocatable = hasArgument("asyncify-relocatable"); + // TODO: Remove the legacy asyncify-relocatable name once emscripten is + // updated. + auto importGlobals = hasArgument("asyncify-import-globals") || + hasArgument("asyncify-relocatable"); + auto exportGlobals = hasArgument("asyncify-export-globals"); auto secondaryMemory = hasArgument("asyncify-in-secondary-memory"); auto propagateAddList = hasArgument("asyncify-propagate-addlist"); @@ -1826,7 +1844,7 @@ struct Asyncify : public Pass { verbose); // Add necessary globals before we emit code to use them. - addGlobals(module, relocatable); + addGlobals(module, importGlobals, exportGlobals); // Compute the set of functions we will instrument. All of the passes we run // below only need to run there. @@ -1904,8 +1922,11 @@ struct Asyncify : public Pass { } private: - void addGlobals(Module* module, bool imported) { + void addGlobals(Module* module, bool imported, bool exported) { Builder builder(*module); + // It doesn't make sense to both import and export these globals at the + // same time. + assert(!(imported && exported)); auto asyncifyState = builder.makeGlobal(ASYNCIFY_STATE, Type::i32, @@ -1926,6 +1947,13 @@ struct Asyncify : public Pass { asyncifyData->base = ASYNCIFY_DATA; } module->addGlobal(std::move(asyncifyData)); + + if (exported) { + module->addExport(builder.makeExport( + ASYNCIFY_STATE, ASYNCIFY_STATE, ExternalKind::Global)); + module->addExport( + builder.makeExport(ASYNCIFY_DATA, ASYNCIFY_DATA, ExternalKind::Global)); + } } void addFunctions(Module* module) { diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-export-globals.wast b/test/lit/passes/asyncify_pass-arg=asyncify-export-globals.wast new file mode 100644 index 00000000000..e50e5b65902 --- /dev/null +++ b/test/lit/passes/asyncify_pass-arg=asyncify-export-globals.wast @@ -0,0 +1,11 @@ +;; RUN: wasm-opt --enable-mutable-globals --asyncify --pass-arg=asyncify-export-globals -S -o - | filecheck %s + +(module +) +;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + +;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + +;; CHECK: (export "__asyncify_state" (global $__asyncify_state)) + +;; CHECK: (export "__asyncify_data" (global $__asyncify_data)) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-import-globals.wast b/test/lit/passes/asyncify_pass-arg=asyncify-import-globals.wast new file mode 100644 index 00000000000..30059fc0a69 --- /dev/null +++ b/test/lit/passes/asyncify_pass-arg=asyncify-import-globals.wast @@ -0,0 +1,10 @@ +;; RUN: wasm-opt --enable-mutable-globals --asyncify --pass-arg=asyncify-import-globals -S -o - | filecheck %s + +;; Also test asyncify-relocatable which is an alias for asyncify-import-globals +;; RUN: wasm-opt --enable-mutable-globals --asyncify --pass-arg=asyncify-relocatable -S -o - | filecheck %s + +(module +) + +;; CHECK: (import "env" "__asyncify_state" (global $__asyncify_state (mut i32))) +;; CHECK: (import "env" "__asyncify_data" (global $__asyncify_data (mut i32))) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast b/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast deleted file mode 100644 index 9dee0f8c011..00000000000 --- a/test/lit/passes/asyncify_pass-arg=asyncify-side-module.wast +++ /dev/null @@ -1,114 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. - -;; RUN: foreach %s %t wasm-opt --enable-mutable-globals --asyncify --pass-arg=asyncify-relocatable -S -o - | filecheck %s - -(module -) -;; CHECK: (type $0 (func (param i32))) - -;; CHECK: (type $1 (func)) - -;; CHECK: (type $2 (func (result i32))) - -;; CHECK: (import "env" "__asyncify_state" (global $__asyncify_state (mut i32))) - -;; CHECK: (import "env" "__asyncify_data" (global $__asyncify_data (mut i32))) - -;; CHECK: (memory $0 1 1) - -;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) - -;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) - -;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) - -;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) - -;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) - -;; CHECK: (func $asyncify_start_unwind (param $0 i32) -;; CHECK-NEXT: (global.set $__asyncify_state -;; CHECK-NEXT: (i32.const 1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $__asyncify_data -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.gt_u -;; CHECK-NEXT: (i32.load -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.load offset=4 -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (unreachable) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $asyncify_stop_unwind -;; CHECK-NEXT: (global.set $__asyncify_state -;; CHECK-NEXT: (i32.const 0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.gt_u -;; CHECK-NEXT: (i32.load -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.load offset=4 -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (unreachable) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $asyncify_start_rewind (param $0 i32) -;; CHECK-NEXT: (global.set $__asyncify_state -;; CHECK-NEXT: (i32.const 2) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $__asyncify_data -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.gt_u -;; CHECK-NEXT: (i32.load -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.load offset=4 -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (unreachable) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $asyncify_stop_rewind -;; CHECK-NEXT: (global.set $__asyncify_state -;; CHECK-NEXT: (i32.const 0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.gt_u -;; CHECK-NEXT: (i32.load -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.load offset=4 -;; CHECK-NEXT: (global.get $__asyncify_data) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (unreachable) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $asyncify_get_state (result i32) -;; CHECK-NEXT: (global.get $__asyncify_state) -;; CHECK-NEXT: )