As an early 30th birthday gift to myself, today I’m releasing into early-access, Hako1.
Hako is a fork of PrimJS2 that is designed to provide a portable, secure, and performant JavaScript engine that can easily be embedded in any program and scale with your needs.
What makes it secure?
PrimJS (and by extension QuickJS) are written in C/C++; integrating them as-is in your program means you inherit any security issues that might be lingering inside them.
Hako compiles down to WebAssembly, a memory-safe, sandboxed execution environment. This means even though Hako is written in C/C++, programs it is embedded in have an extra layer of protection from any potential memory vulnerabilities.
Memory safety is only one piece of the puzzle. For example, consider this common JavaScript Fibonacci function:
function fibonacci(n) {
return n < 1 ? 0
: n <= 2 ? 1
: fibonacci(n - 1) + fibonacci(n - 2)
}
console.log(fibonacci(4))
This was pulled from a top Stack Overflow3 answer. While fibonacci(4)
returns 3 as expected, calling fibonacci(50)
will freeze your browser due to exponential recursive calls.
To combat code that may cause a denial-of-service (regardless of intent), Hako also has a built-in sandboxing mechanism in the form of VMContext which allows you to restrict the capabilities of JavaScript code, such as removing the ability to allocate new memory altogether or removing the use certain JavaScript features.
Additional APIs are exposed to limit the amount of memory and 'gas' a script can consume before it is terminated, and development follows an opinionated 'fail-fast' design for bailing out of potentially unstable code that could consume or block I/O. Combining all of these, you can run hundreds of VMs in parallel on a single machine.
These APIs also allow you to attach opaque data at multiple points, so if a user is abusive, you can easily identify and limit their use of your service.
What makes it embeddable?
Hako does not use Emscripten to compile down to WebAssembly; as long as your environment of choice has a modern WebAssembly runtime, you can embed Hako in it. You can see a no-sugar example of embedding Hako in Go here.
Speaking from the personal trauma of shipping a native library that needed wrappers in various languages, treating the WebAssembly module as a cross-platform static library is a breath of fresh air. No complex build steps. No brittle FFI code generation. No tooling that randomly breaks when you come back to it after some time away because Rust decided to change something.
If you have hako.wasm you’re already 90% of the way there. It is also incredibly tiny. The release build is ~800KB.
This is the part where I wanted to say it’s so embeddable you can even use it right here, but Substack doesn’t support iframes, so here is a URL4.
What makes it fast?
Hako being a fork of PrimJS means we inherit many of the improvements it made. In sythentic benchmarks, Hako shows performance gains of 28% over QuickJS. Compiling to WebAssembly has no noticable impact on performance as the amazing JIT compilers of JavaScriptCore/Bun, V8/NodeJS, and Wasmtime allow code to run at near-native speeds. I’ve also implemented a number of optimizations (including SIMD) for a few hot paths.
You also have the ability to directly profile code in Hako to see just how fast it is executing
And if you do notice slowdowns, build Hako with memory debugging and you can see if your host code is leaking resources anywhere.
Some caveats
The template interpreter and garbage collector avaliable in PrimJS are not fully enabled in Hako. The ByteDance team has not yet open sourced the tooling needed to generate snapshots; I am currently implementing my own tooling to achieve this so whether or not they do this functionality will be enabled soon.
This project is still early and feedback is crucial to stabilizing the API/ABI. If you want to contribute, head over to the Github to get started.
(ha-ko) or 箱 means “box” in Japanese.
https://stackoverflow.com/a/31983992
got it working in a tweet though