My penguin avatar

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:

AgeLines Of CodeCommitsContributorstest262
1627681435550.4%
2836272106975.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:

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).

Kiesel REPL running on Linux Kiesel REPL running on macOS Kiesel REPL running on Windows

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.

Kiesel REPL running on Linux (arm)

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 :^)

Kiesel REPL running on Linux (s390x)

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.

Kiesel REPL running on Linux (powerpc64)

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

Kiesel REPL running on Windows NT 5.2

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 :^)

Kiesel REPL running on OpenBSD 7.6

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 :^)

Kiesel REPL running on UEFI in QEMU

And of course I had to install it on bare metal too:

Kiesel REPL running on UEFI on a Thinkpad

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.

Kiesel REPL running on a Nintendo 3DS

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!