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

How to write Wasm friendly code #28

Closed
est31 opened this issue Jan 18, 2018 · 17 comments
Closed

How to write Wasm friendly code #28

est31 opened this issue Jan 18, 2018 · 17 comments

Comments

@est31
Copy link

est31 commented Jan 18, 2018

Per a comment by @epage on reddit: How can a library author ensure that their library can be used from wasm? What does this mean in particular?

@mgattozzi
Copy link
Contributor

mgattozzi commented Jan 18, 2018

I think we can consider it along the same lines as embedded development (at least for now).
#![no_std] is a good start but I don't think that's the whole story. We're still figuring out in #16
what kinds of assumptions we can make about the host environment. This seems a bit split about
what kind of assumptions we can make and limits what might constitute "best practices". I feel confident that this will be fleshed out over time as we explore this space more and talk it out.

Another limiting factor right now it that wasm is only a 32 bit target right now. We can't use threads or the like until this becomes accepted and implemented as part of the wasm spec beyond the MVP release. This limits usage of crates with dependencies like crossbeam and rayon that are built around parallelization and lock free data structures.

What might be considered good practice for now is letting a library declare a wasm feature flag to
take out functionality from the library that might not work if compiled to wasm. For instance if I had a function that depended on rayon to speed up processing with the wasm feature flag I could have that function be compiled as single threaded so that it ended up being compatible.

As with any project tests to make sure it compiles and runs properly can help ensure that a library is wasm compatible. We should consider figuring out how to best run and test code in a wide variety of contexts such as in an embeded space or in various browsers. Ideally it should just work the same regardless of the environment but as we all know, computers.

These are just a few of my thoughts but I'd love to see what others think/point out inconsistencies or wrong things in what I've stated etc. as well as experiences dealing with wasm.

Blog posts like this one are a good example of this. The author describes some difficulties with porting the library to wasm but mostly it seemed not too error prone!

@koute
Copy link

koute commented Jan 18, 2018

We should consider figuring out how to best run and test code in a wide variety of contexts such as in an embeded space or in various browsers.

As far as testing goes you can already run your normal Rust #[test]s with cargo-web under Node.js and headless Chromium. (Although on wasm32-unknown-unknown your tests won't print out anything since println! doesn't work, so your best bet is to use the *-emscripten targets for now.)

One thing that's still missing is a way to write asynchronous tests. This doesn't matter much if your library is pure Rust, but if you want to call out to JavaScript and that JS code is asynchronous (e.g. you use setTimeout which will call back into your Rust code) then there's currently no way to test that with normal #[test] tests.

@mgattozzi
Copy link
Contributor

As far as testing goes you can already run your normal Rust #[test]s with cargo-web under Node.js and headless Chromium.

Is there Edge, Safari and Firefox support as well or at least planned somehow? Getting it working on every browser that supports it is good rather than letting one web browser dominate the space.

One thing that's still missing is a way to write asynchronous tests. This doesn't matter much if your library is pure Rust, but if you want to call out to JavaScript and that JS code is asynchronous (e.g. you use setTimeout which will call back into your Rust code) then there's currently no way to test that with normal #[test] tests.

Would something like Neon help out here? Or no?

@Pauan
Copy link

Pauan commented Jan 18, 2018

What might be considered good practice for now is letting a library declare a wasm feature flag to
take out functionality from the library that might not work if compiled to wasm. For instance if I had a function that depended on rayon to speed up processing with the wasm feature flag I could have that function be compiled as single threaded so that it ended up being compatible.

It's not an ideal solution, but right now I'm using cfg conditional compilation to make my code work in either the desktop (with rayon and multithreading) or in wasm (without rayon and without multithreading):

// Single threaded
if cfg!(target_arch = "wasm32") {
    (self.populace.len()..self.amount).map(closure).collect()

// Multi threaded
} else {
    (self.populace.len()..self.amount).into_par_iter().map(closure).collect()
}

Of course this isn't ideal:

  • It would be better if rayon itself did the cfg check, so I wouldn't have to.

  • It would be better to use cfg to check for wasm features (like threads), instead of assuming that the wasm32 target doesn't have threads.

But despite its flaws, it's a solution that works for me right now.

An open question is: should we have runtime detection for wasm features (e.g. threads), or is the compile-time cfg enough?

@koute
Copy link

koute commented Jan 18, 2018

Is there Edge, Safari and Firefox support as well or at least planned somehow? Getting it working on every browser that supports it is good rather than letting one web browser dominate the space.

I agree! I'd like to at least also get headless Firefox support in myself, sooner or later. As for other browsers - it'd be great to have them too, but I have a lot of other higher priority stuff I want to add first. (Although if anyone's interested patches are of course welcome.)

Would something like Neon help out here? Or no?

More like being able to supply your own test harness would help, e.g. being able to do something like this (JS testing frameworks like mocha all do this):

#[test]
fn test_foobar(done: Box<FnOnce>) {
    do_something_async.then(move |_| {
        done();
    });
}

In normal Rust this is not necessary since you can just block on anything asynchronous, but in the Web environment you can't since its asynchronicity is inherently cooperative and single-threaded.

It would be better to use cfg to check for wasm features (like threads) rather than assuming that the wasm32 target doesn't have threads.

It would be great to have some cfg! variable which would check for all of Rust's Web targets. (I don't think there is one currently?) If you only check for cfg!(target_arch = "wasm32") then it won't mach when compiling for asmjs-unknown-emscripten.

@mgattozzi
Copy link
Contributor

Ah okay I see what you meant by the test harness. Maybe #[async]/await!() might end up helping here somehow.

@pepyakin
Copy link
Member

@koute Did you hear about empterpreter? I didn't look into it, but maybe it is something that might help in this.

@koute
Copy link

koute commented Jan 19, 2018

@koute Did you hear about empterpreter? I didn't look into it, but maybe it is something that might help in this.

Yeah, I did, but as far as I can see that's only for asm.js (?).

I'm fairly certain it would be possible to implement asynchronous tests for the wasm32-unknown-unknown target with a procedural plugin, some .wasm-bytecode level magic and a custom JS harness. (I'm planning on doing that at some point as I need this to test asynchronous stuff in stdweb, which currently doesn't have any tests for anything asynchronous it exports; but I'm waiting for println! to work, otherwise it's totally pointless.) It might even be possible to use the Emterpreter to extend that to at least asm.js. But, it would be so much nicer and simpler to have official support from Rust for custom test harnesses.

@aidanhs
Copy link
Contributor

aidanhs commented Jan 20, 2018

The emterpreter is a generated VM (in asm.js) and bytecode. WASM is already a bytecode, so you 'just' need the VM, a wasm interpreter, in any language of your choice (since you can compile the VM to wasm).

You could go the emterpreter route, but I extracting it out of emscripten and hooking it up in the way you want seems like a bit of a pain for something you'd eventually throw away. Instead you could 'create' a wasm interpreter directly (since 'creating a wasm interpreter' one may just mean taking an existing one and tweaking it).

@fitzgen
Copy link
Member

fitzgen commented Jan 20, 2018

parity_wasm has an interpreter, and cretonne will eventually be a JITing solution. Would be awesome if we could hook these up to #[test]s.

@est31
Copy link
Author

est31 commented Jan 20, 2018

@fitzgen parity_wasm and cretonne can only execute wasm and nothing beyond. Any tests that don't need integration with the host can be executed by compiling to the machine target of the developer. Those tests that do need host integration most likely need access to the host as well which is either a JavaScript engine or a web browser in headless mode. There is a very small group of tests that don't need host integration but that do rely on wasm particularities like ones that use the proposed wast inline assembly macro.... only that group of tests will really benefit from being hooked up to wasm only interpreters.

For tests they won't be very useful, but maybe for benchmarks they will. But only if they have similar performance characteristics to production environment JITs.

@aidanhs
Copy link
Contributor

aidanhs commented Jan 21, 2018

@est31 this isn't an insurmountable issue, any interpreter would likely need modification anyway to allow pausing (it's not top of many people's feature lists).

I jotted down some notes after looking at parity-wasm briefly (thanks, didn't realise it had an interpreter):

  • https://github.com/paritytech/parity-wasm/blob/516de9a/src/interpreter/runner.rs#L70-L114 is the main loop for running a function. Control appears to return to there when calling another function
  • You could change the definition of fn function_body at https://github.com/paritytech/parity-wasm/blob/516de9a725e84e8de2afe7ffda8c18c4222665a4/src/interpreter/module.rs#L75 to be an enum instead of an option, so you can have another variant like PauseExecution
  • You'd need to implement the trait ModuleInstanceInterface on a custom struct to return PauseExection when function_body is called. I'm hazy on how hard it would be to implement all the other functions, but src/interpreter/native.rs seems to provide a good starting point.
  • You can then recognise the PauseExecution variant inside the function call loop, gather up all of your state and pass it back upwards. It'll probably need some api redesign to support resuming by passing state back in - maybe resume_function to match the existing run_function? Most code would be shared between the two.
  • Once you pass state upwards, you can inspect what was being called and do whatever async stuff you want. When you're ready, you can resume.

You then have a pauseable wasm interpreter for the web and can run tests that assume synchronous behaviour inside nodejs. I don't know if this is a worthy goal or not, just wanted to give some input on how it might be possible.

@est31
Copy link
Author

est31 commented Jan 21, 2018

Oh I see the actual goal was pausing support to drive async tests.

I think the simplest solution would be to add experimental support for an ability to mark functions that return futures as #[test] and having the harness execute those. It could be done via a built in language trait to not cement futures-rs into the language, similar to how it was done with ? in main with the termination trait. This forces coders to write true async code so it is less ergonomic than synchronous code, but I think it is the most proper solution long term.

Implementation wise I believe this is much easier than wiring up a whole interpreter inside wasm and handling all the interaction between the interpreter and the javascript environment.

In fact there is a hack to enable async support without the need for a JIT/interpreter: just use web workers and let them communicate with the main thread via shared array buffers. While the result for the computation hasn't reached the worker thread yet it will have to busy wait as JavaScript has gotten no sleep function. So it is a hack. But likely faster than running an interpreter :p. Also another downside is that thanks to meltdown+spectre SharedArrayBuffers have been disabled by default. Temporarily they claim and I hope they will be re-enabled some day. For now you need to enable them manually, at least Firefox has an option for this.

@aidanhs
Copy link
Contributor

aidanhs commented Jan 22, 2018

There's no 'good' solution.

  • my suggestion requires tweaking and wiring up a wasm interpreter
  • your suggestion 1 requires a language trait or similar, something requiring an RFC at minimum
  • your suggestion 2 requires busy waiting, a disabled-by-default browser flag and a bit of machinery (be it repeatedly checking an well known address or receiving a message) to make sure the main browser process knows when a syscall is waiting

Which you think is easiest depends on your own personal metrics and whether you want a solution now or in the long term.

@pepyakin
Copy link
Member

@aidanhs JFYI parity-wasm's interpreter has been splitted into it's own repo: wasmi and have quite different API

@fitzgen
Copy link
Member

fitzgen commented Feb 15, 2018

Folks who are interested in testing related things, should drop a comment with their needs over at rust-lang/rfcs#2318 and see if that eRFC meets them.

cc @Manishearth

@ashleygwilliams
Copy link
Member

we should not track this here- it's definitely a wider issue that is already addressed in the wasm-bindgen book and the tutorial (always can be improved of course! - but we should file more specific issues on the relevant repo) closing.

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

8 participants