Dev.to · 6 min read

A new WebAssembly runtime enters the arena... Is it worthy?

A new WebAssembly runtime enters the arena... Is it worthy?

A few minutes ago, I released my brand-new WebAssembly runtime, Wago. There's one massive problem though. Wasmtime is mature, fast, and backed by the Bytecode Alliance. Wasmer has been around for years, V8 speaks wasm fluently, and if you're writing Go, wazero is already the de-facto answer. So why would I be stupid enough to make a new wasm runtime? Seems like all the bases are already covered, right? ...right..???? Well, kind of. The existing runtimes are really good, don't get me wrong. I don't think Wago exists because Wasmtime or wazero are bad. In fact, I've spent way too many hours of my life staring at their codebases while building this thing. But there were a few things that still bugged me. Let me give you some backstory. The past two companies I've worked at, Hypermode and Impart Security, both use the venerable wazero to run isolated, secure wasm applications. And it's great! We don't need to boot thousands of Docker containers, spin up micro-VMs, or do anything crazy like that. We can use a few kilobytes of RAM to run some wasm here and a megabyte there. Wazero though... wazero is slow as hell. When I compare it to Wasmtime, it runs much slower. This isn't really wazero's fault though. Wasmtime gets to use Cranelift, a proper optimizing compiler that outputs excellent machine code. Wazero is meant to be simple and "just work." It doesn't aggressively chase peak performance; it chooses to ride the middle ground. Maybe I'm crazy, but I deem that laziness. A quote stuck with me, and I forget who said it. It went something like: Fast hardware should bring excellence, not reason to waste it. I'll answer you myself. I am crazy. I'm GREEDY. I want unreasonably excellent performance while using tiny amounts of RAM. I want something that steps so lightly you barely notice it. The only thing you notice is the results--a runtime that used kilobytes of RAM to compile, generated less machine code than wazero, runs substantially faster, and somehow remains a single-pass compiler. So, I started experimenting. At work, we can deal with poor performance by scaling, but that has a limit. More CPU costs money. More memory costs money. Eventually, "just add another machine" stops being a satisfying answer. I thought, Hey, if i can reduce memory usage by 10x and improve performance by 50%, surely I can cut our costs in half, right? Compilation was especially painful for us. On some of our larger workloads, wazero could peak at hundreds of megabytes of memory just to compile a module. That was unacceptable. So I set myself some slightly unreasonable goals. I wanted a compiler that: used kilobytes of memory to compile generated less machine code compiled several times faster produced code substantially faster than wazero and was still single-pass Oh, and I wanted it to eventually run on embedded systems. Totally reasonable, right? Okay, okay, ideas are nice and all... but did you actually accomplish anything? My experiments eventually became Railshot, Wago's compiler. Railshot doesn't build some enormous IR, optimize it tens of different ways, and then finally spit out pristine machine code. It mostly just goes: .wasm → analyze → compile → machine code That sounds horribly naïve. And, traditionally, it is. Single-pass compilers are incredibly fast to compile with, but they generally produce worse code. Optimizing compilers get to look at the entire function, analyze it, shuffle things around, run optimization passes, and make globally better decisions. Railshot doesn't get that luxury. But scarcity brings excellence, because to overcome your limitations, you must become more than you ever imagined. The wonderful thing about WebAssembly is that binaries often arrive already optimized. If your .wasm came out of LLVM, Rust, TinyGo, or another optimizing compiler, a whole bunch of work has already happened before Wago ever sees it. I don't necessarily need Railshot to rediscover everything the frontend compiler already figured out. WebAssembly is also beautifully structured! Blocks, loops, branches, types, and the operand stack give the compiler a surprising amount of information basically for free. So, instead of building a giant IR, I tried to exploit that structure directly, which led me to something called Valent Blocks (go read the research paper about it if you want to go down that rabbit hole). The basic idea is to maintain small, temporary views of the program that give Railshot just enough information to make smarter decisions while it is compiling. Things like: register pinning bounds-check elimination instruction combining constant folding branch simplification avoiding unnecessary loads and stores ...all without turning Railshot into a traditional (fat) optimizing compiler. In other words: Cheat as much as possible while remaining single-pass. I suppose there's some tipping point where cheating becomes more smart than it is lazy. And somehow... it worked. Let's see those numbers though... Here's the obligatory disclaimer: I wrote Wago. I also ran these benchmarks. Please go run them yourself. Seriously. Please. That said... Here's a few corpora Execution Latency Compile Latency Compile Heap The full benchmark tables are here Across my current corpus, Wago is roughly: 5.7× faster to compile 11× less memory-hungry during compilation 70% faster at execution 52% smaller in generated machine code Not bad for a compiler that's supposed to be generating bad code. The compilation memory is probably the result I'm happiest with. I wanted running wasm to be cheap enough that you just... don't need to worry about it. Ooh, but can it run real stuff? A fast compiler isn't much use if it can only run Fibonacci and some compression algorithms. So Wago has grown into quite a bit more than Railshot. Today, Wago has: support for modern WebAssembly (3.0+!) extremely cheap instance creation standalone native executables a cool plugin system AMD64 and ARM64 native backends (risc-v in the works!) macos, linux, and windows support And here's where things start getting a little weird. Wago's plugin system can extend the runtime itself. Not just add another host function. I mean custom WebAssembly instructions and types. I've even implemented wider SIMD support including 512-bit vectors backed by AVX-512 where available as a plugin That's the kind of insanity I wanted Wago's architecture to allow. A runtime that belongs to you. A runtime that doesn't worry you. A runtime that gives you all the freedom you could imagine. I mean, wasmer is like some cloud-container-pass thing right now. I don't want that. rather than paying wasmer, you can always sponsor me :) :P :O :D

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Cybersecurity News