-
Notifications
You must be signed in to change notification settings - Fork 695
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
Adding Response based versions of validate, compile, and instantiate. #991
Conversation
Can we make these separate functions instead of overloading further? Moreover, I think we need to separate Web-specific extensions from the "general" JS API spec. |
Agree on the overloads. Should we separate into separate file (or clean up Web.md) or just a section in this one? |
Names sound okay to me. Even though less convenient, I'd have a slight preference for a separate file, since technically, it is a separate spec (a supplement) -- I'm thinking of it in analogy to the DOM supplementing the ES lib on the web. |
JS.md
Outdated
|
||
``` | ||
Boolean validate(BufferSource bytes) | ||
|
||
Boolean validate(Response source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, since this is a synchronous method (it returns a Boolean), how can it take a Response? Should it instead create a Promise? In that case, I think it should not be an overload, but a separate function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validate
is only useful for feature testing, so I'm not sure it's worth adding a whole Response overload and Promise type for this. I don't think anyone should be calling validate on a whole codebase. Furthermore, validation, unlike compilation, is crazy fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, hadn't thought of it until making the PR, will remove.
Agree with @rossberg-chromium that these things are specific to a Web embedding and should probably move to their own .md file. |
Separate functions and files sound fine. Not to bikeshed, though, but "download" sounds a bit off. What about the more literal names |
Actually, could these go in Web.md? |
ef02af5
to
1816863
Compare
Moved and renamed. |
Web.md
Outdated
@@ -22,6 +22,82 @@ and retrieve compiled modules from offline storage, instantiate compiled modules | |||
with JavaScript imports, call the exported functions of instantiated modules, | |||
alias the exported memory of instantiated modules, etc. | |||
|
|||
In addition to the core JavaScript API, the Web embedding includes additional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have an "additional" "addition" in this sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, fixed.
Web.md
Outdated
|
||
Promise<{module:WebAssembly.Module, instance:WebAssembly.Instance}> | ||
instantiateResponse(Promise<Response> source [, importObject]) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does one use instantiateResponse
with an importObject
that contains a WebAssembly.Memory
from another instantiateResponse
? i.e. create multiple instances which share a memory, using the response APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd need to Promise-chain them; but I think this is already the case with instantiate
. To avoid the sequential dependency, one would want to instead create the memory/table up front (via their JS constructor) and pass them simultaneously into N instantiateResponse
calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd like to see what the code looks like, and have folks such as @domenic and @slightlyoff chime in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would think you'd either in this case want the memory to be external, or if you wanted it to originate from another module, you'd have to first instantiate that module (i.e. like a libc).
That does have the unfortunate property of serializing things.
The current instantiate method has this same issue, though or am I missing something?
I suppose we could also accept multiple modules bound to one internal memory, but at that point I wonder if we wouldn't be better off sorting out ES6 modules (as they should have a similar behavior).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sequential:
instantiateResponse(fetch(urlA)).then(resultsA =>
instantiateResponse(fetch(urlB), resultsA.instance.exports).then(resultsB =>
...
)
);
Parallel:
var imports = { libc: {
mem: new WebAssembly.Memory(...),
tbl: new WebAssembly.Table(...)
} };
Promise.all([
instantiateResponse(fetch(urlA), imports),
instantiateResponse(fetch(urlB), imports)
]).then(([resultsA, resultsB]) =>
...
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that only works if you don't need to import exports between A and B.
In that case you can build a web of promises that serialize things as little as possible, but you wouldn't be able to get maximum concurrency on the compiles.
If there's dependency chains like that, then you can do better if you use the two-step compile instantiate (as you can get all the compiles going in parallel, download already are).
That's no worse that with the non-response versions of these APIs.
We could try to fix that also, but as I said before its probably better to do that with ES6 modules.
If we did it for the one-step instantiate methods, it would probably end up requiring a declarative way to describe all the hookups (which seems to re-do a lot of the ideas from the ES6 modules space).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
If we did it for the one-step instantiate methods, it would probably end up requiring a declarative way to describe all the hookups (which seems to re-do a lot of the ideas from the ES6 modules space).
IIUC, there is a JS function to dynamically load an ES6 module proposed now (I forget the name). With wasm modules integrated with ES6 modules, that would basically give us this one-step capability.
Web.md
Outdated
|
||
The `Response` or `Promise<Response>` is used as the source of the | ||
bytes to compile. | ||
WebAssembly `source` data is expected to have a mime type of `application/wasm`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/mime/MIME/
here and elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
application/wasm
sounds reasonable. In the current <script type="module">
pipeline, we have a restriction that the MIME type of the response should be one of JS MIME type. This is great since we can choose the different way to produce different modules (JS v.s. WASM modules) based on the MIME type of the given Response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
And good point.
I think this comment contains an important design point:
I'm worried that the proposed API only allows us to use Do you think that's an important usecase, and that this API should get it right from the beginning? |
Web.md
Outdated
``` | ||
Promise<WebAssembly.Module> compileResponse(Response source) | ||
|
||
Promise<WebAssembly.Module> compileResponse(Promise<Response> source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How this works on the web is that the first step of the algorithm wraps whatever it gets in a promise, then unwraps the promise before continuing. I'm not sure how that translates to the notation you're using here (TypeScript??) but it should be noted in the algorithm. In Web IDL it's just Promise<Response> source
as the argument, and then any incoming argument gets converted by the Web IDL bindings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds better. Changed.
Web.md
Outdated
Other mime types | ||
[reject](http://tc39.github.io/ecma262/#sec-rejectpromise) the Promise with a | ||
`WebAssembly.CompileError`. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should mention that the returned promise is rejected with a TypeError if the incoming value, after unwrapping, is not a Response
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
PTAL |
Web.md
Outdated
`WebAssembly.CompileError`. | ||
|
||
The `Promise<Response>` is used as the source of the bytes to compile. | ||
WebAssembly `source` data is expected to have a MIME type of `application/wasm`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather see this as application/webassembly for symmetry with JavaScript, which is application/javascript, not application/js.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record JS is all over the place: https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, but never abbreviated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion here except to note that many other mimetypes abbreviate: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
@flagxor Thank you for exploring the space.
Great to see some effort to address this space of the design. It is not clear (to me anyway) that this meets the needs of resource planning and caching. I understand that the web browser could cache the source data, but it is not clear if that caching will work well if there is a streaming translation stage? The compiler might cache all the input stream, and use that as a key, but that seems a huge burden. What would you think about having a build file, or Makefile of sorts, and that being the input:
The makefile could be text or json, whatever suits people, and would describe the steps required to get the app running, and in a way that allows the web browser to schedule the fetches and to schedule linear memory allocation and to cache parts of the compilation work. Perhaps it could even launch multiple threads all using the same linear memory, and thus be able to optimize for that if that was a significant matter for the web browser. This could also include provision for a custom translation stage. I believe that your proposal could be used to implement parts of such a build process, but that a web browser could do much better with the description than your proposal could ever do. Also, should the build file prove to be a dead end then it might be a small matter to create a polyfill for it. Perhaps part of the problem with Responses is that they have no version or provenance. Can the compiler even trust that the stream is from a URL with a name and version without any modification? For caching to be effective the web browser needs keys, and if it can use named sources with versions and data flow based on these as keys then it is a lot better off than having to use the data at each stage as a key. |
Continuing on this comment, IIUC:
Is the intent then to have:
? |
While looking at the potential of ES6 modules, I see references to a Loader, and find pages such as the following https://whatwg.github.io/loader/#request-translate and https://gist.github.com/dherman/7568080 which includes discussion of setting up a pipeline and includes a source-to-source translation stage. This all sounds much closer to what might be needed. I also note that the ES6 Modules feature static definitions, but they appear to be just top level forms in a JS file, and ES6 Modules alone do not appear to have what we need, rather we already have imports and exports, and simply adding a I also see a lot of uses of Promises, but Promises do not appear to be static descriptions (is that right?), so they do not seem to be the appropriate way to express the loader. Perhaps we need a static description of the loader and need to extend the ideas to meet the specific needs of wasm, so to be able to define a binary-to-wasm translation stage (rather than source-to-source), and to work with the constraints imposed by the linear memory. Edit: and it needs to work well with caching so to support useful cache keys. Would @dherman be willing and able to help on the state of the Loader? |
I realize this is not the formal language, but I wanted to point out some things with respect to MIME types that would be good to have clarified for implementations and tests:
It would also be good to point out an opaque filtered response leads to failure as you don't explicitly define yet how you extract the data out of a response. (I.e., cross-origin without CORS won't work.) |
@jfbastien That matches my understanding. I found the proposal, currently Stage 3, for the dynamic import I mentioned above. So if we made |
OK, so the ES6 module loader API will Just Work for WebAssembly, and be strictly more powerful than this proposal? What would its timing look like, compared to this proposal? @domenic maybe? |
I'm not sure what I'm supposed to be commenting on, exactly? It seems definitely false that a Response based version is less powerful than an import()-based one; for example, you can create synthetic streaming Responses easily, whereas without a service worker there is no such ability for import(). import() also has no ability to pass in a custom WebAssembly.Memory, or separate out instantiation and evaluation. |
Basically: I want to understand the four JavaScript APIs we're proposing for WebAssembly, and listed here. I want a list of features each offers. From my summary it sounds like Response is missing features compared to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bear in mind I don't know much about the Fetch API and Responses. But if I understand correctly, this adds new APIs with the ability to do a wasm compile/instantiate from a URL or a stream, etc.?
If so that sounds fine for Emscripten, we don't have any significant limitations on how the wasm is created (now that we support async compilation). We could pass the URL of the wasm to the proper API, etc., very easily.
@kripken specifically: I'd like to understand whether Emscripten can use this Response API for dynamic linking of wasm modules, where one module imports another's exports. |
@jfbastien I don't see anything specific to Emscripten or these Response versions here, so probably I'm missing something. But the constraint has to be that if module A imports module B's exports, then B must be fully loaded&compiled before A does so. Given these APIs, it seems we could either
Are you asking if 1 and/or 2 are possible with Emscripten? Both should be. |
@kripken yup that sounds right. I'd rather check because I was surprised last time with the promise API being more complex to use for Emscripten than I'd envisioned. I'm not sure an importObect promise would make sense to break this sequencing (or importObject containing some promises which are checked after compilation). We can always add that as an overload later if it makes sense. |
Final ping for #991 (comment). |
As @kripken said, I think we could do dynamic linking with async using the same mechanisms we have now. Currently Emscripten also supports emulation of |
I agree it can be done. I'm asking if this is a good API for it, if Emscripten would use it, and if the API could be better suited to that purpose. It sounds like a resounding "meh" so far. Given that the Response API is probably useful for non-Emscripten uses as well, I expect that Emscripten just isn't a good client. Maybe it would be one if the |
@jfbastien |
@annevk I think he's referring to a the |
@annevk Sorry missed your comment earlier. I've just added verbiage I believe addresses those points, please confirm. I figure strict seems better in this context, as there's a single format. |
@jfbastien If I grok your concern correctly, you're pointing out that properly compiling and instantiating a collection of modules with complex dependencies while ensuring minimum latency and full asynchrony will require a very verbose pile of promise laden JS? I think this is true. But such collections of modules will at this point be the product of something like Emscripten, which will no doubt have the plumbing to do it well. Such modules are also likely to have a complex internal contract between them anyhow. We might be able to make the set up less verbose by accepting promise in a few more places as Derek suggests, but I suspect it will only help some (as those promises won't have simple sources). I think ultimately an asynchronous dynamic loader is a complex beast and making the API more ergonomic for this won't help as much as ensuring it has all the raw low-level rope. I suppose it's a little sad that the recipe for C++ style dynamic loading won't be small, but then again even nacl's "simple" nmf manifests for dynamic loading required a bunch of tooling in practice to get right. I'm hopeful that if we make wise choices for ES6 modules we'll get something super-pithy there, which seems like what you'd want if you're mixing in tiny wasm modules with JS code. |
@flagxor thanks, that seems good enough for now. |
@annevk as @dschuff said, I'm talking about APIs such as these:
The optional
This serializes the response handling. Compilation of It seems weird to me to add a Response API, yet not handle this case up front, because the entire point is to handle streams and my example shows how it doesn't for what I think is a common usecase. We can add an overload later, it's not an MVP API so the overload may ship at the same time as this Response API.
No. My point isn't the amount of code. In my example above, there's a serialization which the API makes necessary but which doesn't need to exist for user code to work correctly. My concern is that we've designed a stream API which blocks for a (likely) common usecase, and it doesn't need to be that way.
Putting our hopes on ES6 modules seems fraught with peril given the comments in this thread. I'd love to, I've already implemented it that way in JSC, but I'm much less confident that's a foregone conclusion given this discussion. |
@jfbastien
I suppose if you used the MIME type to aggressively hint the need to compile, you'd do nearly as well. I don't think any simple change to importObjects will let you just use plain instantiate for complex dynamic linking cases, since you have to express all the ways you might extract an export from an instance. I suppose this is somewhat at odds with instantiate(fetch(url)) being the "recommended" pattern. Maybe I'm still not groking your core concern? |
@flagxor that's correct. Because of this, I'm worried that we're back to advocating for the API that we had prior to #838. We'd really like If developers use something else than This API is a much more minor case of #838. I think Emscripten could be careful at emitting code with this API, and then we'd be OK w.r.t. double-compilation. It's then only a question of tons of JS as you stated above. It seems silly to design this API with minimizing the risk of #838, and minimizing the amount of code Emscripten needs to emit (and what hand-coders need to write). What I'd like is for folks familiar with Response / streams to help design a solution which addresses this as well (basically, an API which makes stream combination possible for WebAsembly). If they think this can be done on top of the currently proposed Response API then I'm good with it. I like the high-level bits of the Response API. |
This is already a limitation of the existing
With this, the module at (I actually had a TODO in JS.md (that I later removed) to add an API just like this to allow instantiating whole cyclic batches of modules at the same time.) But, as I said, this is a more complicated API and thus it makes sense to have the simple version here for the simple case. |
What @lukewagner says sounds good to me. As long as we go back to a multi-stream API for Maybe we should start updating our github milestones, and find another marker than 🦄! |
Agreed, API Luke suggests sounds close. |
This allows us to support streaming compilation and origin bound sources for WebAssembly modules. Proposes a mime type of application/wasm for WebAssembly modules.
Added, PTAL |
Update LGTM, let's merge this and continue in #997. |
This allows us to support streaming compilation and origin bound
sources for WebAssembly modules.
Proposes a mime type of application/wasm for WebAssembly modules.