The Life of an Optimization Barrier
blog.trailofbits.com
blog.trailofbits.com
Seems crazy that this hasn't happened already given how prevalent and old this issue is.
I’ve heard rumours that Google have done some work internally in this area, but it was never open sourced or upstreamed.
I imagine it's not straightforward, just seems like the effort would have started earlier.
Actually, that's not entirely true; if running wasm-pack, there's another optimizer `wasm-opt`, running on output of llvm and wasm-bindgen.
I've found it able to produce measurable improvements in speed and size of the produced .wasm - which is quite surprising to me, given it's basically a set of simple (compared to llvm) optimization passes running on top of whatever llvm already produced, with none of the metadata. However, this also means it can easily do unexpected things like inlining a `#[inline(never)]` function (because again, it only sees the .wasm file, with none of rust or llvm metadata) - I stumbled upon this several times when trying to profile wasm code in browsers.
This also means that if llvm ever supported constant-time semantics, wasm-opt would happily ignore these too.
Then there are steps below that layer to consider as well. Maybe not so relevant when targeting Wasm, but x86 code is increasingly executed inside emulators on ARM chips. Do those respect the constant-timeness?
And what about processor microcode and fused instructions, can a future CPU include an optimization that will un-constant-time your code? (In other words: I really don’t know, are there constant-time guarantees made by the ISA?)
Not on AMD64, at least: neither Intel nor AMD will guarantee the timing behavior or timing complexity of an instruction between processors. REP prefixes with string/data operations exemplify this -- they historically had data-dependent timings, but have become increasingly decoupled as both Intel and AMD have shoved "fast string" modes into ucode.
What we get in return for all that pain is slightly faster execution — in theory. A recent post on HN actually showed that at least in some cases, modern LLVM compiles to code that actually runs 1-2% slower than an ancient LLVM version.
Shouldn’t we conclude from all this that what we need is a movement to go back to simple, deterministic and fast compilers?
Maybe the problem is that making big improvements to a compiler for a fifty year old language that sits pretty close to the metal is getting increasingly difficult because the people writing the compilers are running out of clever tricks.
Plenty of JavaScript and WASM exists for cryptography purposes, for what it's worth, because Node isn't the only runtime and "native" code isn't easily portable.
We also get protections against whole classes of security bugs, ala Spectre and other vulns. Some of those protections make speed tradeoffs.
(To explain: A decade ago, if there was a code path where you dereference null, you would get a clean crash. Not pretty, and potentially a denial of service, but usually not further exploitable. Nowadays, the compiler will infer that this path is not taken, eliminate the if, potentially eliminate other checks as well because they are then reasoned to be redundant, and then randomly execute something. This is practically more dangerous in a lot more situations than Spectre ever was.)
There should probably be some kind ——constant-time compiler flag to prevent the compiler from compromising that property when it’s a higher priority than raw performance.