Kiesel Devlog #12: Write Once, Run Anywhere
Published on 2025-04-29.Your favorite JavaScript Engine turned two years old yesterday! I'll use that as an excuse to write another blog post β not just about the birthday though π₯³
Some Numbers
Kiesel is about 80,000 lines of Zig right now, give or take. A lot of it is spec comments so the amount of actual code is a bit lower. There are a few new contributors but I'm still responsible for 98% of commits, so that's definitely the largest codebase I've authored!
Let's start a spreadsheet:
Age | Lines Of Code | Commits | Contributors | test262 |
---|---|---|---|---|
1 | 62768 | 1435 | 5 | 50.4% |
2 | 83627 | 2106 | 9 | 75.1% |
The Roadmap
Let's take a look at what's planned next!
Nothing. This is vibe coding before vibe coding was cool (sans the markov chain). If it's not fun I'm not doing it. If it is fun I might do it, maybe. No plans :^)
And Now For Something Completely Different
When I learned Python as a kid I kept reading about portability and that Python is a good fit for it because your applications will run on Windows, macOS, and Linux. These days I know that's bullshit, good luck finding a language that doesn't do that. Writing code that runs on the three major operating systems isn't difficult, it's the default.
Let's make it difficult!
Platform Abstractions
At some point I started experimenting with porting Kiesel to some rather obscure targets, like this one. The process was always the same: taking a fresh copy of the source code, adding hacks wherever needed until it builds and runs (two very different stages of porting!), taking a funny screenshot, maybe saving the diff somewhere, moving on.
Even if I tried to actually commit those changes it wouldn't have been maintainable. In C you end up with #ifdef
soup and the Zig equivalent is not much nicer. After the third or fourth time I decided that having support for all of those odd devices out of the box would be nice and started working on a platform abstraction. I later discovered that V8 has something similar, although less flexible β v8::Platform
.
The default implementation that Kiesel provides is largely built on top of the Zig standard library and avoids making any assumptions about the underlying platform. You don't have to think about anything:
const platform = Agent.Platform.default();
defer platform.deinit();
This gives you:
- A garbage-collecting allocator that works on a large number of platforms
- Writers for stdout/stderr
- TTY configuration for pretty-printing that takes
NO_COLOR
andCLICOLOR_FORCE
into account - Information about the native stack bounds for stack overflow detection, if supported
- A neutral default locale for Intl (BCP 47
und
) - A function to retrieve the current system time, if supported
Funnily enough, despite being the world's most portable language, the bits of Kiesel that rely on C are the first to fall apart when you leave the happy path of x86_64-linux
. Even though the Zig toolchain has fantastic support for giving you access to libc when cross-compiling that doesn't work on all targets.
In particular we need to deal with the absence of any of these:
The allocator can easily be swapped out with malloc()
or similar at the expense of leaks, regular expressions can operate in a stub mode where they pretend to work but don't, and support for Intl can be removed at build time.
$ zig build -Denable-libgc=false -Denable-libregexp=false -Denable-intl=false
So far, so good! But what happens when other parts of the default platform implementation stop working?
One such example is UEFI which is not supported by regular std.io
APIs, at least until ziglang/zig#22226 is merged. Various other things don't work either so let's make our own platform:
const allocator = std.os.uefi.pool_allocator;
const stdout: std.io.AnyWriter = .{ ... };
const stderr: std.io.AnyWriter = .{ ... };
const platform: Agent.Platform = .{
// UEFI pool allocator, memory is leaked instead of GC'd
.gc_allocator = allocator,
.gc_allocator_atomic = allocator,
// Custom writers using the UEFI console
.stdout = stdout,
.stderr = stderr,
// Coloring is only supported using ANSI escape codes and the
// Windows console API, not UEFI console (yet!)
.tty_config = .no_color,
// Stack overflow detection is not supported
.stack_info = null,
// Build without Intl, default locale is void
.default_locale = {},
// This happens to have an implementation for UEFI :^)
.currentTime = std.time.milliTimestamp,
};
Additionally Zig provides std.Options
as a way to customize certain behavior of the standard library at compile time, which comes in handy too. In the case of UEFI we set a no-op logFn
until std.io
learns about the UEFI console, as well as a custom implementation of cryptoRandomSeed
, which tries to use std.posix.getrandom()
by default. And well, UEFI is very much not POSIX.
You can find the full code for this in src/uefi.zig
. And just like that we can boot into JavaScript!
Showcases
Linux, macOS, Windows
To prove my earlier point: of course Kiesel runs without issues on the latest versions of Linux, macOS, and Windows. It's using statically linked musl by default so it'll work on pretty much any distro. The REPL β a nod to CPython β includes OS-specific instructions for exiting it (EOF works differently on Windows, as everything does).
Thanks to Sarah for this screenshot!
Linux, But Weird
Still Linux but we're leaving desktop user territory.
Here's Kiesel running on my projector at home, which came with Linux 4.9 (Android 9) out of the box. Zig officially supports Linux β₯ 5.10 at the time of writing and may use syscalls that fail miserably on older versions as a result, but for the most part it works. If you ever get an error.Unexpected
back this is probably the reason.
Do you have a mainframe at home? If so, Kiesel for IBM Z may be what you need. I'm pretty sure I know people who this applies to, but for now I'm using a LinuxONE VM that IBM gave me for open source purposes :^)
To make sure Kiesel also works on less common big-endian systems (you're probably reading this on little-endian) famfo once tested it on ppc64.
Windows, But Ancient
Together Domi and I managed to run Kiesel on Windows NT 5.2, a.k.a. Windows Server 2003. I no longer have the patches but it involved finding DLLs online that backport APIs from newer Windows versions. Don't ask why it's running from the SeaMonkey installation directory.
Understandably Zig only wants to deal with Windows 10 and above nowadays so this is very much not supported.
Hey, we should do that again and document it <3
Thanks to Domi for this screenshot!
OpenBSD
Not too long ago one of the OpenBSD developers reached out:
Needed a newer Zig and a few tweaks, but I got it running. One more platform to add to the list :)
One of the tweaks turned out to be a bug in std.c
that I fixed upstream. Unfortunately it's not possible to cross-compile for OpenBSD as Zig can't provide a libc:
error: error: unable to find or provide libc for target 'x86_64-openbsd.7.3...7.6-none'
I might look into setting up a VM so I can at least check if the build still works once in a while :^)
Thanks to Tobias for this screenshot!
UEFI
The Zig standard library already wraps large parts of the UEFI API, so I had to try. And sure enough, it worked!
Thanks to CxByte who kindly added UEFI support to his zigline library you can now boot into a JavaScript REPL. I doubt this is the first time ever this exists but I personally haven't seen any other projects doing it before :^)
And of course I had to install it on bare metal too:
Nintendo 3DS
I bought a used Nintendo 3DS, put homebrew on it, and then promptly buried it in a drawer.
Luckily someone already did the work of gluing devkitPro and the Zig toolchain together: let Zig output an object file compiled for arm-freestanding-eabihf
, throw that into devkitPro's arm-none-eabi-gcc
to get an ELF file, and finally feed that to 3dsxtool
. It's not pretty but it works.
The first proof of concept was using a hardcoded snippet of JS, but over time I expanded it to use the software keyboard for an actual REPL, enabled pretty-printing, and ported more of the runtime. There are some crashes left to fix but nowadays it works almost as well as the regular CLI!
The 3DS even ships with JS out of the box (the built-in browser is based on WebKit), but I'm probably the first to use iterator helpers on it.
Your System Here?
π₯Ίππ
Thanks!
β¦to everyone who keeps expanding the Kiesel Enterprise Cinematic Universe (mostly Eloy tbh), Carter for continuing to work on GC things I don't fully understand, Domi for providing CI infra so I can build for all the platforms no one uses, and in particular Alex from the Zig team for the huge effort to improve target support even more!
Let's see what platforms we can run JavaScript on in the next year :^)
Also check out these posts from friends across the web!
-
TDOH-2025
2025-03-29 β famfo's place on the internet -
Time to break some habits
2024-11-05 β lnl's weblog -
Fediverse instances on weird hardware, networks and operating systems
2025-04-23 β Eloy's website -
JS engine code size
2025-05-21 β goose.icu