-
Notifications
You must be signed in to change notification settings - Fork 58
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
Interaction between Rust wasm/foreign modules and encapsulation #92
Comments
Hey @dflemstr I would say this is the right place since this is where the working group for wasm and Rust coordinate! I would like to here what @alexcrichton thinks on this considering he's done most of the compiler work regarding the wasm32 target. I'm sure many others here have thoughts on the matter as well! |
@dflemstr You're definitely in the right place. Let me give my thoughts on your post:
I'm undecided about this. On the one hand importing the So I guess it depends on whether we want
If we treat
I believe Rust already has a notion like that: https://doc.rust-lang.org/book/first-edition/ffi.html#callbacks-from-c-code-to-rust-functions I imagine it would look like this: extern "wasm" fn foo() -> i32 {
5 + 10
}
I'm not so sure about this one. That sounds like it's trying to apply Rust's module system and scoping rules to JS, but JS has its own module system. As for the rest of your post, I generally agree. I think this is less a matter of "the Rust team decided things should be this way" and more a matter of "we haven't decided how things should be yet, so the current status-quo is a bit wonky". |
FWIW right now wasm and rustc have no knowledge of import paths really, they're sort of just opaque strings. AFAIK it's bundlers that attach meaning to these import paths to allow files to import one another. In that sense I'm not entirely sure what we'd do differently in rustc/wasm-bindgen/etc? Specifically on the topic of FFI and "what symbols are exported" or "what is their ABI", that's quite similar to https://github.com/rust-lang-nursery/rust-wasm/issues/29 I think |
@alexcrichton It might be necessary for rustc to carefully generate the relative imports in such a way that they work correctly after running |
Actually, the situation is much more complicated than I thought. The plan is for But that means if the So we either have to give up on the idea of And then there's issues about name collisions, if multiple Rust crates export the same variable name... |
@Pauan oh but my point is that rustc isn't generating imports at all, authors are writing them down in source code and rustc is moving those strings along to the output file. |
@alexcrichton Yes, but there's all sorts of problems if the imports are just passed through unchanged:
There's a bunch of other nasty stuff that can happen, that's just a small taste. Doing this right will be hard, since authors will want to import file paths relative to the |
Oh sure, but this is also serious hypothetical territory right now. Nothing in the toolchain supports specification of the import module, much less actually resolving it. I think before we debate what should happen we should get something working, see how it works, and go from there. |
Does it makes sense to use a path specific to JS there at all anyway? That means the rust crates would be written with specific JS files in mind, which honestly I don't see working out if we consider normal rust crates like chrono that you have as a dependency. So for something like chrono it makes a lot more sense to just define something like this: #[wasm_import_module = "chrono"]
extern {
fn time() -> f64; // Made up signature
} That way you have a clean namespacing on the rust side that isn't prone to potential conflicts. However I think it definitely does make sense to be able to specify JS dependencies there too, so it probably makes sense to have a custom section in the wasm that specifies how those map to JS. (JSImportMappings ("chrono" "chrono-helper.js")) And on Rust's side it could look like: #[js_import_mapping = "chrono-helper.js"]
#[wasm_import_module = "chrono"]
extern {
fn time() -> f64; // Made up signature
} |
@alexcrichton Absolutely, I'm not trying to stop the prototyping work. But since ES6 modules and npm are stabilized, we can discuss theoretical use-cases, to make sure that we will be able to (eventually) support them. @CryZe That would require that any Rust crate that wants to use JS files would have to publish those JS files as an npm module. That's true even if the Rust crate only wants to include a tiny snippet of JS code. Also how would the naming go? Would the Rust crate be published to npm as And then there's things like wasm-bindgen and stdweb which need to be able to generate JS files. So we will need some way of including JS code in a Rust crate. Maybe it would be inline like the stdweb
How is that better than specifying a relative path to the Here is my proposal: when compiling a crate to As such, the header needs to be able to uniquely identify each crate. I assume When a crate uses
In addition, when As an example, let's consider a Rust crate located at // File `src/foo/bar.rs`
#[wasm_import_module = "../qux.js"]
extern {
fn time() -> f64;
}
#[wasm_import_module = "./corge.js"]
extern {
fn random() -> f64;
} // File `src/qux.js`
export function time() {
return Date.now();
} // File `src/foo/corge.js`
export function random() {
return Math.random();
} And lets suppose that The [
{
"id": "some_unique_identifier",
"path": "/path/to/crate",
"imports": ["src/qux.js", "src/foo/corge.js"]
}
] In this example there's only one crate, but keep in mind that it would contain the information for all the crates (including all dependencies). For the sake of clarity I described the data format using JSON, but I don't really care about the format: it could be binary or anything else. In addition, when generating the
Now that the Of course Everything I described above doesn't require
In addition, the above custom header format is generic, so it can work with other languages like C++. In other words, it's not tied specifically to Rust, it simply contains some filepath information, nothing else. So it can become a de-facto wasm standard (similar to the work being done on embedding And because it simply contains filepaths, it doesn't need to be limited to So let's say somebody is compiling to The above proposal doesn't allow for Also, I'm not sure if it's even a good idea to allow for importing |
Oh, also, in my above proposal I assumed that I still think that's the best idea, however there is an acceptable alternative: #[wasm_import_module = "/src/qux.js"] In this case the import is relative to the crate, not relative to the current The custom header format would be exactly the same, the imports within However, I don't like this because it's inconsistent with filepaths in ES6 modules. I think it's really nice if But if other people are convinced that this approach is better, then I'll accept it. The exact syntax for |
Also, I mentioned the need to uniquely identify each crate. Rather than generating a unique identifier, it could instead simply use the index to uniquely identify each crate. Looking at the [
{
"path": "/path/to/crate",
"imports": ["src/qux.js", "src/foo/corge.js"]
}
] And generate these imports within the
Rather than using Either approach works fine, but this index-based approach might be easier to implement. |
I'm new to this discussion but... in general this is a well-exercised but somewhat arcane problem called "linking". So I'm going to ramble a little bit and try to describe the problem from that perspective, mainly so I can try to think about it. WASM modules describe all the information needed for linking: what names a module exports, what module names and symbol names a module imports. However, it says nothing at all about how that relates to Javascript or Rust anything else outside of wasm itself. The first problem is one of uniqueness of names, and Rust traditionally solves this by mangling names. If the names need to be something that other programming languages can understand, there's It seems like if you want to treat JS files like Rust you have to fit them into Rust's assumptions (modules, crates, etc) in a reasonable way, and conflating symbol names and module names with file names is not the way to do this. If you want to specify the location of a JS file relative to a Rust file you could easily import the JS file into the Rust code as a string, but I don't know what that accomplishes (I frankly know very little about the JS <-> Rust bridging process, just how it looks from Rust and wasm.) From the Rust side, it seems semi-obvious that if you want to be able to call JS code from Rust, it needs to be treated like a Rust module. This probably means just making it a Rust module and filling it with FFI code generated by From the perspective of the wasm host, it's the wasm host that's in charge of (dynamically) linking things at runtime. wasm itself has no idea how any of this language/crate/module/function nonsense; it just knows about names in wasm modules. The host provides modules full of names, and how it gets them is none of wasm's business. A crate is not really a unit of dynamic linking, and Rust has no language-level story for dynamic linking, so a .wasm file built from Rust source is going to either include all the code necessary for running the whole program, or just a subset of it, a list of symbols it imports and exports, and a big sign saying "no longer rustc's problem". If we take the cue from the C ffi, then the For calling Rust from JS, I don't know much about how JS modules or the wasm<->JS host interface operates, but a Rust program will export some (non-mangled, probably) names of functions or data and JS will ask the wasm host to provide them. Binary distribution of a library is a different ball-game; rustc helps you not at all there, as far as I know, it's generally up to the programmer and the operating system to make sure things slot together the way they're supposed to. In this case, the web browser/wasm host is our operating system, I don't have any real answers, I just want to make sure everyone is 100% aware of what is going on: we're talking about building a format and tools for dynamic linking. WebAssembly is our .dll format, and it provides fairly complete but also quite minimal information. We need to figure out how to map the assumptions of Rust crates and Javascript modules onto the information it provides us. Rust provides assumptions that make linking easy: mangled names, no dynamic linking, no unlinked libraries hanging around, dedicated FFI crates. wasm works fine with these assumptions. So it feels like the real question is "how do we easily make nice FFI crates out of Javascript and bundle it all up to deliver to a web browser to make it as hard as possible for dynamic linking to fail?" The other question is "how do we nicely expose functions from Rust wasm modules to JS?" and that's not something I know how to handle, but the Rust -> JS part is pretty Minor edits for clarity and correctness. |
@icefoxen I appreciate you sharing your thoughts. Allow me to respond to some of the things you said:
I think it's important for this discussion to understand exactly how Rust, JS, and wasm interact with each other (which includes the linking process). This is our current plan (though it might change): Let's suppose that you want to compile a Rust crate to wasm:
Within the #[wasm_import_module = "./foo.js"]
extern {
fn foo() -> f64;
} Or they can use the following to import npm packages (which are specified in the #[wasm_import_module = "foo"]
extern {
fn foo() -> f64;
} The above import syntax is standard for JavaScript:
After compiling the Rust crate (using This In other words, all of the Rust static linking has already occurred, there is no dynamic linking between Rust crates. For each usage of However, the At this point But we're not quite done yet. You now need to use the
The end result is that the
At this point the Now you must link the
And now you can finally run the program, because everything has been successfully linked together. There will be a tool which streamlines the above process and makes it easy (by calling Most of what I just said has already been implemented. The part that hasn't been implemented (and what my proposal is trying to fix) is allowing Rust crates to contain By using However, the import paths in the compiled My proposal fixes that by adding a tiny bit of filepath information to the Note: my proposal has nothing at all to do with variable or function names. As you said that is a solved problem with ES6 modules. My proposal also has nothing at all to do with FFI interop between JS <-> Rust, that's already handled by My proposal is only about making sure that the import filepaths within
Yes of course Rust will need to use But that's completely separate to my proposal. The problem my proposal is trying to fix is the following:
The situation with JS is very different from the situation with C. Being able to include
Unfortunately it's not that simple, because of the way that wasm + JS + npm are, things cannot just be handled in the same way it's handled with C.
That infrastructure already exists (Webpack, Rollup, Parcel, etc.). And the plan is for us to reuse that infrastructure as much as possible. The purpose of my proposal is just to ensure that the filepaths are correct so that Webpack/Rollup/Parcel/etc. can do the final linking.
If we can avoid the header information then that would be great! I'm definitely open to alternative proposals which fix the filepath problem. However, the status quo is not sufficient to fix the filepath problem. So we need some solution (not necessarily my solution).
Yes, after |
By the way, you might be wondering why it needs to go through those multiple steps (running There's a few reasons:
|
Do we have any issues opened for this on the
I could not agree more on this.
How the npm dependencies will be handled here? I agree this will work great if you have |
@sendilkumarn Thanks for taking a look!
I don't think so. There's nothing wasm-pack can do until after rustc (or wasm-bindgen) supports this.
They'll be handled the same way wasm-pack handles them right now: it creates a Bundlers (like Webpack, Rollup, and Parcel) understand the |
@Pauan Thanks for all those detailed explanations. I really enjoy reading them 👍
Even inside the P.S: There are a lot of use cases. Mapping & Framing them in a single mental model is really difficult. (atleast for me) |
You import it with normal npm import syntax, like this: #[wasm_import_module = "foo"]
extern {
fn foo() -> f64;
} That will import the Then that will translate into this wasm import:
I haven't tested it, but I believe the above wasm import will Just Work(tm) with the JS bundlers (Webpack, etc.). So in the case of npm modules, the only thing wasm-pack needs to do is generate the I suppose there might be some conflicts between my proposal and npm packages (e.g. if there is an npm package called That can be solved easily enough by using prefixes/namespaces:
So the Just to be clear, these namespace prefixes would be created by rustc (or wasm-bindgen): the The way that Now, when running wasm-pack, it would:
So the end result would be something like this:
Notice that it copied the Now everything is set up correctly so that Webpack (etc.) can correctly do the final linking. This handles the conflict correctly, because |
I think this is handled by RFCs like rustwasm/rfcs#8 and rustwasm/rfcs#6, so closing. |
I hope this is the right place for this discussion; I tried finding a forum that would reach the right target audience and this seemed like the best place.
I've been working with Rust/wasm for quite a while. For that I've been using
wasm-bindgen
quite heavily (for a not-yet-released project) using a Webpack loader I've authored: https://github.com/dflemstr/rust-native-wasm-loaderOne thing I have noticed is that there doesn't seem to be a clear direction regarding how code modules are to be treated between Rust and foreign modules (such as ES6 modules or other wasm modules) on the source level.
Incidentally, it seems to be working similar to C but not consistently. This behavior is quite confusing when interfacing between two languages that both support modules/namespacing (Rust+JS)
Remember, this is the perhaps naive impression of somebody using Rust together with wasm. I'm aware of how the current wasm FFI is closely modeled after the C FFI.
Imagine the following
tree
(with the module hierarchy like you would expect):What should the semantics be around visibility and scope for these files? This is what my intuition would tell me:
editor.js
should importleftpad.rs
as../util/leftpad.rs
, or import somemyrustlib.wasm
module where the symbol is available asutil::leftpad::xyz
in some fashion.leftpad.rs
should importeditor.js
as../reducer/editor.js
.util
module, so thateditor.js
would not see such a symbol defined inleftpad.rs
, butleftpad.js
would be able to.With that said, here is what current tools are doing:
rustc
symbols that areexport "C"
'd are exported in a global shared namespace.wasm-bindgen
will export everything marked with#[wasm_bindgen]
also in a global shared namespace (including more advanced things such as structs/classes etc).wasm-bindgen
will resolve all imports (#[wasm_bindgen(module = "...")]
) relative to the location of the final generated module artifact, wherever that may be.wasm-bindgen
only supports symbols that are both crate-public and exported.I find this behavior surprising and feel like one could do either of these things:
lib.rs
/main.rs
) and resolve them relative to that location.Does this sound reasonable? What are everybody's thoughts on this?
The text was updated successfully, but these errors were encountered: