-
Notifications
You must be signed in to change notification settings - Fork 695
Improve code loading and modules section #71
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
Conversation
([directly or indirectly](AstSemantics.md#calls)). | ||
* A builtin function (implemented by the host environment) is provided to load modules at runtime. | ||
* Functions are again imported by name/signature with error on mismatch. | ||
* These imported functions can only be called [indirectly](AstSemantics.md#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.
Is this saying that builtin functions are only called via function pointers?
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.
No, the "builtin" referenced above is the builtin you call to load a module (i.e., eval
). It is the functions imported from the dynamically-loaded module that are only callable via func-ptr.
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.
Got it, thanks.
Probably just my own miscomprehension, but it could be avoided by changing "A builtin function (implemented by the host environment) is provided to load modules at runtime" to e.g. "Modules can be loaded at runtime by a provided builtin function (implemented by the host environment)."
(The current text of "A builtin function is" appeared to mean "Let's talk about builtin functions: A builtin function is something that is ...".)
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.
Ah, I think I see why I misunderstood things that way: the previous bullet at that level is "A module is loaded from a sequence of bytes", which is indeed elaborating about modules. So I thought that "A builtin function is" was doing the same.
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.
Good suggestion, will do
I'd really like a description of how this is different from dynamic linking, and what dynamic linking will do that module load can't. The way I think about it is that module load is ~ forking a new process, and communicating with it through indirect calls ( |
Not quite: ES6 module imports allow synchronous calls between modules, no |
* An load-time error is raised if the signature of an import does not match the signature of the export. | ||
* These imported functions can be called like other internal functions | ||
([directly or indirectly](AstSemantics.md#calls)). | ||
* Modules can be loaded at runtime by a provided builtin function (implemented by the host environment). |
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.
"can be loaded at runtime" - by whom? Is this talking about the host environment (JS on the web) loading modules, or modules loading other modules?
@lukewagner "ES6 modules, except the code isn't JS" doesn't really help me understand what we would do for a non-browser wasm shell. Multiple heaps in the same process? We can implement sandboxing, or rely on implementation-defined behavior and let cross-heap accesses work. I'd really like this section to be understandable without knowing about ES6 modules. |
83e4e05
to
03ed409
Compare
Ok, updated to clarify @kripken's and @jfbastien's questions. @jfbastien: outside a Web context, yes, v.1 imports imply separate heaps. This makes sense in the general module system of the browser/node.js (heap is an internal detail of a module, along with JS vs. WebAssembly), but I expect that a minimal shell wouldn't have any use for imports until we added dynamic linking support. |
* These imported functions can be called like other internal functions | ||
([directly or indirectly](AstSemantics.md#calls)). | ||
* Modules can be loaded at runtime by WebAssembly code calling a | ||
builtin function (implemented by the host environment). |
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 am puzzled by this. What is the use case for wasm loading another module dynamically, as opposed to statically? And, since this is a v1 feature, how would we polyfill it?
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.
The impl scheme I was imagining was that, if this feature was used, all indirect calls would be translated into:
(ptr > K ? ffi(ptr, args) : tbl[ptr&M](args))
that is, to dynamically create a function pointer, you'd use an out-of-range high value that would then call out of asm.js into an FFI where you could lookup the right callee in a table.
You're right that there's not a strong use case and initially removed this. What caused me to add it back was I couldn't think of a good alternative for how else one would implement the dynamic feature-test-based switch described at the end of this section which would allow a dev to run the same .wasm on a browser and non-browser. But explaining this now, it does seem like it's better just to say "wait for true dynamic linking, use #ifdef for now and ship separate builds". I'll remove.
Anyhow, answering you now I'm thinking that for v.1, #ifdef would be good enough
03ed409
to
1b268fd
Compare
(Updated) |
1b268fd
to
19a1006
Compare
|
||
* A module can declare a subset of its functions to be exports. | ||
* A module can declare that it imports functions (with given names and signatures) from other modules | ||
(with given URLs) and these imports will be recursively loaded when the module is loaded. |
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.
Not an objection, but I'm curious: what is the use case for modules loading other modules (by URL) in v.1? This is a significant boost over what asm.js modules can do, I'm wondering what use you had in mind for this capability.
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.
The "by URL" part just falls out from how modules are defined: you import dependent modules by URL, not by passing them in by value. Other than the URL bit, this is just the asm.js stdlib/ffi feature.
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 see. And is the URL bit because of consistency with ES6 modules? Or something else? I feel like I still don't understand the new element here.
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.
Yeah, in ES6, you write import foo from "bar"
(http://jsmodules.io/). The module loader pipeline has a lot of control for how "bar"
gets interpreted, but by default it just does a normal relative fetch. Also, modules are, by default, loaded once and shared (for a given canonicalized URL), so if you want to "prepare" module A before it is imported by module B, you can import A directly, mess with it, and then load module B (which will then import the same instance of A).
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.
Got it, thanks.
I don't agree that we should go forward with module loading in V1 as I understand it right now. It sounds like in-process processes, but not quite. The differences seem to come from how things would work in-browser and how ES6 modules work, which are details I strongly think we want to avoid when implementing wasm. It doesn't sound like module loading would work at all in a non-browser context, which IMO makes this a no-go. Another metric I'd go by is: once we have dynamic linking will module loading be redundant? It sounds like many heaps in the same process (and sync IPC) are the main advantage? @lukewagner am I misunderstanding what you intend with this section? At the minimum my disagreement means it needs clarification! I'd like @titzer / @davidsehr / @dannoc to chime in on this. |
Ah, I think the source of disagreement here is that I put too much in the general bullet points that instead belongs under the "in a Web environment" (which could be generalized to include node.js to say "in an ES6 module environment"). So at a basic level, WebAssembly modules need to be able to call "out", and "module importing" provides a single unified way to do that: you name what module you want to import, what functions of that module you want to call and with what signatures. Past that, though, what happens when you call an imported function should be a black box, from a spec POV. Maybe it's running JS, maybe shell, maybe C++, maybe other WebAssembly. What is on the other side of that import, what may be imported, how names or mapped to what gets imported -- none of this is part of the spec. Thus, the spec wouldn't mandate WebAssembly modules importing other WebAssembly modules -- that would just be something that falls out of the ES6 module loader design -- and, e.g., a minimal WebAssembly embedding could choose to only support importing from a fixed set of builtin modules. Does that sound reasonable @jfbastien? |
Ah I see what you mean, @lukewagner. That makes more sense to me: this is only discussing how Agreed that makes sense, as otherwise a wasm module would be able to do anything. It does still overlap with dynamic linking, but only in a fairly limited sense. Any interactions with the embedder would originally be ad-hoc through this approach, and once we have dynamic linking we can export regular functionality through a proper dynamic library. Do I have this right? I think there's some rephrasing needed! |
@jfbastien Yes, exactly right, and thanks for helping to push towards clarity. Will rephrase. |
19a1006
to
4f5aad4
Compare
Basically rewrote it to better capture this discussion. I also moved it to the top of V1.md b/c it logically precedes the following sections which go into more details about what module is. |
* A minimal shell environment might define `main` to be the only | ||
meaningful export. | ||
* We may want to define an `init` method in the spec that is always called | ||
after loading a module and before any other exports are called. |
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 like PNaCl's approach of only defining _start
as an export: it's up to the developer's code (usually provided by the toolchain) to go through the .init_array
(which PNaCl merges with the C++ global static ctors). The approach is very extensible-web-y in that it's the minimum feature set and lets developers to everything.
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.
Are you specifically talking about initialization or saying that there only be a single export. If the former, I'm missing how that's different than what the bullet says (s/init/_start/). If the latter, then we hurt the use case where a wasm module is written to be used as an ES6 module from JS.
4f5aad4
to
0986668
Compare
Doh, forgot to merge this. |
Improve code loading and modules section
This change seeks to clarify the wording in the 'code loading and imports' section. Remember, this is for plain v.1 imports (which are ES6-symmetric) not v>1 dynamic loading, which is still under discussion.