> Tom Tromey and I have replaced the most performance-sensitive portions of the source-map JavaScript Library’s source map parser with Rust code that is compiled to WebAssembly. The WebAssembly is up to 5.89 times faster than the JavaScript implementation on realistic benchmarks operating on real world source maps.<p>Wow. Some people have been wondering if Rust has a "killer app", but this could be it. Right now WebAssembly only supports non-GC languages (so C, C++, and Rust), and of those three Rust is the easiest to get started with. It looks really appealing.
Wow, I had assumed a lot more of the Rust runtime/stdlib would need to come along with anything, but the wasm-unknown-unknown target and aggressive culling of unused code looks to make this really competitive. That's awesome, and I'm really happy to have my predictions and assumptions proven wrong. :)<p>Edit: Some of the numbers:<p>Original JS: just under 30,000 bytes.<p>Closure compiler minified JS: 8,365 bytes.<p>New combined JS and WASM: 20,996 bytes<p>Roughly half of the WASM output is JS and half of which is WASM, since not all components in the original JS were replaced, just a specific subset. There is some duplication of functions that both the remaining JS still uses and the new WASM code does as well. Rust diagnostic and error messages appear to still be present inthe data section although unusable, so could be cleared out with better tooling.
VLQ decoding is sort of subtle, and the one shown in the article under "Base 64 Variable Length Quantities" looks broken.<p>1. `shift` can grow large, and an overlong left shift will panic in Rust. I expect an input like "gggggggC" to crash.<p>2. `accum >>= 1` is wrong because it rounds down. It needs to be a round-towards-zero: `accum /= 2`.<p>3. `accum` is a 32 bit int which is too small. It needs to be larger so it can represent -2^31 in sign-magnitude form.
FYI, the Sentry folks have also written a Rust-based sourcemap decoder:<p><a href="https://github.com/getsentry/rust-sourcemap" rel="nofollow">https://github.com/getsentry/rust-sourcemap</a>
This is brilliant. Very excited to see more applications like this making great use of the possibilities of Rust+WASM.<p>One thing that stuck out to me was the exploded-struct callback mechanism for reporting Mappings back to JS. I've also been struggling to handle the low-bandwidth interface between JS and WASM. That wasn't a strategy I'd considered, but it's pretty neat.<p>It's simple enough and will work in this case, but unfortunately doesn't generalize very well. I've been exploring using well-defined binary encoding for this purpose (specifically Cap'n'Proto, but Protobuf or another binary encoding would work, too).<p>See an example I put together: <a href="https://github.com/couchand/rust-wasm-capnproto-example" rel="nofollow">https://github.com/couchand/rust-wasm-capnproto-example</a>. I'm definitely going to go back and clean that up with some of the FFI patterns from this article.
parse_mappings is incorrect. It has the following line:<p><pre><code> Vec::<usize>::from_raw_parts(capacity_ptr, size, capacity);
</code></pre>
But size is wrong. size here is the number of bytes that JavaScript instructed Rust to allocate. It's not the size of the Vec::<usize>.<p>This should be harmless as the resulting Vec is immediately deallocated, so the only thing that size is used for is deinitializing values, and since the values are usize there's nothing to deinitialize. But it's still technically incorrect.
The get_last_error() snippet shows LAST_ERROR as being a static mut (which IIRC requires unsafe access as it's not thread-safe). But the parse_mappings() snippet shows safe access to LAST_ERROR using an API that clearly indicates it's not just an Option. I assume that at this point it's actually a std::thread::LocalKey.