---
title: "Kiesel Devlog #15: Release 0.4.0"
slug: kiesel-devlog-15-release-0-4-0
url: https://listedarticles.com/articles/kiesel-devlog-15-release-0-4-0
canonical_url: https://linus.dev/posts/kiesel-devlog-15/
content_type: changelog
language: en
published_at: 2026-10-08T00:00:00.000Z
updated_at: 2026-10-10T20:07:10.706Z
author: "Linus Groh"
authored_by: human
publisher: "linus.dev"
publisher_url: https://linus.dev/
topics: ["JavaScript", "Zig", "Programming Languages"]
license: all-rights-reserved
word_count: 648
reading_minutes: 3
citation: "Linus Groh, linus.dev. \"Kiesel Devlog #15: Release 0.4.0.\" 8 Oct 2026. https://linus.dev/posts/kiesel-devlog-15/ (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Kiesel Devlog #15: Release 0.4.0

> Linus Groh releases Kiesel 0.4.0, his JavaScript engine written in Zig, after updating to Zig 0.17: new Kiesel.args and Kiesel.env APIs, more iterator helpers, a SerenityOS port, and inline caches for property access that give 20-30% microbenchmark speedups, with a faster parser planned for 0.5.0.

# Kiesel Devlog #15: Release 0.4.0

Published on 2026-10-08.

---

This week I completed the update to [Zig 0.17](https://ziglang.org/download/0.17.0/release-notes.html) and released Kiesel 0.4.0! It contains 64 commits across 2.5 months, see the [changelog](https://codeberg.org/kiesel-js/kiesel/src/branch/main/CHANGELOG.md#0-4-0-2026-10-07) for the full list of user-facing changes.

## `Kiesel.args` / `Kiesel.env`

I mostly *build* rather than *use* this project, and it shows — beyond the core language implementation only a few APIs needed for real-world programs are available. We did have file I/O for a while, and with the addition of arguments and environment reading a simple cat(1) clone becomes feasible:

```
#!/usr/bin/env kiesel

const [_exe, _script, ...files] = Kiesel.args;

if (files.length === 0) {
  const contents = Kiesel.readStdin();
  Kiesel.print(contents, { newline: false });
} else {
  for (const file of files) {
    const contents = Kiesel.readFile(file);
    Kiesel.print(contents, { newline: false });
  }
}
```

```
$ ./cat.js cat.js
```

## Even More Iterator Helpers

Over the last few years JS has rapidly advanced the usability of iterators from just providing the basic [protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) at the language level to a large collection of `Iterator.prototype` methods. The initial [*Iterator Helpers*](https://github.com/tc39/proposal-iterator-helpers) proposal shipped in 2024, followed by [*Iterator Sequencing*](https://github.com/tc39/proposal-iterator-sequencing) in 2025, and [*Joint Iteration*](https://github.com/tc39/proposal-joint-iteration), [*Iterator Join*](https://github.com/tc39/proposal-iterator-join), [*Iterator Includes*](https://github.com/tc39/proposal-iterator-includes), and [*Iterator Chunking*](https://github.com/tc39/proposal-iterator-chunking) all this year. Phew!

The last three are newly implemented:

```
function* count() {
  for (let i = 1; ; ++i) yield i;
}

console.log(count().take(3).join(" + "));
console.log(count().take(10).includes(5));
console.log(JSON.stringify(count().chunks(3).take(3).toArray()));
```

```
$ kiesel main.js
1 + 2 + 3
true
[[1,2,3],[4,5,6],[7,8,9]]
```

I also implemented the [*Await Dictionary*](https://github.com/tc39/proposal-await-dictionary) proposal, which is still at Stage 3.

## SerenityOS Port

I've done a number of fun ports over the years but this might be my favorite one yet! It's an intersection of most of my notable open source work in the past six years: SerenityOS, the Zig project, and Kiesel itself.

![Kiesel REPL on SerenityOS](/images/posts/kiesel-devlog-15/serenity.png)

This required gradually upstreaming SerenityOS target support in Zig ([#20913](https://github.com/ziglang/zig/pull/20913), [#23192](https://github.com/ziglang/zig/pull/23192), [#23198](https://github.com/ziglang/zig/pull/23198), [#24561](https://github.com/ziglang/zig/pull/24561), [#24633](https://github.com/ziglang/zig/pull/24633), [#25457](https://github.com/ziglang/zig/pull/25457), [#25779](https://github.com/ziglang/zig/pull/25779), [#30756](https://codeberg.org/ziglang/zig/pulls/30756), [#31916](https://codeberg.org/ziglang/zig/pulls/31916), [#31931](https://codeberg.org/ziglang/zig/pulls/31931), [#32172](https://codeberg.org/ziglang/zig/pulls/32172), [#36465](https://codeberg.org/ziglang/zig/pulls/36465), [#37064](https://codeberg.org/ziglang/zig/pulls/37064), [#37065](https://codeberg.org/ziglang/zig/pulls/37065)), a handful of SerenityOS LibC and Kernel changes ([#25804](https://github.com/SerenityOS/serenity/pull/25804), [#26350](https://github.com/SerenityOS/serenity/pull/26350), [#26543](https://github.com/SerenityOS/serenity/pull/26543), [#26928](https://github.com/SerenityOS/serenity/pull/26928)), and the package itself ([#26931](https://github.com/SerenityOS/serenity/pull/26931)), which is cross-compiled against a now minimally patched Zig standard library. I hope to have those remaining bits upstreamed by the time 0.18 is released :^)

Other notable mentions: the long-anticipated [DOS port](https://donotsta.re/notice/BAALcuwQHIrmMin9OK) finally became reality thanks to [dos.zig](https://github.com/jayschwa/dos.zig) being revived, and OpenBSD/NetBSD binaries are now built in CI.

## Inline Caches

Inline caches are one of the fundamental optimizations used by virtually all JS engines (and various other dynamic programming languages). I recommend [this blog post](https://mathiasbynens.be/notes/shapes-ics) if you want to learn more about how they work.

Kiesel had ICs for a long time, but the [bytecode interpreter rewrite](../kiesel-devlog-13/#new-bytecode-interpreter) earlier this year left it with only a basic implementation of monomorphic property ICs. I've now fixed those gaps ([#220](https://codeberg.org/kiesel-js/kiesel/pulls/220), [#221](https://codeberg.org/kiesel-js/kiesel/pulls/221), [#222](https://codeberg.org/kiesel-js/kiesel/pulls/222)):

* Polymorphic property access (up to 4 before becoming megamorphic)
* Computed property access
* Prototype lookups

The output is rather cryptic, but you can see them in the emitted bytecode:

```
$ kiesel --print-bytecode -c 'o = {}; o.foo = 1; o.foo'
Bytecode (command)
   0: 12 00 00 00 00 00 00             create_object r0, 0
   7: 4e 00 00 00 00 00 00             set_binding @0 ("o"), r0
  14: 49 00 00 00 00 00 00             get_binding r0, @0 ("o")
  21: 07 01 00 01                      load_number_i8 r1, 1
  25: 50 00 00 01 00 01 00 00 00 00 00 set_property r0, r1, @1 ("foo"), @0
  36: 49 00 00 00 00 00 00             get_binding r0, @0 ("o")
  43: 4a 01 00 00 00 01 00 00 00 00 00 get_property r1, r0, @1 ("foo"), @0
  54: 78 01 00                         return r1
```

One (`@0`) `set_property` IC and one (`@0`) `get_property` IC. (They're indexed by type, not shared.)

The [implementation](https://codeberg.org/kiesel-js/kiesel/src/branch/main/src/interpreter/Vm/ic.zig) is only a few hundred lines for all of them, but consistently provides speedups of 20-30% in microbenchmarks.

## Plans for 0.5.0

The parser is about to get fast, stay tuned 👀
