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

Support multi-module 1-step Instantiate with dependencies #997

Open
flagxor opened this issue Feb 24, 2017 · 20 comments
Open

Support multi-module 1-step Instantiate with dependencies #997

flagxor opened this issue Feb 24, 2017 · 20 comments

Comments

@flagxor
Copy link
Member

flagxor commented Feb 24, 2017

We should support a version of instantiate that will continue to allow us to recommend it as the preferred instantiation method one dynamic linking / multiple modules become more popular.

WebAssembly.instantiateGroup({a:Promise<Response>, b:Promise<Response>, otherImports:importObject})
With this, the module at urlA could import the foo export of the module at urlB by importing from "b" "foo".

Questions:

  • Do we want to support cycles or just DAGs?
  • Do we want to support the BufferSource overload?
  • How does this interact with ES6 modules? (or does it?)
@rossberg
Copy link
Member

Just repeating my usual observation that allowing cycles is much more than a mere API addition. It would introduce full-fletched recursive modules into Wasm, which is a tricky design space with numerous potential implications on present and future versions of the language. (FWIW, recursive modules for typed languages are still an active and rather involved research topic.) We should be very careful not to do something hasty there.

So for a Milestone 2 feature it would be much safer to separate the problem of bulk instantiation from recursive linking and only allow DAGs.

One other nit: otherImports should not be part of the object but a separate parameter.

@lukewagner
Copy link
Member

lukewagner commented Feb 27, 2017

Agreed on starting with DAGs, just as a practical first step.

On the otherImports bit: good point. I had been thinking that it was simpler to put them all in the same "namespace", but thinking more about this: in the future it is likely we'd want to allow importing Module, ArrayBuffer, etc and then there would be an ambiguity: are we asking to instantiate these or just use them as import values. So yeah, definitely better to keep otherImports a separate parameter.

So the original reason I thought it was simple to keep them in the same namespace: let's say I'm instantiating some module in the DAG of some call instantiateGroup(modules, importObj) and that module imports "a" "b"; where do we look up "a"? We could merge modules and importObj into a single namespace, but then what about duplicates: do we require there are none or do we define precedence (e.g., modules over importObj)? The latter seems fine, actually, and no more limiting than providing a single namespace object.

@mtrofin
Copy link
Contributor

mtrofin commented Feb 28, 2017

@flagxor - this would be a helper API, correct?

I worry we would enter composition engine territory with this API. Questions of policy arise. Besides cycles, as @rossberg-chromium illustrates, there's also: how do you match imports and exports; what if there are 2 imports you want satisfied with the same export.

I would prefer we let third parties come up with such policies, and us just provide the basic building blocks, API-wise.

@jfbastien
Copy link
Member

@mtrofin this is an offshoot of #991. The main goal is to allow multiple ongoing streams to make forward progress, reducing the time to play on a wasm site that uses dynamic linking.

You bring up important design points, but I don't think third parties can do what you propose. The JS API have to do this. Unless I'm wrong :)

So, let's consider what you say, but I think we have to do so in the JS API.

@mtrofin
Copy link
Contributor

mtrofin commented Mar 2, 2017

Let's also take a step back. What's the motivating user story for this API?

The proposal frames this in the context of dynamic linking. The motivations for that feature are, afaik:

  • apps like photoshop that have lots of tools, the user doesn't need them all, so the app brings down needed tool on demand;
  • the shared library case, where a common engine (like a gaming engine) is cached and then multiple unrelated apps instantiate it, instantiate their own module(s), and share same heap
  • the hosted JIT language - the JIT-ed program is a module that's instantiated separately from (but likely sharing heap with) the module that's the language runtime

In all of these cases, bindings happen at different times - first we instantiate one module; later, and, importantly, because of some user event, we want to instantiate another one and relate imports/exports.

The instantiateGroup API doesn't accommodate for that "some user event".

Which places the API square in the field of composition (a'la dependency injection frameworks and composition engines). Why do we want (what motivates) that?

@jfbastien - I am not sure how #991 blocks a third party composition engine from working when using the response-based APIs. Specifically:

var components = [];
components.push(WebAssembly.compile(fetch(url1)));
components.push(WebAssembly.compile(fetch(url2)));

Promise.all(components)
  .then(compiled_modules => {
    // wire them here.
});

@lukewagner
Copy link
Member

@mtrofin You're thinking about runtime dynamic linking. What we're talking about here is load-time dynamic linking. The use cases for that are:

  • common engine, small variation between multiple applications
  • separate frequently-changing parts of app from infrequently changing parts to minimize re-download+recompile

@mtrofin
Copy link
Contributor

mtrofin commented Mar 2, 2017

@lukewagner - I agree, this is about load-time linking. I think I went about my point in too convoluted of a way. Let me try this:

  • I claim developers are not blocked in achieving the load time composition use cases, even when you factor in Adding Response based versions of validate, compile, and instantiate. #991. I offered a snippet for the latter, and I think we know how it'd work with the shipped APIs.

  • because developers aren't blocked, and because instantiateGroup would have to make policy decisions (like how to match imports/exports, deal with duplicates, etc), I propose we not do this API: it will be expensive to spec, implement, and maintain, and it risks not being used. Instead, I propose we let the community bring in whatever load time composition engines they see fit, and should a winning set of policies come out, then reconsider providing this in the platform.

@lukewagner
Copy link
Member

lukewagner commented Mar 2, 2017

@mtrofin The problem with that snippet is it uses compile, not instantiate, and the resolution of #838 was that people should use instantiate by default as it's the most efficient option across engines. Note that even in V8, instantiate isn't cheap, so being able to instantiateGroup({a:fetch(url1), b:fetch(url2)}) is useful for achieving max parallelism.

@lukewagner
Copy link
Member

Also, since we've punted out all the fetching/memoization logic, I don't think remaining logic for matching imports/exports is too complex here.

@mtrofin
Copy link
Contributor

mtrofin commented Mar 2, 2017

How about this: let's first prototype an implementation for instantiateGroup.

@lukewagner
Copy link
Member

Sounds reasonable :)

@jfbastien
Copy link
Member

@mtrofin what @lukewagner said, as well as: you have to write an example with importObject which one instantiated module passes to the next. In that scenario, things are serialized with the current Response API.

@flagxor
Copy link
Member Author

flagxor commented May 11, 2017

An alternative for this might be to extend instantiate + compile to allow Promises throughout the options parameter. This would allow you to state things up front like having the memory, but defer things like having the imports from something not yet compiled.

@lukewagner
Copy link
Member

That's a pretty cool idea. Is there any precedent for module systems doing this in the past that anyone has heard of?

@jfbastien
Copy link
Member

Yeah I think @rossberg-chromium proposed this? I like it 😁

@rossberg
Copy link
Member

@lukewagner, the dynamic module system I developed in my thesis (a long time ago...) defined its semantics for lazy linking in a similar manner, based on proper futures. :)

@jfbastien
Copy link
Member

Who's willing to champion this?

@lukewagner
Copy link
Member

@rossberg-chromium Cool. And so iiuc, this would be able to support DAGs but not (cyclic) graphs, yes?

@mtrofin
Copy link
Contributor

mtrofin commented May 12, 2017

There is a similar precedent in production, too - .NET's MEF allows import satisfaction with Lazy objects. Similar to this proposal here, in that the object providing the service required by the importer is resolved after binding time. A more closer correspondent in JS would be a function returning the import - but probably Promise is more composable and easier to document.

@rossberg
Copy link
Member

@lukewagner, yes, it's just a way to be able to "provide" the dependencies before you "have" them, so that e.g. compilation can proceed and parallelism can be maximised. The actual instantiation step for each module would still have to wait for its import promises, so you cannot construct cycles with this alone.

@mtrofin, actually, lazy objects and futures/promises are very closely related (so close that in the system I mentioned they where one and the same thing). So mapping dot-net's mechanism to promises is most natural.

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

7 participants