WebAssembly / binaryen

Optimizer and compiler/toolchain library for WebAssembly

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[feature requeset] wasm-merge outputs js glue code.

doehyunbaek opened this issue · comments

In the README, there is a code snippet which glues together two wasm modules with glue code js,

wasm-merge is kind of like a bundler for wasm files, in the sense of a "JS bundler" but for wasm. That is, with the wasm files above, imagine that we had this JS code to instantiate and connect them at runtime

To our usecase, wasm-merge outputting compile-time linked wasm is immensely useful, but I think having the runtime linked wasm files with js glue code would be also very useful.

Do you mean to add an option to emit the JS code that does runtime linking? That code is usually pretty simple and it depends on how you load the wasm files, so I'm not sure how wasm-merge could help here, yet. But if it can that could be a good idea.

I thought some static analysis done for wasm-merge can be reused for outputting runtime linking js code, for example, in the README.md, it says

all while fixing up name conflicts and connecting corresponding imports

Although I haven't read the implementation in detail, the data structures needed to do the job might be reused. Or maybe the code itself is so simple that it's just better making some independent implementation.

So if you think this would be a good idea, would this feature be on some future roadmap or accepted-if-pr-opened basis?

My guess is that the code is pretty simple in general, stuff like this (similar to the example in the README):

new WebAssembly.Instance(module_A, {
  module_B: module_B.exports,
  module_C: module_C.exports,
});

That is, you just instantiate each module with all the exports from the other as its imports. But maybe I am forgetting some complex part of this code?

I have thought that logic for runtime linking and static linking would be of similar difficulty, but after reading your example and thinking it through, have reached a same conclusion that the js code for runtime linking should be way simpler. Thanks for your explanation.

One problem I encountered is when two wasm modules have a circular dependency on each other, for example,

index.wat:

(module
  (import "env" "changeMemory" (func))
  (memory (export "memory") 1)
)

env.wat:

(module
  (import "index" "memory" (memory 1))
  (func (export "changeMemory")
    (i32.store (i32.const 0) (i32.const 0))
  )
)

I am able to merge them with wasm-merge with this command: wasm-merge index.wasm index env.wasm env --enable-multimemory -o merged.wasm,

which outputs this wasm file:

(module
  (type (;0;) (func))
  (func (;0;) (type 0)
    i32.const 0
    i32.const 0
    i32.store
  )
  (memory (;0;) 1)
  (export "memory" (memory 0))
  (export "changeMemory" (func 0))

But for the case of runtime linking, although I tried with hand, I was not able to express this linkage in JS. At least for functions, I could make do with some monkey patching in the style of below code:

let index
let indexModule = new WebAssembly.Module(await fetch("index.wasm"))
let indexImport = {
    "env": {
        // I think this is a correct workaround.
        "changeMemory": () => {
            env.exports.changeMem();
        },
    }
}
let env
let envModule = new WebAssembly.Module(await fetch("env.wasm"))
let envImport = {
    "index": {
        // FIXME: this is wrong.
        "memory": new WebAssembly.Memory({ initial: 1 }),
    }
}

env = new WebAssembly.Instance(envModule, envImport)
index = new WebAssembly.Instance(indexModule, indexImport)

But for memory, as mentioned in the FIXME, and table and global, I don't think there is a workaround.

Is there some way out that?

Circular dependencies are a problem, correct. The usual way that is fixed is to add indirection, as in your example: rather than connect exports directly, add a JS export in the middle that does a dynamic lookup each time.

For the memory and table there is really no workaround, correct. The simplest thing is to define them in the first module that is instantiated, and import them from later ones.

You can see both of those techniques in use in Emscripten's dynamic linking code, btw (it's not a small amount of code to read, so I wouldn't necessarily recommend it, but those are fully working code samples at least. All tests with names like test_dylink_* are of dynamic linking).

Thanks! Learned a lot. Will take a look and think through.

Hi. @kripken. I got to make a very specific solution for my use case, but I think I will not work on some general-purpose solution. Would it be best to close this issue?

Sounds good.