How to correctly import a WebAssembly.Global from JS in Zig?

Hello,

I am trying to import a WebAssembly.Global from JavaScript into my Zig-compiled Wasm module.

Based on the MDN documentation, the host (JavaScript) side is straightforward:

const startOffsetGlobal = new WebAssembly.Global({ value: 'i32', mutable: false }, 0);
const endOffsetGlobal = new WebAssembly.Global({ value: 'i32', mutable: false }, 10000);

const importObject = {
    env: {
        memory: wasm_memory,
        array_scope_offset_start: startOffsetGlobal,
        array_scope_offset_end: endOffsetGlobal,
    },
};

const { instance } = await WebAssembly.instantiate(module, importObject);

The goal is to write Zig code that generates the corresponding Wasm import section:

(module
  (import "env" "array_scope_offset_start" (global i32))
  (import "env" "array_scope_offset_end" (global i32))
  ...
)

And then uses the global.get instruction to access these values.

However, I have been unable to find the correct Zig syntax to achieve this.

I have tried this:

// main.zig
extern const array_scope_offset_start: usize;
extern const array_scope_offset_end: usize;
// build.zig
      
// ...
exe.import_symbols = true;
// ...

This did not work. The compilation succeeded, but it produced a faulty Wasm module. Instead of creating global imports, the compiler treated the symbols as being located at memory address 0. The generated WAT for using the global was i32.const 0, i32.load, which is incorrect.

Given the above, what is the correct, idiomatic Zig syntax to declare an external global variable that will be imported from a WebAssembly host module (e.g., ā€œenvā€)?

Thank you for your help.

Afaik, Zig doesn’t give you access to this functionality. Maybe there’s a way but I don’t know it.

If you don’t need this exact API, but you just need global like behavior, you can check out my project zig-javascript-bridge: GitHub - scottredig/zig-javascript-bridge: Easily call Javascript from Zig wasm

The library has an export function, and the tooling with generate the javascript to access it. There’s usage in the example: zig-javascript-bridge/example/src/main.zig at 9dcf78afc413f1b37f3a82e68bdebdc0aa6dc6b3 Ā· scottredig/zig-javascript-bridge Ā· GitHub
and the Javascript side:
zig-javascript-bridge/example/static/script.js at 9dcf78afc413f1b37f3a82e68bdebdc0aa6dc6b3 Ā· scottredig/zig-javascript-bridge Ā· GitHub

In case you don’t want to use my library, here’s the issue discussing how it was achieved: Add support for global variables Ā· Issue #8 Ā· scottredig/zig-javascript-bridge Ā· GitHub

2 Likes

I think you need to use the other form of importing external symbols:

const array_scope_offset_start = @extern(*i32, .{
    .name = "array_scope_offset_start",
    .library_name = "env",
});

This should allow you to use other module names other than ā€˜env’ if desired.

Thanks for the responses.

I tested this form of importing external symbol (@extern), it resulted in same compiled WASM as when I used the keyword extern. So I guess both are treated the same by the compiler.

I came across this Target Tier System (https://ziglang.org/download/0.14.0/release-notes.html#Tier-System) and the target wasm(32,64)-freestanding is in additional platforms, so the possibility of importing Globals might not be implemented yet. Or this specific case requires some exact build config that I am unaware of. Either way, for my specific code, it is possible to manually rewrite the few specific instructions in WAT, then compile to WASM, and it works flawlessly.

1 Like

you could also just write assembly block in Zig instead of WAT/WASM externally

comptime {
    asm (
        \\.globl get_array_scope_offset_start
        \\.globaltype array_scope_offset_start, i32
        \\
        \\get_array_scope_offset_start:
        \\.functype get_array_scope_offset_start() -> (i32)
        \\ global.get array_scope_offset_start
        \\ end_function
    );
}

extern fn get_array_scope_offset_start() i32;

export fn foo() i32 {
    return get_array_scope_offset_start();
}
(module
  (type (;0;) (func (result i32)))
  (import "env" "array_scope_offset_start" (global (;0;) (mut i32)))
  (memory (;0;) 256)
  (global (;1;) (mut i32) i32.const 1677216)
  (export "memory" (memory 0))
  (export "get_array_scope_offset_start" (func 0))
  (export "foo" (func 1))
  (func (;0;) (type 0) (result i32)
    global.get 0)
  (func (;1;) (type 0) (result i32)
    call 0)
)

(wasm assembly syntax is left as an exercise for readers (i cannot find a reference for that lol))

1 Like

I think its just the webassembly text format? Didn’t find anything to verify that WAT is what LLVM is using in a quick search.

Of course because they are not the same thing :smiley: How can you even mix up one that is asm-like (look at them labels, directives in my code block) and something lisp or s-expr like? (look at them parens in the post you linked)

(Also, just a suggestion, but ya know, you can fact check by just creating an example Zig program and test your hypothesis, before posting anything :smiley: Why think and imagine, when in these cases, the compiler is the truth)

(wink wink also to the @extern suggester above)

Sorry, I wasn’t paying enough attention before I posted.