My penguin avatar

Kiesel Devlog #13: Human After All

Published on 2026-04-28.

Hi! It's been a while. Exactly one year to be precise. Don't worry, the project is alive and well, and so am I!

Unfortunately, the world around us is not. JS itself has always been the target of ridicule — in some communities more than others — so I'm used to just doing my own thing. But now that people are instructing GPUs to shit out entire JS engines and other people are cheering them on I mostly lost interest in sharing progress myself. No GitHub links to not throw anyone under the bus, but it's dire.

Old man yells at Claude, I suppose. More than ever I want to remind people that it's fine to build things just for fun, on your own terms, for free. Kiesel does not have a purpose beyond that. It's a combined technical and creative work made by a human, for no one.

Anyway, let's move on to the fun stuff!

A Year's Worth of Changes

608 commits (two of those from new contributors), 26,539 lines of code, and an 18.3% increase of the test262 score. Way too much to cover it all but we'll look at some of the highlights!

AgeLines Of CodeCommitsContributorstest262
162,7681435550.4%
283,6272106975.1%
3110,16627141193.4%

I'm a data nerd so of course there's a dashboard. Also, shout-out to curl for theirs.

Being in the 90th percentile of test262 is a clear sign that most things just work now, and I'm looking forward to push it a little further when implementing the Explicit Resource Management proposal. It's a moving target thanks to the never ending addition of new tests for proposals, but at the time of writing Kiesel is placed between Boa (95.5%) and Apple's JavaScriptCore (89.6%) according to test262.fyi.

New Bytecode Interpreter

By far the most exciting recent change is the rewrite of Kiesel's bytecode interpreter. This is part of a larger rewrite project which I pondered for a while, then wrote up in an issue, followed by doing absolutely nothing for nearly half a year. After several small prototypes I finally gained enough traction to commit (pun intended).

After hacking my way to feature parity for most of February I then quickly deleted the old VM. Good riddance!

Notable changes include:

Some things are not quite polished yet, for example register allocation, but it's a huge improvement over the old interpreter that I cobbled together in the early days without knowing what I was doing.

A major motivation was performance, with noticeable results across the entire v8-v7 benchmark suite:

Temporal

After nearly nine years, the Temporal proposal reached stage 4 at the March 2025 TC39 plenary! I had the pleasure of being there in person when Philip did the final presentation followed by lots of applause and cake. Check out Jason's post on the Bloomberg JS blog for a deep dive.

I first became familiar with Temporal in 2021 while implementing it in LibJS and then found myself implementing it again last year, this time in Kiesel. In the meantime Boa had turned their implementation into the excellent temporal_rs library which I ended up using, much like I chose icu4x to implement Intl.

This means that I only needed to write about 10,000 lines to implement the 250+ functions of the various Temporal objects, and most of the datetime logic is handled by temporal_rs. Zig makes interfacing with its C FFI super easy:

/// 7.3.12 get Temporal.Duration.prototype.nanoseconds
/// https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds
fn nanoseconds(agent: *Agent, this_value: Value, _: Arguments) Agent.Error!Value {
    // 1. Let duration be the this value.
    // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
    const duration = try this_value.requireInternalSlot(agent, Duration);

    // 3. Return 𝔽(duration.[[Nanoseconds]]).
    return Value.from(temporal_rs.c.temporal_rs_Duration_nanoseconds(duration.fields.inner));
}

With Duration being a Kiesel object wrapper around temporal_rs::Duration:

pub const Duration = MakeObject(.{
    .Fields = struct {
        inner: *temporal_rs.c.Duration,
    },
    .finalizer = struct {
        fn finalizer(object: *Object) void {
            temporal_rs.c.temporal_rs_Duration_destroy(object.as(Duration).fields.inner);
        }
    }.finalizer,
    .tag = .temporal_duration,
    .display_name = "Temporal.Duration",
});

Several other engines, including V8 and thus Chromium and Node.js, also implemented Temporal using temporal_rs. Props to the Boa team for building something that works for everyone!

Alpine Package

You can now apk add kiesel on Alpine edge, making it the first Linux distribution to package Kiesel! This was initially done by fossd with follow-up changes and occasional updates handled by myself.

There also was a PR for nixpkgs, but it was abandoned. If you'd like to package for your distro of choice feel free to reach out, I'm happy to help :^)

Zig 0.16

The long awaited Zig 0.16 release happened a few weeks ago and I promptly updated to it. Because ECMAScript does not specify any I/O there wasn't much exposure to the headline feature, but the countless other quality of life improvements are great as well.

I also went to a Zig Day for the first time in February, which was super fun!

Kiesel for Microsoft Windows Server 2003

I spent two evenings learning WinDbg and hacking up a 600 line patch for Zig's standard library to get this working again after the last Zig update. Just because we can.

Onto the Next Year!

Who knows what will happen, but some things I'm excited about:


P.S. My favorite Daft Punk album is Random Access Memories, but the title seemed fitting. :^)


Loading posts...