There have been lots of great posts about experiences with Rust these days, but nothing about really large applications. While I may be writing about my own experiences with writing Rust for Servo later, I thought that this might be a useful thing to share to fill in the gap.
Note that it was written for an academic audience, but it isn't too dense :)
The section on open problems is interesting. Can anyone comment on their real world impact?
5. Open problems
While this work has discussed many challenges in browser design
and our current progress, there are many other interesting open
problems.
Just-in-time code. JavaScript engines dynamically produce na-tive code that is intended to execute more efficiently than an in-terpreted strategy. Unfortunately, this area is a large source of se-curity bugs. These bugs come from two sources. First, there are
potential correctness issues. Many of these optimizations are only
valid when certain conditions of the calling code and environment
hold, and ensuring the specialized code is called only when those
conditions hold is non-trivial. Second, dynamically producing and
compiling native code and patching it into memory while respect-ing all of the invariants required by the JavaScript runtime (e.g., the
garbage collector’s read/write barriers or free vs. in-use registers)
is also a challenge.
Integer overflow/underflow. It is still an open problem to provide
optimized code that checks for overflow or underflow without in-curring significant performance penalties. The current plan for Rust
is to have debug-only checking of integer ranges and for Servo to
run debug builds against a test suite, but that may miss scenarios
that only occur in optimized builds or that are not represented by
the test suite.
Unsafe code correctness. Today, when we write unsafe code in
Rust there is limited validation of memory lifetimes or type safety
within that code block. However, many of our uses of unsafe code
are well-behaved translations of either pointer lifetimes or data
representations that cannot be annotated or inferred in Rust. We are
very interested in additional annotations that would help us prove
basic properties about our unsafe code, even if these annotations
require a theorem prover or ILP solver to check.
Incremental computation. As mentioned in Section 2, all mod-ern browsers use some combination of dirty bit marking and incre-mental recomputation heuristics to avoid reprocessing the full page
when a mutatation is performed. Unfortunately, these heuristics are
not only frequently the source of performance differences between
browsers, but they are also a source of correctness bugs. A library
that provided a form of self adjusting computation suited to incre-mental recomputation of only the visible part of the page, perhaps
based on the Adapton [HPHF14] approach, seems promising.
I'm pretty surprised by the comments regarding integer overflow penalties. I'd gotten the impression that we've been able to easily able to get it for < 10%, and sometimes closer to no overhead: http://danluu.com/integer-overflow/
The first two issues (JIT code compilation and integer overflow/underflow) add up to a significant fraction of the crashing bugs in Gecko that have security exposure. I can't provide specific numbers, but both are well over 10% of that subset of security bugs.
On the correctness of unsafe code, we have very few instances where Servo will actually crash (as opposed to panic and unwind "gracefully"), but they've overwhelmingly been in unsafe code blocks when we weren't properly respecting lifetimes and caused a use after free.
For interactive applications, the ability to incrementally reflow the page (say, after an element's value for the display property is changed) is crucial. Modern browser engines have a ton of code that tries to minimize the amount of reflow performed (and sometimes loses correctness!), because it's really easy to accidentally reflow too much and fail to maintain 60fps. We of course hope that throwing parallelism at layout will solve some of this in Servo, but having a more principled approach to the problem of recomputation is interesting, too.
Note that the paper was written a while ago, so things are a bit outdated (like the integer overflow bit).
> Just-in-time code.
I'm not the expert on this, but JIT is where a lot of perf wins can be had for javascript. JIT might even be the best place to work on if you want to make your polished JS engine faster.
As far as security goes, goto Lars' reply.
> Integer overflow/underflow.
We now do exactly what was suggested in the paper. Rust enables overflow checking in debug, and we run our CI in debug anyway (release mode build integration is half baked now and I need to work on that)
A concrete example would be a loop that just goes on incrementing something. `for _ in 0..n {ctr++}`. This would be zipped to ctr+=n in release mode (optimizations and no bounds checking), but in debug mode there would be bounds checking making it harder to optimize. Additionally in the real world analogue of this toy example, there would be other slowness in debug, and we wouldn't hit the overflow condition.
So, Rust being Rust the situation is vastly better than C++ or similar. But we still have tons of unsafe bindings and also unsafe being used for perf. Last rust upgrade we had an unsafe transmute of a refcell that broke stuff and caused a confusing segfault in some code far away.
Proving correctness is hard, though. C++ is riddled with undefined behavior, and `unsafe` shares a lot of that UB, plus more UB due to assumptions that rustc makes. When you write `unsafe`, writing code as if you're in the wonderful world of C++ won't work. There are assumptions like "&T will never be mutated" which rustc will make, which should hold even in unsafe code, and if you do something to break that, you cause UB.
On top of that Rust hasn't been properly formalized yet. So this is something that's still far off for us. However, a limited form of correctness checking sounds doable-- but I don't have much knowledge of formal checking to be sure of this.
Of course, a limited form of unsafe correctness as proposed in the paper
Ms2ger told me recently that a majority of the unsafe code in Servo (ignoring the stuff needed for FFI) was to work around the lack of DST coercions (Arc<Trait>) and virtual structs. With the former landing soon and the latter being in the design phase, how much unsafe code would be left working around missing Rust features?
6 comments
[ 2.8 ms ] story [ 20.7 ms ] threadNote that it was written for an academic audience, but it isn't too dense :)
5. Open problems
While this work has discussed many challenges in browser design and our current progress, there are many other interesting open problems.
Just-in-time code. JavaScript engines dynamically produce na-tive code that is intended to execute more efficiently than an in-terpreted strategy. Unfortunately, this area is a large source of se-curity bugs. These bugs come from two sources. First, there are potential correctness issues. Many of these optimizations are only valid when certain conditions of the calling code and environment hold, and ensuring the specialized code is called only when those conditions hold is non-trivial. Second, dynamically producing and compiling native code and patching it into memory while respect-ing all of the invariants required by the JavaScript runtime (e.g., the garbage collector’s read/write barriers or free vs. in-use registers) is also a challenge.
Integer overflow/underflow. It is still an open problem to provide optimized code that checks for overflow or underflow without in-curring significant performance penalties. The current plan for Rust is to have debug-only checking of integer ranges and for Servo to run debug builds against a test suite, but that may miss scenarios that only occur in optimized builds or that are not represented by the test suite.
Unsafe code correctness. Today, when we write unsafe code in Rust there is limited validation of memory lifetimes or type safety within that code block. However, many of our uses of unsafe code are well-behaved translations of either pointer lifetimes or data representations that cannot be annotated or inferred in Rust. We are very interested in additional annotations that would help us prove basic properties about our unsafe code, even if these annotations require a theorem prover or ILP solver to check.
Incremental computation. As mentioned in Section 2, all mod-ern browsers use some combination of dirty bit marking and incre-mental recomputation heuristics to avoid reprocessing the full page when a mutatation is performed. Unfortunately, these heuristics are not only frequently the source of performance differences between browsers, but they are also a source of correctness bugs. A library that provided a form of self adjusting computation suited to incre-mental recomputation of only the visible part of the page, perhaps based on the Adapton [HPHF14] approach, seems promising.
On the correctness of unsafe code, we have very few instances where Servo will actually crash (as opposed to panic and unwind "gracefully"), but they've overwhelmingly been in unsafe code blocks when we weren't properly respecting lifetimes and caused a use after free.
For interactive applications, the ability to incrementally reflow the page (say, after an element's value for the display property is changed) is crucial. Modern browser engines have a ton of code that tries to minimize the amount of reflow performed (and sometimes loses correctness!), because it's really easy to accidentally reflow too much and fail to maintain 60fps. We of course hope that throwing parallelism at layout will solve some of this in Servo, but having a more principled approach to the problem of recomputation is interesting, too.
> Just-in-time code.
I'm not the expert on this, but JIT is where a lot of perf wins can be had for javascript. JIT might even be the best place to work on if you want to make your polished JS engine faster.
As far as security goes, goto Lars' reply.
> Integer overflow/underflow.
We now do exactly what was suggested in the paper. Rust enables overflow checking in debug, and we run our CI in debug anyway (release mode build integration is half baked now and I need to work on that)
A concrete example would be a loop that just goes on incrementing something. `for _ in 0..n {ctr++}`. This would be zipped to ctr+=n in release mode (optimizations and no bounds checking), but in debug mode there would be bounds checking making it harder to optimize. Additionally in the real world analogue of this toy example, there would be other slowness in debug, and we wouldn't hit the overflow condition.
Here's a case where a Rust API has issues, but only when bounds checks are off: https://internals.rust-lang.org/t/rc-is-unsafe-mostly-on-32-...
> Unsafe code correctness
So, Rust being Rust the situation is vastly better than C++ or similar. But we still have tons of unsafe bindings and also unsafe being used for perf. Last rust upgrade we had an unsafe transmute of a refcell that broke stuff and caused a confusing segfault in some code far away.
Proving correctness is hard, though. C++ is riddled with undefined behavior, and `unsafe` shares a lot of that UB, plus more UB due to assumptions that rustc makes. When you write `unsafe`, writing code as if you're in the wonderful world of C++ won't work. There are assumptions like "&T will never be mutated" which rustc will make, which should hold even in unsafe code, and if you do something to break that, you cause UB.
On top of that Rust hasn't been properly formalized yet. So this is something that's still far off for us. However, a limited form of correctness checking sounds doable-- but I don't have much knowledge of formal checking to be sure of this.
Of course, a limited form of unsafe correctness as proposed in the paper