Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple imports of the same wasm file: same instance or different instances? #60

Open
carlopi opened this issue Feb 16, 2022 · 7 comments

Comments

@carlopi
Copy link

carlopi commented Feb 16, 2022

As per the title, the basic case is like:

//a.js
import {someFunc} from someModule.wasm
someFunc();
//b.js
import {someFunc} from someModule.wasm
someFunc();
//c.js
import a.js
import b.js

While instantiating c.js, there will be a single wasm module whose export 'someFunc' has been called twice OR two different instance of the same module, each called once?


Then depending on the answer, there might be possible problems (or at least some need of disambiguation / explanation) how to avoid some common problems.
With separate instances, the fact that memory might become an issue, with same instance the fact that different components might (without knowledge) share the same internal state.

I implemented ES6 module's support in Cheerp (C++ to JS/Wasm compiler), and we ended up with modules exporting a default initialization function that when called returns (asynchronously) a new instance of the module.

Here the explanation of the approach: https://docs.leaningtech.com/cheerp/ES6-Modules

@devsnek
Copy link
Member

devsnek commented Feb 16, 2022

You might be interested in #44

@takikawa
Copy link
Collaborator

takikawa commented Feb 17, 2022

While instantiating c.js, there will be a single wasm module whose export 'someFunc' has been called twice OR two different instance of the same module, each called once?

I believe this will actually depend on the host environment, due to how the ES spec specifies how modules are resolved. In particular, see HostResolveImportedModule. But for running on the web, the HTML spec should give the same module record.

Here's an excerpt from HostResolveImportedModule's spec text:

  • Each time this operation is called with a specific referencingScriptOrModule, specifier pair as arguments it must return the same Module Record instance if it completes normally.

Multiple different referencingScriptOrModule, specifier pairs may map to the same Module Record instance. The actual mapping semantic is host-defined but typically a normalization process is applied to specifier as part of the mapping process. A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers.

The last paragraph says the mapping is host-defined. In the case of loading in HTML and the Web, it's defined here in the HTML spec. As far as I know, this algorithm and HTML's module map will ensure that for modules with the same base URL and specifier (should be the case for someModule.wasm I think) you'll get the same module record.

(Module records should also only get evaluated once, and module record evaluation is when the Wasm module gets instantiated)

@carlopi
Copy link
Author

carlopi commented Feb 17, 2022

Ok, maybe this could be added somewhere to the description / examples in some form, since it's a quirk that may be non-obvious.

Then I have a concern that basically this gives observability / non-determinism if the graph imports is something different than a tree. Think of a utility wasm file, that is included twice by two different components, they will then share the state.

Think of some interface like "setMyData" "getMyData", it will not be OK in general to share the same instance.

This is similar to the JavaScript case, but there at least there are ways to write files that are impossible to misuse (eg. exporting a factory to an actual instance) while Wasm modules have no clear way of add the constrain: this should not be a shared instance.

@takikawa
Copy link
Collaborator

takikawa commented Feb 25, 2022

Yeah, I see what you're saying in terms of sharing state, but that's also generally how JS modules behave in a JS module graph as far as I know. I think there is no obvious way to change this behavior while having Wasm modules be the usual module records in the ECMA spec. I think it's a good idea to note that it works similarly to JS somewhere though.

Other people have been asking for a way to have ESM integration provide a Wasm module instead of an instance (see #14). In that situation, you could do import someModule from "someModule.wasm" as "wasm-module" and then instantiate it separately twice or however many times you want.

I don't think that will be included in this initial proposal though, as it depends on the import reflection proposal.

@carlopi
Copy link
Author

carlopi commented Feb 27, 2022

Yeah, I see what you're saying in terms of sharing state, but that's also generally how JS modules behave in a JS module graph as far as I know. I think there is no obvious way to change this behavior while having Wasm modules be the usual module records in the ECMA spec. I think it's a good idea to note that it works similarly to JS somewhere though.

Yes and no, as in this is similar to JS, but JS have clear way to expose interface where state in non-global, while in WebAssembly it's far-from-obvious.

Even the example counter.wasm expose functionality to get or increment the counter, and I find it puzzling that adding unrelated code (that somehow also uses counter.wasm) lead to 'breaking' existing code assumptions.

My concern is that ESM integration would mostly work out of the box, only for that do be broken in surprising ways as more code gets added to a given project.
And a secondary concern is that there is no proper way to opt-out, eg, exposing a wasm file to the module system while enforcing that no-one might get access to or invalidate the state.

@devsnek
Copy link
Member

devsnek commented Feb 27, 2022

fwiw I don't think that the esm proposal has to solve every use case wrt loading wasm modules. If you have specialized requirements, you can always manually use WebAssembly APIs with raw bytes like you would do today.

@carlopi
Copy link
Author

carlopi commented Jul 20, 2022

While thinking about #63, I sort of came to better understand the problem I was seeing here.

Another way of looking at this is that a module/instance interface is not entirely "public" as in safe to use for anyone on the outside, since there are a few exports that are meant to be tied to a specific JS behavior, and is not safe (neither from the user, nor from anyone who was relying on the instance). Think to an export like __terminate(), it would not be OK for __terminate, on a currently live Instance, to be reachable via ESM integration by a third party.

This is also somehow tied implicitly share state-full instances, basically a way out would be having a new module instantiated every time (since then you can't do harm if you misuse the implied usage), but this somehow shifts the purpose of the proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants