Repository: bytecodealliance/wasm-tools
Pull request #1431 (2024-02-29 09:37:27)

wit-component: Support Asyncified modules with shared-everything link
This commit adds support for modules Asyncified with `--pass-arg=asyncify-relocatable` to the shared-everything linker. It adds the `__asyncify_state` and `__asyncify_data` mutable globals definitions as it does for `__stack_pointer` so that the Asyncify global state can be shared across all modules.

This unlocks CRuby to link shared-everything modules.

kateinoigakukun: CC: @dicej 

Patch:
diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs
index 240197ca..4488aa8f 100644
--- a/crates/wit-component/src/linking.rs
+++ b/crates/wit-component/src/linking.rs
@@ -358,6 +358,14 @@ fn make_env_module<'a>(
 
         add_global_export("__stack_pointer", stack_size_bytes, true);
 
+        // Binaryen's Asyncify transform for shared everything linking requires these globals
+        // to be provided from env module
+        let has_asyncified_module = metadata.iter().any(|m| m.is_asyncified);
+        if has_asyncified_module {
+            add_global_export("__asyncify_state", 0, true);
+            add_global_export("__asyncify_data", 0, true);
+        }
+
         for metadata in metadata {
             memory_offset = align(memory_offset, 1 << metadata.mem_info.memory_alignment);
             table_offset = align(table_offset, 1 << metadata.mem_info.table_alignment);
@@ -1436,6 +1444,24 @@ impl Linker {
                         name: (*name).into(),
                     }
                 }))
+                .chain(if metadata.is_asyncified {
+                    vec![
+                        Item {
+                            alias: "__asyncify_state".into(),
+                            kind: ExportKind::Global,
+                            which: MainOrAdapter::Main,
+                            name: "__asyncify_state".into(),
+                        },
+                        Item {
+                            alias: "__asyncify_data".into(),
+                            kind: ExportKind::Global,
+                            which: MainOrAdapter::Main,
+                            name: "__asyncify_data".into(),
+                        },
+                    ]
+                } else {
+                    vec![]
+                })
                 .collect();
 
             let global_item = |address_name: &str| Item {
diff --git a/crates/wit-component/src/linking/metadata.rs b/crates/wit-component/src/linking/metadata.rs
index 954f3355..f641b2c7 100644
--- a/crates/wit-component/src/linking/metadata.rs
+++ b/crates/wit-component/src/linking/metadata.rs
@@ -211,6 +211,10 @@ pub struct Metadata<'a> {
     /// Whether this module includes any `component-type*` custom sections which include exports
     pub has_component_exports: bool,
 
+    /// Whether this module imports `__asyncify_state` or `__asyncify_data`, indicating that it is
+    /// asyncified with `--pass-arg=asyncify-relocatable` option.
+    pub is_asyncified: bool,
+
     /// The functions imported from the `env` module, if any
     pub env_imports: BTreeSet<(&'a str, (FunctionType, SymbolFlags))>,
 
@@ -313,6 +317,7 @@ impl<'a> Metadata<'a> {
             has_wasi_start: false,
             has_set_libraries: false,
             has_component_exports,
+            is_asyncified: false,
             env_imports: BTreeSet::new(),
             memory_address_imports: BTreeSet::new(),
             table_address_imports: BTreeSet::new(),
@@ -380,6 +385,18 @@ impl<'a> Metadata<'a> {
                                     return type_error();
                                 }
                             }
+                            ("env", "__asyncify_data" | "__asyncify_state") => {
+                                result.is_asyncified = true;
+                                if !matches!(
+                                    import.ty,
+                                    TypeRef::Global(wasmparser::GlobalType {
+                                        content_type: ValType::I32,
+                                        ..
+                                    })
+                                ) {
+                                    return type_error();
+                                }
+                            }
                             ("env", "__memory_base" | "__table_base" | "__stack_pointer") => {
                                 if !matches!(
                                     import.ty,


Test patch:
diff --git a/crates/wit-component/tests/components/link-asyncify/component.wat b/crates/wit-component/tests/components/link-asyncify/component.wat
new file mode 100644
index 00000000..93c1dae9
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/component.wat
@@ -0,0 +1,290 @@
+(component
+  (core module (;0;)
+    (table (;0;) 1 funcref)
+    (memory (;0;) 17)
+    (global (;0;) (mut i32) i32.const 1048576)
+    (global (;1;) (mut i32) i32.const 0)
+    (global (;2;) (mut i32) i32.const 0)
+    (global (;3;) i32 i32.const 1048592)
+    (global (;4;) i32 i32.const 1)
+    (global (;5;) i32 i32.const 1048608)
+    (global (;6;) i32 i32.const 1)
+    (global (;7;) (mut i32) i32.const 1048624)
+    (global (;8;) (mut i32) i32.const 1114112)
+    (export "__stack_pointer" (global 0))
+    (export "__asyncify_state" (global 1))
+    (export "__asyncify_data" (global 2))
+    (export "bar:memory_base" (global 3))
+    (export "bar:table_base" (global 4))
+    (export "foo:memory_base" (global 5))
+    (export "foo:table_base" (global 6))
+    (export "__heap_base" (global 7))
+    (export "__heap_end" (global 8))
+    (export "__indirect_function_table" (table 0))
+    (export "memory" (memory 0))
+    (@producers
+      (processed-by "wit-component" "$CARGO_PKG_VERSION")
+    )
+  )
+  (core module (;1;)
+    (@dylink.0
+      (mem-info (memory 1 4))
+      (needed "foo")
+    )
+    (type (;0;) (func))
+    (type (;1;) (func (param i32)))
+    (type (;2;) (func (result i32)))
+    (import "env" "__memory_base" (global $__memory_base (;0;) i32))
+    (import "env" "__table_base" (global $__table_base (;1;) i32))
+    (import "env" "__asyncify_state" (global (;2;) (mut i32)))
+    (import "env" "__asyncify_data" (global (;3;) (mut i32)))
+    (import "env" "memory" (memory (;0;) 1))
+    (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
+    (func $__wasm_call_ctors (;0;) (type 0))
+    (func $__wasm_apply_data_relocs (;1;) (type 0))
+    (func $asyncify_start_unwind (;2;) (type 1) (param i32)
+      i32.const 1
+      global.set 2
+      local.get 0
+      global.set 3
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_stop_unwind (;3;) (type 0)
+      i32.const 0
+      global.set 2
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_start_rewind (;4;) (type 1) (param i32)
+      i32.const 2
+      global.set 2
+      local.get 0
+      global.set 3
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_stop_rewind (;5;) (type 0)
+      i32.const 0
+      global.set 2
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_get_state (;6;) (type 2) (result i32)
+      global.get 2
+    )
+    (func $bar (;7;) (type 0))
+    (export "test:test/test#bar" (func $bar))
+    (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
+    (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+    (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+    (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+    (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+    (export "asyncify_get_state" (func $asyncify_get_state))
+    (data $.bss (;0;) (global.get $__memory_base) "\00\00\00\00")
+  )
+  (core module (;2;)
+    (@dylink.0
+      (mem-info (memory 1 4))
+      (needed "bar")
+    )
+    (type (;0;) (func))
+    (type (;1;) (func (param i32)))
+    (type (;2;) (func (result i32)))
+    (import "env" "__memory_base" (global $__memory_base (;0;) i32))
+    (import "env" "__table_base" (global $__table_base (;1;) i32))
+    (import "env" "__asyncify_state" (global (;2;) (mut i32)))
+    (import "env" "__asyncify_data" (global (;3;) (mut i32)))
+    (import "env" "memory" (memory (;0;) 1))
+    (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
+    (func $__wasm_call_ctors (;0;) (type 0))
+    (func $__wasm_apply_data_relocs (;1;) (type 0))
+    (func $asyncify_start_unwind (;2;) (type 1) (param i32)
+      i32.const 1
+      global.set 2
+      local.get 0
+      global.set 3
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_stop_unwind (;3;) (type 0)
+      i32.const 0
+      global.set 2
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_start_rewind (;4;) (type 1) (param i32)
+      i32.const 2
+      global.set 2
+      local.get 0
+      global.set 3
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_stop_rewind (;5;) (type 0)
+      i32.const 0
+      global.set 2
+      global.get 3
+      i32.load
+      global.get 3
+      i32.load offset=4
+      i32.gt_u
+      if ;; label = @1
+        unreachable
+      end
+    )
+    (func $asyncify_get_state (;6;) (type 2) (result i32)
+      global.get 2
+    )
+    (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
+    (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+    (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+    (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+    (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+    (export "asyncify_get_state" (func $asyncify_get_state))
+  )
+  (core module (;3;)
+    (type (;0;) (func))
+    (type (;1;) (func (param i32)))
+    (import "env" "memory" (memory (;0;) 0))
+    (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
+    (import "bar" "__wasm_apply_data_relocs" (func (;0;) (type 0)))
+    (import "foo" "__wasm_apply_data_relocs" (func (;1;) (type 0)))
+    (func (;2;) (type 0)
+      call 0
+      call 1
+    )
+    (start 2)
+    (elem (;0;) (i32.const 1) func)
+    (elem (;1;) (i32.const 1) func)
+    (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00")
+    (@producers
+      (processed-by "wit-component" "$CARGO_PKG_VERSION")
+    )
+  )
+  (core instance (;0;) (instantiate 0))
+  (alias core export 0 "memory" (core memory (;0;)))
+  (alias core export 0 "__heap_base" (core global (;0;)))
+  (alias core export 0 "__heap_end" (core global (;1;)))
+  (core instance (;1;)
+    (export "__heap_base" (global 0))
+    (export "__heap_end" (global 1))
+  )
+  (core instance (;2;))
+  (alias core export 0 "memory" (core memory (;1;)))
+  (alias core export 0 "__indirect_function_table" (core table (;0;)))
+  (alias core export 0 "__stack_pointer" (core global (;2;)))
+  (alias core export 0 "bar:memory_base" (core global (;3;)))
+  (alias core export 0 "bar:table_base" (core global (;4;)))
+  (alias core export 0 "__asyncify_state" (core global (;5;)))
+  (alias core export 0 "__asyncify_data" (core global (;6;)))
+  (core instance (;3;)
+    (export "memory" (memory 1))
+    (export "__indirect_function_table" (table 0))
+    (export "__stack_pointer" (global 2))
+    (export "__memory_base" (global 3))
+    (export "__table_base" (global 4))
+    (export "__asyncify_state" (global 5))
+    (export "__asyncify_data" (global 6))
+  )
+  (core instance (;4;) (instantiate 1
+      (with "GOT.mem" (instance 1))
+      (with "GOT.func" (instance 2))
+      (with "env" (instance 3))
+    )
+  )
+  (alias core export 0 "__heap_base" (core global (;7;)))
+  (alias core export 0 "__heap_end" (core global (;8;)))
+  (core instance (;5;)
+    (export "__heap_base" (global 7))
+    (export "__heap_end" (global 8))
+  )
+  (core instance (;6;))
+  (alias core export 0 "memory" (core memory (;2;)))
+  (alias core export 0 "__indirect_function_table" (core table (;1;)))
+  (alias core export 0 "__stack_pointer" (core global (;9;)))
+  (alias core export 0 "foo:memory_base" (core global (;10;)))
+  (alias core export 0 "foo:table_base" (core global (;11;)))
+  (alias core export 0 "__asyncify_state" (core global (;12;)))
+  (alias core export 0 "__asyncify_data" (core global (;13;)))
+  (core instance (;7;)
+    (export "memory" (memory 2))
+    (export "__indirect_function_table" (table 1))
+    (export "__stack_pointer" (global 9))
+    (export "__memory_base" (global 10))
+    (export "__table_base" (global 11))
+    (export "__asyncify_state" (global 12))
+    (export "__asyncify_data" (global 13))
+  )
+  (core instance (;8;) (instantiate 2
+      (with "GOT.mem" (instance 5))
+      (with "GOT.func" (instance 6))
+      (with "env" (instance 7))
+    )
+  )
+  (core instance (;9;) (instantiate 3
+      (with "env" (instance 0))
+      (with "bar" (instance 4))
+      (with "foo" (instance 8))
+    )
+  )
+  (type (;0;) (func))
+  (alias core export 4 "test:test/test#bar" (core func (;0;)))
+  (func (;0;) (type 0) (canon lift (core func 0)))
+  (component (;0;)
+    (type (;0;) (func))
+    (import "import-func-bar" (func (;0;) (type 0)))
+    (type (;1;) (func))
+    (export (;1;) "bar" (func 0) (func (type 1)))
+  )
+  (instance (;0;) (instantiate 0
+      (with "import-func-bar" (func 0))
+    )
+  )
+  (export (;1;) "test:test/test" (instance 0))
+  (@producers
+    (processed-by "wit-component" "$CARGO_PKG_VERSION")
+  )
+)
diff --git a/crates/wit-component/tests/components/link-asyncify/component.wit.print b/crates/wit-component/tests/components/link-asyncify/component.wit.print
new file mode 100644
index 00000000..ad829fd0
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/component.wit.print
@@ -0,0 +1,5 @@
+package root:component;
+
+world root {
+  export test:test/test;
+}
diff --git a/crates/wit-component/tests/components/link-asyncify/lib-bar.wat b/crates/wit-component/tests/components/link-asyncify/lib-bar.wat
new file mode 100644
index 00000000..12ebf05b
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/lib-bar.wat
@@ -0,0 +1,81 @@
+(module
+  (@dylink.0
+    (mem-info (memory 1 4))
+    (needed "foo")
+  )
+  (type (func))
+  (type (func (param i32)))
+  (type (func (result i32)))
+  (import "env" "__memory_base" (global $__memory_base  i32))
+  (import "env" "__table_base" (global $__table_base  i32))
+  (import "env" "__asyncify_state" (global  (mut i32)))
+  (import "env" "__asyncify_data" (global  (mut i32)))
+  (import "env" "memory" (memory  1))
+  (import "env" "__indirect_function_table" (table  0 funcref))
+  (func $__wasm_call_ctors  (type 0))
+  (func $__wasm_apply_data_relocs  (type 0))
+  (func $asyncify_start_unwind  (type 1) (param i32)
+    i32.const 1
+    global.set 2
+    local.get 0
+    global.set 3
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_stop_unwind  (type 0)
+    i32.const 0
+    global.set 2
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_start_rewind  (type 1) (param i32)
+    i32.const 2
+    global.set 2
+    local.get 0
+    global.set 3
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_stop_rewind  (type 0)
+    i32.const 0
+    global.set 2
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_get_state  (type 2) (result i32)
+    global.get 2
+  )
+  (func $bar (type 0))
+  (export "test:test/test#bar" (func $bar))
+  (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
+  (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+  (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+  (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+  (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+  (export "asyncify_get_state" (func $asyncify_get_state))
+  (data $.bss  (global.get $__memory_base) "\00\00\00\00")
+)
diff --git a/crates/wit-component/tests/components/link-asyncify/lib-bar.wit b/crates/wit-component/tests/components/link-asyncify/lib-bar.wit
new file mode 100644
index 00000000..3e13ebdf
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/lib-bar.wit
@@ -0,0 +1,9 @@
+package test:test;
+
+interface test {
+   bar: func();
+}
+
+world lib-bar {
+    export test;
+}
diff --git a/crates/wit-component/tests/components/link-asyncify/lib-foo.wat b/crates/wit-component/tests/components/link-asyncify/lib-foo.wat
new file mode 100644
index 00000000..f47168cf
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/lib-foo.wat
@@ -0,0 +1,78 @@
+(module
+  (@dylink.0
+    (mem-info (memory 1 4))
+    (needed "bar")
+  )
+  (type (func))
+  (type (func (param i32)))
+  (type (func (result i32)))
+  (import "env" "__memory_base" (global $__memory_base  i32))
+  (import "env" "__table_base" (global $__table_base  i32))
+  (import "env" "__asyncify_state" (global  (mut i32)))
+  (import "env" "__asyncify_data" (global  (mut i32)))
+  (import "env" "memory" (memory  1))
+  (import "env" "__indirect_function_table" (table  0 funcref))
+  (func $__wasm_call_ctors  (type 0))
+  (func $__wasm_apply_data_relocs  (type 0))
+  (func $asyncify_start_unwind  (type 1) (param i32)
+    i32.const 1
+    global.set 2
+    local.get 0
+    global.set 3
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_stop_unwind  (type 0)
+    i32.const 0
+    global.set 2
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_start_rewind  (type 1) (param i32)
+    i32.const 2
+    global.set 2
+    local.get 0
+    global.set 3
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_stop_rewind  (type 0)
+    i32.const 0
+    global.set 2
+    global.get 3
+    i32.load
+    global.get 3
+    i32.load offset=4
+    i32.gt_u
+    if
+      unreachable
+    end
+  )
+  (func $asyncify_get_state  (type 2) (result i32)
+    global.get 2
+  )
+  (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs))
+  (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+  (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+  (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+  (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+  (export "asyncify_get_state" (func $asyncify_get_state))
+)
diff --git a/crates/wit-component/tests/components/link-asyncify/lib-foo.wit b/crates/wit-component/tests/components/link-asyncify/lib-foo.wit
new file mode 100644
index 00000000..3171fc99
--- /dev/null
+++ b/crates/wit-component/tests/components/link-asyncify/lib-foo.wit
@@ -0,0 +1,3 @@
+package test:test;
+
+world lib-foo { }