<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>null program</title>
  <link rel="alternate" type="text/html" href="https://nullprogram.com"/>
  <link rel="self" type="application/atom+xml" href="https://nullprogram.com/feed/"/>
  <updated>2026-10-17T21:36:09Z</updated>
  <id>urn:uuid:f8b65823-4ec5-3a70-efc8-2b713aa63091</id>

  <author>
    <name>Christopher Wellons</name>
    <uri>https://nullprogram.com/</uri>
    <email>wellons@nullprogram.com</email>
  </author>

  <entry>
    <title>tugz: a new deflate library with three applications</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/10/13/"/>
    <id>urn:uuid:21156575-515c-862e-9b0f-8a86d5b229fc</id>
    <updated>2026-10-13T22:32:24Z</updated>
    
    <content type="html">
      <![CDATA[<p>I spent a week creating a new <a href="https://en.wikipedia.org/wiki/Deflate">deflate</a> implementation from scratch in C
named <strong><a href="https://github.com/skeeto/tugz">tugz</a></strong> to provide an <a href="https://infozip.sourceforge.net/">Info-ZIP</a>-compatible <code>zip</code> program for
<a href="https://github.com/skeeto/w64devkit">w64devkit</a> 2.10.1 (released today). I also replaced <a href="https://frippery.org/busybox/">busybox-w32</a>’s
deflate-based <code>unzip</code>, <code>gzip</code>, <code>gunzip</code>, and <code>zcat</code> apps as well. My new
deflate performs much better than <a href="https://zlib.net/">zlib</a> and on par with <a href="https://github.com/ebiggers/libdeflate">libdeflate</a>.
In general the new programs are substantially faster and produce slightly
smaller outputs than the BusyBox equivalents, which is optimized for size
than speed after all, but also compared to the original gzip and Info-ZIP
you get in a typical Linux distribution. The tugz programs are
<a href="/blog/2023/02/15/">CRT-free</a> (inspect them with <a href="/blog/2024/06/30/">peports</a>!) with first-class Windows
support and better Unicode handling.</p>

<p><img src="/img/tugz.png" alt="" /></p>

<p>Besides the cute logo, tugz is public domain and written entirely in my
personal style with unity builds, <a href="/blog/2025/01/19/">arena allocation</a>, platforms layers
with minimal interfacing (read, write, open, etc.), and extensive <a href="/blog/2025/02/05/">fuzz
testing</a>. The <code>zip</code> implementation carefully avoids a Windows <a href="https://github.com/ebiggers/libdeflate/issues/323">deflate
bug</a> that has personally bitten me. Its CMake build drives the more
complex machinery (tests, etc.), but it’s optional for straightforward
builds. Just compile the platform layers. For instance, in w64devkit:</p>

<pre class="highlight"><code>$ cc -nostartfiles -O2 -o  zip  platform/zip_windows.c    -lmemory
$ cc -nostartfiles -O2 -o gzip  platform/gzip_windows.c   -lmemory
$ cc -nostartfiles -O2 -o unzip platform/unzip_windows.c  -lmemory
$ cc -shared -nostdlib -O2 -o tugz.dll platform/libtugz.c -lmemory
</code></pre>

<p><code>gunzip</code> and <code>zcat</code> are aliases/symlinks for <code>gzip</code>. <code>tugz.h</code> defines the
library interface, featuring an <a href="/blog/2023/12/17/">allocator callback</a>. I’m not currently
using the library in any projects, nor is it included in w64devkit, but it
might come in handy someday. The applications use the internal interfaces.</p>

<p>tugz <code>unzip</code> supports Deflate64 decompression, an extension to deflate
with a larger sliding window. Windows built-in ZIP support uses it for ZIP
files larger than 2GiB, to little practical benefit. BusyBox <code>unzip</code> does
not support Deflate64 and so cannot unzip some Windows-made ZIP files. The
replacement will work better in these cases.</p>

<p>Unlike <a href="https://github.com/skeeto/u-config/blob/master/src/linux_noarch.c">u-config</a>, I did not write Linux syscall platform layers this
time, but it would not be difficult to do so. Then such builds would truly
work in any Linux distribution, new or old. I leave it as a fun exercise
for the reader.</p>

<p>The original gzip and Info-ZIP are old, and like so many tools from that
era they include line conversion features. When you decompress files you
can translate the line endings at the same time in one tool. Over the
years, archaic line-ending features have caused far more harm than good
(<em>arg</em>… Git <code>core.autocrlf</code>), so I deliberately omit them. Using these
flags is an error.</p>

<p>Similarly, ZIP encryption features are deliberately left unimplemented.
Attempting to use encryption is an error.</p>

<p>One of my hard and fast rules was full support for Windows XP. This wasn’t
automatic, but nor was it difficult. tugz needs several <code>*ByHandle{Ex,}W</code>
functions from Win32 that are unavailable in XP. When they’re unavailable,
it falls back to equivalent Nt functions via GetProcAddress. I also made a
<a href="https://github.com/skeeto/sshd">lightwight SSH server</a> that is trivial to build and run on Windows
XP, allowing coding agents to automatically test and verify tugz on my old
systems. It’s quite fascinating to watch it take control of my old laptop,
and it’s already proving useful in other projects, including w64devkit.</p>

<p><strong>Update</strong>: After <a href="https://github.com/skeeto/tugz/issues/1">a request for <code>zipinfo</code> / <code>unzip -Z</code></a>, tugz
nos has this utility as well. It will appear in the next w64devkit.</p>

<h3 id="usage-in-w64devkit">Usage in w64devkit</h3>

<p>What does it mean for users? You can invoke <code>zip</code> in your build scripts or
whatever to package releases in a Windows-friendly ZIP file. Before today
the only option in w64devkit was going through <a href="https://cmake.org/cmake/help/latest/module/CPack.html">CPack</a>. A <a href="/blog/2017/08/20/">Makefile</a>,
perhaps:</p>

<pre class="highlight"><code>myapp-$VERSION.zip: myapp.exe README.md
    rm -f $@
    zip -X9 $@ $^
</code></pre>

<p>The <code>rm</code> because tugz follows the Info-ZIP behaviors of update-in-place.
The output is deterministic, particularly when given <code>-X</code> (omit extended
attributes) so that your releases are stable. The <code>-9</code> requests maximum
compression. Or maybe after you’ve <a href="/blog/2026/10/04/">generated my blog</a>, recursively
zip it up for archival:</p>

<pre class="highlight"><code>$ ssg
$ zip -r nullprogram.zip _site/
</code></pre>

<p>This is another project I’d been considering for a couple years now. It
turned out better than I expected, and I’m quite excited about it. In my
eyes it represents a kind of ideal project, and I wish more projects were
shaped like this.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>A new, bespoke static site generator to replace Jekyll</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/10/04/"/>
    <id>urn:uuid:bee8d4c0-5185-8d4b-b14a-4aba71276e87</id>
    <updated>2026-10-04T16:35:49Z</updated>
    <category term="c"/><category term="cpp"/><category term="meta"/><category term="ai"/>
    <content type="html">
      <![CDATA[<p>My blog began as a <a href="https://www.blosxom.com/">blosxom</a> (Perl) site running on a VPS. In 2011 I
<a href="/blog/2011/08/05/">moved to the new GitHub Pages</a>, with the site generated statically
by <a href="https://jekyllrb.com/">Jekyll</a> (Ruby) on a GitHub server. It was a no-brainer: easier,
faster, and cheaper, better in every way. After 15 years of Jekyll, and
spurred by <a href="https://napcakes.nekoweb.org/posts/m4-is-nice/">a post on m4</a>, this week I replaced it with <a href="https://github.com/skeeto/skeeto.github.com/tree/master/_ssg">a new,
custom-built static site generator</a>, dubbed <em>ssg</em>, in “C with
templates” C++20. The ~8KLoC source <a href="/blog/2025/01/19/">closely follows my personal coding
style</a> including <a href="/blog/2024/04/14/">templated arenas and slices</a>, zero
dependencies, and a libc-free core. It’s wicked fast, and a complete, cold
generation of my blog takes 150ms on my MacBook. That is, <em>it’s done
before Ruby would even reach Jekyll’s entry point</em>. It’s a been a great,
real-world demonstration of the effectiveness of my coding philosophy.</p>

<p>Look around and you’ll find almost nothing visually changed. Outside of
syntax highlighting, the HTML is semantically identical. I did not try to
match Rouge’s syntax highlighting, just its CSS selectors so that I could
use my style sheet unmodified. With that in my own hands, I now get syntax
highlighting that better suits my needs, some Rouge bugs gone and support
for new languages, particularly assembly (<a href="/blog/2025/04/04/">WAT</a>, <a href="/blog/2024/02/05/">AT&amp;T</a>, <a href="/blog/2022/02/18/">Aarch64</a>)
and <a href="/blog/2020/11/17/">QBasic</a>.</p>

<p>Because ssg only supports my site, and its source lives along side it, I
don’t need a template system, i.e. Liquid. I can modify the C++ source as
needed. So the upgrade was three steps: (1) fix 19 years of accumulated
site bugs that Jekyll quietly tolerated, (2) add the new generator, (3)
delete Liquid templates and pre-generated tag pages (now dynamically
generated, with preservation of original feed UUIDs). Jekyll and ssg both
work simultaneously at step #2, allowing side-by-side comparisons from the
same site source. This is where most of the time was spent. Dropping
templates means performance comparisons with Jekyll are unfair, as Jekyll
is solving a different problem. (But I think it’s fair to speculate that
ssg with Liquid templates would still smoke Jekyll!)</p>

<p>Historically, Jekyll was a dreadfully slow site generator until <a href="https://jekyllrb.com/news/2019/08/20/jekyll-4-0-0-released/">the 4.0.0
release in August 2019</a>. While performance is mostly resolved, I still
have ingrained habits to work around it. The final Jekyll version of my
site took ~2 seconds on the same MacBook. Not too bad, and it has a fast
<code>--incremental</code> mode, though it’s always been a little buggy, not quite
matching a full generation.</p>

<p><strong>No, the pain point for Jekyll is not performance but <em>deployment</em></strong>.
Especially when trying to match the GitHub Pages configuration. The Ruby
deployment situation remains just awful. I never once generated my blog on
Windows, in part to avoid jumping through hoops to set up Jekyll. Last
year I switched to macOS (from Linux) as my primary, personal development
environment. This meant setting up Jekyll on macOS, and <a href="https://jekyllrb.com/docs/installation/macos/">the situation is
farcical</a>. The Ruby community ought to feel embarrassed about this.
The <code>source</code> lines delay shell startup by 60ms, and I’m unwilling to bear
that cost in nearly every shell just to occasionally run Jekyll, plus
environment litter that may interfere with other tools. So I needed to
remember to enter an isolated Jekyll environment, and to tell AIs to use
if they needed Jekyll.</p>

<p>With ssg you just need a C++ compiler. As native application, you don’t
need any development tools once it’s built of course, and the compiled
program is a single file that could be copied to another system and run
as-is. It’s a unity build — I did say it closely follows my personal style
— so you don’t even need a build system. On <a href="https://github.com/skeeto/w64devkit">w64devkit</a> that looks like:</p>

<pre class="highlight"><code>$ cc -nostartfiles -o _ssg/ssg.exe _ssg/main_win32.cpp
</code></pre>

<p><code>cc</code> (or <code>gcc</code>) works because the compiler driver knows to invoke the C++
front-end for this input. It doesn’t use the C++ standard library, so the
linker doesn’t need <code>-lstdc++</code>. <code>-O0</code> builds like this run at about half
speed compared to optimized builds, and <code>-O1</code> is sufficient to get all the
compiler optimization benefits. Normally debug builds would be around 10x
slower, but fast debug builds is par for the course for my style. While
you don’t need it, there <em>is</em> a CMake build, but that’s really just for
driving the test suite.</p>

<p>Both build and tool work on Windows XP, too, so for the first time ever I
could <a href="/blog/2025/03/02/">fully compose a post on my old XP laptop</a> if I wished.</p>

<p>On the MacBook this <code>-O0</code> build takes ~150ms — quite good for 8,372 lines
of code. A cold site generation takes this build ~260ms. That’s so fast I
could very well re-compile the generator each time I regenerate the site.
Indeed that’s exactly how it works in the GitHub Actions pipeline. Overall
it’s faster to use a debug build than a release build. The cache is too
slow and unreliable for release builds to make sense in Actions.</p>

<pre class="highlight"><code>$ cc -std=c++20 -o _ssg/ssg _ssg/main_posix.cpp  # macOS
</code></pre>

<p>For the first decade of GitHub Pages, Jekyll was the only option. It ran
opaquely in the somewhere at GitHub with a pass/fail result, requiring a
local matching configuration for debugging. When they introduced GitHub
Actions in 2018, Jekyll was a pre-configured, transparent pipeline, and it
became possible to run whatever generator you wanted in its place. I’m
finally taking advantage of that, and I still get automatic page builds on
push like I had with Jekyll. It’s now slightly faster — Actions overhead
dominates either way — despite compiling an entire C++ program each run.</p>

<p>I do not plan to divorce my generator from my blog, so ssg will not become
a general-purpose static site generator. Its whole purpose is to serve
this one particular need. You’re free to fork it and use it as a basis for
your own needs, of course. This sort of bespoke, written-to-order software
is likely the future of software. Why bother with one-site-fits-all when
an exactly-sized solution has the same cost?</p>

<h3 id="a-first-for-generative-ai">A first for generative AI</h3>

<p>I’ve been thinking about this project for years. Had I written it in the
2023–2025 time frame, I estimate ~2–4 weeks, using <a href="/blog/2023/01/18/">u-config</a> (3KLoC,
lower complexity) as a measuring stick. Here in <a href="/blog/2026/03/29/">2026</a>, it took about
~20 minutes to write my detailed prompt, then ~2 hours for Opus 5.5 in
Claude Desktop to produce ssg in essentially its current form, bug-free as
far as I can tell, including an <em>untested</em> Win32 platform layer (written
on the MacBook, no Wine). I’ve spent more time on this article than I did
on the new generator.</p>

<p>I spent a couple more hours looking it over, shocked at Opus 5.5 perfectly
matching my personal style. It really does look like I wrote ssg. Reading
it is uncanny, like seeing someone reproduce my own handwriting such that
I couldn’t distinguish it. During this review I realized we ought to use
debug builds in the pipeline, so we had <a href="https://github.com/skeeto/skeeto.github.com/commit/2c9aa4e9">one follow-up on the hot spot in
debug builds</a>. Then I noticed the Win32 platform layer generated an
empty site on Windows XP, <a href="https://github.com/skeeto/skeeto.github.com/commit/35228acf">another small followup</a>. (XP wasn’t in
my original prompt, so I don’t count this as a bug.) That was it.</p>

<p>Two years ago <a href="/blog/2024/11/10/">I said</a> that AI-generated code was too poor to be
practical. That changed by the end of 2025. In earlier 2026 projects I
tried to get AI to write C following my style, using my writing on the
subject as a guide, but they’d just recreate the <a href="/blog/2023/02/11/">no-good standard
library</a> from scratch then flub arena allocation. Ask those models
to write conventional C and you get the usual, error-prone C you’ll find
anywhere. So I settled on conventional C++ as Good Enough. I could
complete projects ~20x faster, but with results not quite as good as I’d
have done myself. A reasonable trade-off.</p>

<p>Opus 5.5 released on September 22nd. It is the first model I’ve used that
actually understands my coding philosophy and <strong>writes C at least as well
as me</strong>. This was the ideal project for this test, as my core writing on
this subject was naturally in context, but it generalizes when referencing
my writing and prior work. As of two weeks ago I can now have my cake and
eat it, too. No more trade-offs.</p>

<p>If you’d like to get similar C-with-templates results on your own project,
cite these four articles in your prompt:</p>

<ul>
  <li><a href="https://nullprogram.com/blog/2025/01/19/">https://nullprogram.com/blog/2025/01/19/</a></li>
  <li><a href="https://nullprogram.com/blog/2024/04/14/">https://nullprogram.com/blog/2024/04/14/</a></li>
  <li><a href="https://nullprogram.com/blog/2025/09/30/">https://nullprogram.com/blog/2025/09/30/</a></li>
  <li><a href="https://nullprogram.com/blog/2025/10/16/">https://nullprogram.com/blog/2025/10/16/</a></li>
</ul>

<p>As well as perhaps my relevant, similar projects. All the frontier models
know about me personally and are familiar with my work, so invoking my
name may be enough. If the model is as good as Opus 5.5, you might get an
accurate impression of how I would have done your project.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>What's been going on in w64devkit the past year</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/09/20/"/>
    <id>urn:uuid:843e3ff4-8bd3-4106-bca9-608a80e66545</id>
    <updated>2026-09-20T01:49:36Z</updated>
    <category term="c"/><category term="cpp"/>
    <content type="html">
      <![CDATA[<p>The past year has been exciting for <a href="https://github.com/skeeto/w64devkit">w64devkit</a>, which like any software
distribution is never complete. <a href="https://peter0x44.github.io/">Peter0x44</a> joined as co-maintainer, and
has pushed the project in good, new directions, with ideas I would never
have considered. Many of his improvements have gone back upstream, and so
you may benefit even if you don’t use w64devkit. I’d like to touch on the
various odds and ends from the past year.</p>

<h3 id="release-security">Release security</h3>

<p>In April I <a href="/blog/2026/04/25/">announced that release packaging is now signed</a>. Today
<em>all</em> EXEs and DLLS in w64devkit are code-signed with my key. My signing
key established a good reputation thanks to thousands of unique signatures
observed on the order of ~100,000 hosts — a pleasant side effect from
including ~300 binaries in a release. Users should have fewer problems
these days with security software. MSYS2 has also <a href="https://www.msys2.org/dev/code-signing/">adopted my signing
tool</a>, <code>aas-sign</code>, which is now included in w64devkit releases.</p>

<p>Builds are now automated by GitHub Actions, triggered when I (and <em>only</em>
I) push a new tag. That process code-signs and creates the release. Every
step of the release process is transparent, derived strictly from source
in the repository. Nowhere does it go behind a curtain and permit secret
tampering.</p>

<p>But that’s not all. I enabled <em>release immutability</em>: On publish, release
artifacts are locked in and nobody, not even me, can modify them. You can
tell by the presence of a release attestation at the end of the artifacts
listing. Nobody involved in the project down the road can go rogue and
sneak something into an old release.</p>

<h3 id="toolchain-changes">Toolchain changes</h3>

<p>The x64 release is now a “multilib” toolchain. That is it can compile
programs for 32-bit Windows, like a superset of the x86 release. The only
purpose of the x86 release is to run w64devkit on older hardware or older
operating systems. Like the x86 release, it targets Windows XP by default
and requires a CPU supporting SSE2 (i.e. at least Pentium 4).</p>

<p>To target x86, pass <code>-m32</code> when compiling and linking. Or better, use
tools prefixed with the <code>i686-w64-mingw32</code> architecture triple. The latter
is easier in general because different tools require different switches,
and the prefixed tools are <a href="/blog/2021/02/08/">aliases</a> that automatically do the right
thing.</p>

<pre class="highlight"><code>$ x86_64-w64-mingw32-gcc -o hello64.exe hello.c
$ i686-w64-mingw32-gcc -o hello32.exe hello.c
</code></pre>

<p>Adding multilib was cheaper and easier than I anticipated, and it’s thanks
to Peter0x44. It’s already been convenient for me for several projects.</p>

<p>In the past you needed w64devkit’s <code>bin/</code> on your <code>$PATH</code> in order to use
the compiler, because that’s how GCC found the other tools it needs. This
is no longer the case, and you can invoke <code>path/to/gcc.exe</code> from scripts
and such without touching your path. This required a small GCC patch.</p>

<p>The standard COFF object format supports up to 65,535 sections. When the
format was designed this probably seemed like more than enough, but modern
C++ programs can easily exceed it, particularly debug builds of programs
using lots of lambda functions. As C++ projects grow they eventually hit a
threshold where builds fail unless you ask for the <a href="https://peter0x44.github.io/posts/bigobj_format_explained/">bigobj</a> COFF format,
which supports over 4 billion sections. This is annoying, and the tools
ought to deal with this automatically.</p>

<p>A couple years ago I patched Bintutils to produce bigobj by default so the
old limit would never affect a build. The catch is that Binutils lacks an
interface to request standard COFF, so you can’t downgrade if needed. Why
would you need to? When interacting some simpler linkers like the official
Go toolchain (gc)! Some linkers can’t consume bigobj. It also breaks some
builds that auto-detect the toolchain’s object format, as some detection
scripts don’t know about bigobj (ex. libbacktrace at the time).</p>

<p>Ideally Binutils would quietly upgrade to bigobj as needed. Only large
programs see bigobj. Naive linkers and detection scripts only deal with
standard COFF. Well, thanks to Peter0x44 that’s now what Bintutils does
upstream! My bigobj hack is no longer necessary, and cgo works again.</p>

<p>This past year I’ve decided to put less emphasis on making w64devkit as
small as possible, and I’m willing to increase the installation and
distribution size for worthwhile features. This is embodied by switching
from <code>-Os</code> to <code>-O2</code> for static runtimes and tools that are computationally
expensive. Everything is now a bit bigger and a bit faster. Programs that
spend significant time in the standard library will be a little faster,
too. Programs already designed for performance will see no difference.</p>

<h3 id="new-tools">New tools</h3>

<p>CMake, Ninja, and <a href="/blog/2026/04/07/">a new, graphical CMake debugger</a> are all now
included. They require at least Windows 7. I’ve patched CMake to default
to Ninja, as CMake’s default is to look for a Visual Studio installation.
You can still <code>-G MinGW Makefiles</code> for GNU Make instead of Ninja, but I
don’t recommend it. Ninja is faster and more robust.</p>

<pre class="highlight"><code>$ cmake -B build       # configure for Ninja
$ cmake --build build  # build with Ninja
</code></pre>

<p><code>ccmake</code> is included (a Peter0x44 suggestion), a TUI front-end to examine
and modify CMake configurations. It’s been useful, more than I expected.
You can pass <code>-j</code> for parallel builds, but I strongly recommend using the
environment instead, e.g. in your <code>.profile</code>:</p>

<pre class="highlight"><code>export CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)"
export CTEST_PARALLEL_LEVEL=$(nproc)
</code></pre>

<p>Yes, parallel testing, too! I also recommend multi-config if that makes
sense for you:</p>

<pre class="highlight"><code>$ cmake -B build -G 'Ninja Multi-Config'
$ cmake --build build --config Debug
$ cmake --build build --config Release
</code></pre>

<p>I was tempted to make this default, but it changes the build tree layout
(<code>Debug/</code> and <code>Release/</code> directories) and a shocking percentage of real
world <code>CMakeLists.txt</code> break under multi-config because they’re written
incorrectly. The world needs a good CMake linter, which could catch most
cases statically.</p>

<p>Complementing is Ccache, with special patches from Peter0x44 to improve
Windows support. If you switch between branches often then using <code>ccache</code>
will speed up your (re-)builds. I also set up the conventional Ccache
<code>lib/ccache/</code> directory. It’s a directory in w64devkit that when added
your <code>$PATH</code> transparently backs all your builds with Ccache. On a typical
Linux distribution this would be <code>/usr/lib/ccache/</code>. You can enabled it in
your <code>w64devkit.ini</code> using <code>path type</code>, too:</p>

<pre class="highlight"><code>path type = minimal+ccache
</code></pre>

<p>In my case Ccache hasn’t been useful as I hoped when I added it. I’ve
adopted Git worktrees workflow instead, so each branch sits still with its
own build tree(s).</p>

<p>To aid COM programming, the kit now includes <code>widl</code> and <code>uuidgen</code>, the
open source alternative to Microsoft MIDL, for generating Interface
Definition Language (IDL) files. <code>uuidgen</code> is a <a href="https://github.com/skeeto/w64devkit/blob/master/src/uuidgen.c">minimalist rewrite by
yours truly</a> of <a href="https://learn.microsoft.com/en-us/windows/win32/rpc/generating-interface-uuids">the Microsoft tool</a>.</p>

<p><a href="https://github.com/skeeto/w64devkit/pull/392">G. Berthiaume wrote</a> a new tool, <code>make2compdb</code>, that
<a href="https://github.com/skeeto/w64devkit/issues/251">extracts</a> JSON Compilation Database files, <code>compile_commands.json</code>,
from Make builds. It operates as a unix filter:</p>

<pre class="highlight"><code>$ make -Bwn | make2compdb &gt;compile_commands.json
</code></pre>

<p>I wrote a <code>recycle</code> tool that sends files and folders to the recycle bin,
which for the latter is usually faster than deleting them with <code>rm -rf</code>.
The <code>trash</code> tool on other systems. I rarely delete files anymore, instead
using this command to send them off to sit in the recycle bin for a couple
weeks until Storage Sense automatically deletes them.</p>

<p>A <a href="https://nullprogram.com/blog/2026/03/29/#quiltcpp">few months ago</a> I announced the addition of <code>quilt</code>, an old
school patch management tool. The original doesn’t support Windows, so
this is a <a href="https://github.com/skeeto/quilt.cpp">full rewrite in C++</a>. Now that every platform has
Quilt, w64devkit’s patches are now managed with Quilt.</p>

<p>NSIS, an installer creator, is now included, so you can build your own
application installers. Just the command line tool, <code>makensis</code>. It’s also
in a “multilib” configuration, and x64 w64devkit can produce 32-bit and
64-bit installers. (My motivation to add NSIS was my <a href="https://github.com/skeeto/dumb-alt-tab">alternative alt-tab
switcher</a>, which requires an installer to install properly.)</p>

<p>Zstandard <code>zstd</code>/<code>unzstd</code> is now included because source tarballs are
often in <code>.tar.zst</code> format. It’s a great compression format, and ought to
be your default (rather than gzip) when you need compression. busybox-w32
<code>tar</code> knows to use <code>zstd</code> for <code>.tar.zst</code>.</p>

<h3 id="runtimes">Runtimes</h3>

<p>Unique to w64devkit, C11 threads are now part of the Mingw-w64 runtime. A
new implementation by yours truly, independent of winpthreads, smaller
than winpthreads, and requires no special compiler or linker flags. (If
you’re careful, you can even use it in <a href="/blog/2023/02/15/">CRT-free programs</a>.) The
catch is that it requires Windows 7 or later because I built it on <a href="/blog/2024/10/03/">SRW
locks</a>; XP would have required substantial complexity.</p>

<p>Writing an implementation taught me that <em>C11 threads are underspecified
poorly designed</em> — in case it wasn’t already obvious by the presence of
<a href="https://blog.stephencleary.com/2013/04/recursive-re-entrant-locks.html">recursive locks</a>! This C standard addition did not get nearly the
attention is needed and should have been cut (<a href="/blog/2023/02/11/">par for the course</a>).
My implementation excludes recursive locks. Trying to create one always
fails. It also has a weakened <code>thrd_current</code> because the semantics don’t
map onto the Windows threading model.</p>

<p><code>std::terminate</code> actually terminates (traps) instead of calling <code>exit</code>, so
it traps in GDB for inspection. This includes uncaught exceptions. It also
no longer prints a half-baked stack trace, shedding ~100k of dead weight
from most C++ programs. I did this in two steps a year apart, the second
just recently. I misplaced the trap in the first change, preventing it
from shedding as much weight as it could.</p>

<h3 id="future-directions">Future directions</h3>

<p>Years ago I disabled Link-Time Optimization (LTO) due to bugs in GCC and
Binutils. Just having it available in the toolchain triggered LTO bugs.
These problems reduced my faith that LTO could produce correct programs,
so I disabled it.</p>

<p>However, I’m toying with the idea of not only re-enabling LTO, but even
distributing “FatLTO” runtimes, particularly for C++ and Fortran. That is,
runtime object files will contain both native code (at <code>-O2</code> per above)
and LTO bytecode. If you don’t request LTO, you get the pre-compiled
native code just like today, at no link-time cost. If you enable LTO, it
essentially rebuilds the runtime itself at link time. Very computationally
expensive, but you get full control, and you can even coerce it back to
the old <code>-Os</code> (or even <code>-Oz</code>) if you prefer that.</p>

<p>As far as I can tell, nobody actually distributes a toolchain with FatLTO
runtimes, so w64devkit might be the first. I know I’m the first to cross
compile FatLTO objects with GCC — remember that everything in w64devkit is
cross-compiled — because it’s never worked correctly in a GCC release. My
first attempt produced a toolchain that didn’t work.</p>

<p>I’m feeling more confident this time around because fixing LTO bugs is now
very easy: Strap a frontier AI into a good coding harness, show it what’s
broken, give it all the sources (<code>source.tar</code>), and ~15 minutes later I
have a patch. We live in a sci-fi world. I started by fixing known-to-me
LTO bugs, enabled FatLTO, then worked through newly-discovered LTO bugs.
I’ve been dogfooding it, and nothing new has popped up in awhile, but it
will take some time for me to feel confident. I’m less confident in my
ability to discover Fortran runtime bugs. Upstream GCC <a href="https://gcc.gnu.org/ai-policy.html">rejects these
fixes out of hand</a>, so through no choice of my own w64devkit will have
a unique edge in this space.</p>

<p>While some of the new features require at least Windows 7, I’m still
committed to supporting Windows XP as a baseline for the x86 release. I
keep an old XP laptop on hand, on which I test and enjoy w64devkit. You
just have to make do with some older tools, but that’s what you were
already doing anyway.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>A custom virtual machine for the Stars! 4X game</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/09/17/"/>
    <id>urn:uuid:0b80f9d8-07a1-4d9f-a26d-f119b08364da</id>
    <updated>2026-09-17T02:00:00Z</updated>
    <category term="c"/><category term="game"/>
    <content type="html">
      <![CDATA[<p><a href="https://en.wikipedia.org/wiki/Stars!">Stars!</a> is a 1995 <a href="https://en.wikipedia.org/wiki/4X">4X</a> game (explore, expand, exploit, exterminate)
for 16-bit Windows 3.1 that I first played ~28 years ago. While Windows is
famously backwards compatible, it’s notoriously difficult to play Stars!
today. Windows x64 cannot run 16-bit applications, and playing requires
either retro hardware or emulation (<a href="https://github.com/otya128/winevdm">otvdm</a>, <a href="https://www.dosbox.com/">DOSBox</a>), sometimes
paired with <a href="https://www.winehq.org/">Wine</a>. My new, exciting solution, <strong><a href="https://github.com/skeeto/StarsVM">Stars!VM</a></strong>, or
<em>Stars! Virtual Machine</em>, embeds a custom <a href="/blog/2014/12/09/">80286</a> emulator and a Win16
to Win32 bridge. As native Win32, the game looks and feels exactly as it
did originally, except sporting a modern file chooser and 4k scaling. It’s
indistinguishable from a genuine 32-bit or 64-bit port of the game,
especially with the original 16-bit game embedded inside the VM
executable.</p>

<p><img src="/img/stars.png" alt="" /></p>

<p>The <a href="/blog/2026/04/25/">signed</a> releases on GitHub embed a compressed copy of the original
16-bit game, so that single EXE is ready to play out-of-the-box with no
further setup or downloads. I’m distributing 32-bit builds (but requires
SSE2) because there’s no advantage to 64-bit here, and these builds work
(almost) everywhere <em>except</em> 16-bit Windows. 32-bit Windows could run the
original 16-bit game, but the VM-encapsulated version is better behaved.
It doesn’t dump a <code>Stars.ini</code> under <code>C:\WINDOWS</code>, it interacts properly
with the task bar, and copy protection is neutralized via the OS bridge.</p>

<p>If you ever been curious about Stars!, now’s the time to try it. The game
has a thorough, built-in tutorial, but also check out the <a href="https://archive.org/download/manual_Stars/Stars.pdf">manual</a>,
<a href="https://wiki.starsautohost.org/">wiki</a>, <a href="https://starsautohost.org/strategy/guidef/SSG.htm">strategy guide</a>, and <a href="https://starsautohost.org/stars.htm">AutoHost</a> (play-by-email service).
The game predates the modern search engine concept, otherwise they might
have chosen a better name. I suggest using “stars 4x” in your searches.</p>

<p>If you want to build from source and hack on the VM yourself, the best
tool for the job is <a href="https://github.com/skeeto/w64devkit">w64devkit</a>, of course, because it <a href="/blog/2020/09/25/">comes with
everything you’ll need</a>. Plus the game itself: <code>stars.exe</code>
from <a href="https://wiki.starsautohost.org/wiki/Downloads"><code>stars27jrc3.zip</code></a>.</p>

<h3 id="implementation-details">Implementation details</h3>

<p>The emulator itself requires x86 or x86-64 because it does not implement
x87 (80-bit floating point) in software, but instead runs these operation
directly on the host’s x87 hardware. This is simple, fast, and precise.
The project validates the emulation as a whole with a differential fuzzer
against the host. The fuzzer randomly generates a 16-bit instruction,
emulates it, then runs it with <a href="/blog/2015/03/19/">JIT</a> on the host and compares the
results.</p>

<p>Handles on Windows are pointer-sized, and so the Win16-to-Win32 bridge
maps 16-bit handles to host handles. It marshals between different struct
layouts when translating these calls, services the DOS interrupts the game
requires, an copies data in and out of guest memory. It’s rather like
running a <a href="/blog/2026/01/01/">Wasm</a> instance, which of course makes sense in retrospect.</p>

<p>The Win32 bridge is also monitorable and manipulatable using the Model
Context Protocol (MCP). AI agents can “see” the UI “DOM” as it’s built,
and can drive it by injecting synthetic events into the event pump, all
without going through the usual desktop control. The MCP can also read and
write guest memory. Opus 5 played a complete game through MCP — which is
quite fun to watch — requesting my assistance at just two points when it
got stuck in the UI. A foundation for a new Stars!Bench?</p>

<p>Targeting old 16-bit computers, the authors couldn’t afford to build a
sloppy, wasteful UI, and so by modern standards the game UI is remarkably
fast and responsive. They don’t make ‘em like they used to. Computing the
next turn, or “turn generation,” is the computational bottleneck, and so
that’s where I focused my optimization efforts. The emulator can trace
executed instructions, so I gathered traces of turn generation, then had
Fable 5.1 identify and reverse engineer the hottest common routines (e.g.
the game’s L’Ecuyer MCG PRNG) and basic blocks. Each was re-written in C
and mapped into the instruction decoder as new 80286 instructions. On load
the emulator identifies these routines and patches them with the new
instruction. This resulted in a nearly ~2x speedup of turn generation. By
exploiting local conditions, emulating a particular known program, I get
JIT performance without JIT complexity.</p>

<p>The original game doesn’t use <a href="/blog/2023/02/13/">buffered I/O</a>, and instead issues many
small reads and writes. Passing these small reads/writes straight to Win32
made I/O take ~5% turn generation time, probably worse today than it was
back then. Plus it’s just rude. The emulator buffers the game’s I/O calls,
further speeding up turn generation.</p>

<p>The game has some sound effects in the “battle VCR” and the final version
of the game shipped with Microsoft’s <code>WaveMix.dll</code>. Rather than load and
link this DLL, the emulator implements the DLL’s interfaces natively, and
these routines are dynamically linked into the 16-bit process. You will
not need this DLL with the emulator, nor is it embedded in releases.</p>

<p>The game also has art assets embedded uncompressed in the original EXE,
forming the bulk of its ~3MB. Stars!VM uses a custom LZ-based compression
algorithm tailored to compressing the original game. It’s compressed when
embedded in releases. So the 32-bit version of the game is half the size
of the original, at ~1.5MB.</p>

<p>The original game requires a serial code, serving as its copy protection.
A code is 8 alpha-numeric characters that must pass two checks. Failing
the first is loud, but failing the second will sabotage your game with
penalties. You’ll know because it will announce that your people suspect
you are a usurper. Most codes you’ll find online are such “usurper” codes.
Play-by-email (PBEM) saves embed a hardware signature derived from C and D
drive configuration. People playing on different machines using the same
serial code recieve the usurper penalty.</p>

<p>I bought a serial code back in the day, but it hasn’t been possible to
purchase one a for at least decade now. So the emulator injects a fixed
serial code on first run (disable with <code>--prompt-serial</code>), and you won’t
need to worry about it. The VM also produces a fixed hardware signature
(same as any other emulator), so it looks like everyone running Stars!VM
is sharing the a machine, meaning no penalty for key reuse. I cracked the
serial code checks anyway, allowing me discover interesting ones. My
favorites: CLONEMUM, CROSSNUT, EGGSWAIN, GHOSTKIN, GONKSHOW, GRIPMIME,
SEEKCAPS, SIFTBOLD, SIRBUOYS, SLIMFAZE, SLOTMOPS, SPAWNELK, SUNBLOND,
WANTNEAR, and WRONGPOX. These look like some of <a href="/blog/2017/07/27/">my passwords</a>.</p>

<h3 id="endless-possibilities">Endless possibilities</h3>

<p>I’m quite pleased and excited with the results, especially for a weekend
project. It’s breathed life back into the game for me, not only having a
better experience running it, but also that I can trivially bend the game
to my will in the ways I dreamed about. A few hooks in the right places
should open the game to easy modding, but I’m more engineer than modder.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>Concurrent, atomic MSI hash tables</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/05/06/"/>
    <id>urn:uuid:d877f4c2-b213-4af7-8fb9-269558ee6b86</id>
    <updated>2026-05-06T02:01:17Z</updated>
    <category term="c"/>
    <content type="html">
      <![CDATA[<p>Readers will be familiar with <a href="/blog/2022/08/08/">Mask-Step-Index (MSI) hash tables</a>, a
technique for building fast, open-addressed hash tables in <a href="/blog/2025/01/19/#flat-hash-map">a dozen lines
of code</a>. If multiple threads or processes access an MSI table with
at least one still inserting elements, care must be taken to avoid data
races. This article will show how to add atomic operations to MSI tables
in order to support different concurrency constraints.</p>

<p>Let’s begin with the simplest case: An integer hash set, no deletions,
only one insert thread (single producer), and consumers do not care about
insert order. That is, the producer inserts A then B, but consumers may
observe B in the table before A. Suppose this is the hash table in the
single-threaded case:</p>

<pre class="highlight"><code><span class="t">int32_t</span> *<span class="f">lookup</span>(<span class="t">int32_t</span> key, <span class="t">int32_t</span> *table, <span class="t">int</span> exp)
{
    <span class="t">uint64_t</span> hash = ((<span class="t">uint64_t</span>)key * <span class="m">1111111111111111111u</span>) &gt;&gt; <span class="m">32</span>;
    <span class="t">uint32_t</span> mask = ((<span class="t">uint32_t</span>)<span class="m">1</span> &lt;&lt; exp) - <span class="m">1</span>;
    <span class="t">uint32_t</span> step = (hash &gt;&gt; (<span class="m">32</span> - exp)) | <span class="m">1</span>;
    <span class="k">for</span> (<span class="t">uint32_t</span> index = hash;;) {
        index = (index + step) &amp; mask;
        <span class="k">if</span> (!table[index] || table[index]==key) {
            <span class="k">return</span> table + index;
        }
    }
}
</code></pre>

<p>Keys must be non-zero, and tables are zero-initialized. Usage example:</p>

<pre class="highlight"><code>    <span class="c">// Initialization</span>
    <span class="k">enum</span> { exp = <span class="m">8</span> };
    <span class="t">int32_t</span> table[<span class="m">1</span>&lt;&lt;<span class="m">8</span>] = {};

    <span class="c">// Producer</span>
    <span class="k">for</span> (<span class="t">int</span> i = <span class="m">0</span>; i &lt; nkeys; i++) {
        *lookup(keys[i], table, exp) = keys[i];
    }

    <span class="c">// Consumer</span>
    <span class="t">int32_t</span> key = <span class="m">1234</span>;
    <span class="t">bool</span> present = *lookup(key, table, exp);
</code></pre>

<p>The only problem is the data race on <code>table</code> slots. Since consumers can
tolerate out-of-order insertions, ordering does not matter and relaxed
atomics eliminate the data race. Insert and query now have different
requirements, so it makes sense to distinguish them. Starting with the
latter:</p>

<pre class="highlight"><code><span class="t">bool</span> <span class="f">contains</span>(<span class="t">int32_t</span> key, <span class="t">int32_t</span> *table, <span class="t">int</span> exp)
{
    <span class="t">uint64_t</span> hash = ((<span class="t">uint64_t</span>)key * <span class="m">1111111111111111111u</span>) &gt;&gt; <span class="m">32</span>;
    <span class="t">uint32_t</span> mask = ((<span class="t">uint32_t</span>)<span class="m">1</span> &lt;&lt; exp) - <span class="m">1</span>;
    <span class="t">uint32_t</span> step = (hash &gt;&gt; (<span class="m">32</span> - exp)) | <span class="m">1</span>;
    <span class="k">for</span> (<span class="t">uint32_t</span> index = hash;;) {
        index = (index + step) &amp; mask;
        <span class="t">int32_t</span> k = __atomic_load_n(table+index, __ATOMIC_RELAXED);
        <span class="k">if</span> (!k) {
            <span class="k">return</span> <span class="k">false</span>;
        } <span class="k">else</span> <span class="k">if</span> (k == key) {
            <span class="k">return</span> <span class="k">true</span>;
        }
    }
}
</code></pre>

<p>Note how all elements are accessed by atomic loads, as a producer may
store to any slot at any time. Now producers:</p>

<pre class="highlight"><code><span class="t">bool</span> <span class="f">insert</span>(<span class="t">int32_t</span> key, <span class="t">int32_t</span> *table, <span class="t">int</span> exp)
{
    <span class="t">uint64_t</span> hash = ((<span class="t">uint64_t</span>)key * <span class="m">1111111111111111111u</span>) &gt;&gt; <span class="m">32</span>;
    <span class="t">uint32_t</span> mask = ((<span class="t">uint32_t</span>)<span class="m">1</span> &lt;&lt; exp) - <span class="m">1</span>;
    <span class="t">uint32_t</span> step = (hash &gt;&gt; (<span class="m">32</span> - exp)) | <span class="m">1</span>;
    <span class="k">for</span> (<span class="t">uint32_t</span> index = hash;;) {
        index = (index + step) &amp; mask;
        <span class="k">if</span> (!table[index]) {
            __atomic_store_n(table+index, key, __ATOMIC_RELAXED);
            <span class="k">return</span> <span class="k">true</span>;
        } <span class="k">else</span> <span class="k">if</span> (table[index] == key) {
            <span class="k">return</span> <span class="k">false</span>;
        }
    }
}
</code></pre>

<p>This function may load elements non-atomically because there’s only one
producer: the current thread. This idea could not be expressed were the
type system involved, e.g. <code>_Atomic</code>, but <a href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">GCC atomics</a> do not
involve require such special qualifiers. Stores on the other hand are
concurrent with consumers, requiring an atomic store. Single-producer,
multiple-consumer (SPMC) usage is nearly identical to the single-threaded
case:</p>

<pre class="highlight"><code>    <span class="c">// Producer</span>
    <span class="k">for</span> (<span class="t">int</span> i = <span class="m">0</span>; i &lt; nkeys; i++) {
        insert(keys[i], table, exp);
    }

    <span class="c">// Consumer</span>
    <span class="t">int32_t</span> key = <span class="m">1234</span>;
    <span class="t">bool</span> present = contains(key, table, exp);
</code></pre>

<p>A concurrent integer hash table is contrived and unrealistic. In a real
program a key likely carries some broader semantic meaning. For example,
if that “integer” is actually a memory offset known as a pointer, then it
<em>points</em> at some object, and it is important that stores to that object
happen before consumers observe the pointer in the table:</p>

<pre class="highlight"><code><span class="t">bool</span>   <span class="f">insert</span>(<span class="t">Thing</span> *thing, <span class="t">Thing</span> **table, <span class="t">int</span> exp)
<span class="t">Thing</span> *<span class="f">lookup</span>(<span class="t">Key</span> key, <span class="t">Thing</span> **table, <span class="t">int</span> exp)
</code></pre>

<p>Where usage might look like:</p>

<pre class="highlight"><code>    <span class="c">// Producer</span>
    <span class="k">for</span> (<span class="t">int</span> i = <span class="m">0</span>; i &lt; nthings; i++) {
        things[i].key = ...;  <span class="c">// update/init object</span>
        insert(things+i, table, exp);
    }

    <span class="c">// Consumer</span>
    <span class="t">bool</span> present = !!find((<span class="t">Key</span>){...}, table, exp);
</code></pre>

<p>In this case relaxed atomics are insufficient. Updates to the inserted
object may be reordered after the insertion, and consumers will race on
those updates. In this case we upgrade to acquire-release:</p>

<pre class="highlight"><code><span class="t">Thing</span> *<span class="f">lookup</span>(<span class="t">Key</span> key, <span class="t">Thing</span> **table, <span class="t">int</span> exp)
{
    <span class="c">// ...</span>
    <span class="k">for</span> (...) {
        <span class="c">// ...</span>
        <span class="t">Thing</span> *thing = __atomic_load_n(table+index, __ATOMIC_ACQUIRE);
        <span class="k">if</span> (!thing || thing-&gt;key==key) {
            <span class="k">return</span> thing;
        }
    }
}

<span class="t">bool</span> <span class="f">insert</span>(<span class="t">Thing</span> *thing, <span class="t">Thing</span> **table, <span class="t">int</span> exp)
{
    <span class="c">// ...</span>
    <span class="k">for</span> (...) {
        <span class="c">// ...</span>
        <span class="k">if</span> (!table[index]) {
            __atomic_store_n(table+index, thing, __ATOMIC_RELEASE);
            <span class="k">return</span> <span class="k">true</span>;
        } <span class="k">else</span> <span class="k">if</span> (table[index]-&gt;key == thing-&gt;key) {
            <span class="k">return</span> <span class="k">false</span>;
        }
    }
}
</code></pre>

<p>In this case producer and consumer synchronize on the atomics. Producer
stores are ordered before the release, and consumer loads are ordered
after the acquire. Objects are not modified once in the table, so atomics
are not required for their fields. On some architectures, including x86,
there will be no indication at the ISA level that atomics are in use —
i.e. this likely generates the same code as the single-threaded version —
and these atomics merely constrain the compiler’s instruction scheduling.</p>

<p>As a side effect of synchronizing, consumers will now observe insertions
in the same order as the producer. This is a more realistic and practical
situation than an integer hash table.</p>

<h3 id="multiple-producers">Multiple producers</h3>

<p>The multiple-producer case (MPMC) is more complicated for producers, but
consumers are unaffected, so we need only modify insertion. Still without
any locks, we will optimistically update the table. We look at the current
slot item, and if nothing is present compare-and-swap the new element in
place. On failure we <em>acquire</em> the element that won the race, continuing
as though it’s what we saw in the first place.</p>

<pre class="highlight"><code><span class="t">bool</span> <span class="f">insert</span>(<span class="t">Thing</span> *thing, <span class="t">Thing</span> **table, <span class="t">int</span> exp)
{
    <span class="c">// ...</span>
    <span class="k">for</span> (...) {
        <span class="c">// ...</span>
        <span class="t">Thing</span> *current = __atomic_load_n(table+index, __ATOMIC_ACQUIRE);
        <span class="k">if</span> (!current) {
            <span class="t">int</span> pass = __ATOMIC_RELEASE;
            <span class="t">int</span> fail = __ATOMIC_ACQUIRE;
            <span class="k">if</span> (__atomic_compare_exchange_n(
                    table+index, &amp;current, thing, <span class="m">0</span>, pass, fail)) {
                <span class="k">return</span> <span class="k">true</span>;
            }
        }
        <span class="k">if</span> (current-&gt;key == thing-&gt;key) {
            <span class="k">return</span> <span class="k">false</span>;
        }
    }
}
</code></pre>

<p>This is quite similar <a href="/blog/2023/09/30/#as-a-concurrent-hash-map">my hash trie concurrency enhancement</a> a few
years ago.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>I have officially retired from Emacs</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/04/26/"/>
    <id>urn:uuid:91357133-9d2d-4a6f-9b39-4bd1d35c814e</id>
    <updated>2026-04-26T00:00:00Z</updated>
    <category term="ai"/><category term="cpp"/><category term="emacs"/><category term="elfeed"/>
    <content type="html">
      <![CDATA[<p>This article was discussed <a href="https://old.reddit.com/r/emacs/comments/1svziwa">on reddit</a> and <a href="https://news.ycombinator.com/item?id=47906651">on Hacker News</a>.</p>

<p>This past Tuesday I typed <code>C-x C-c</code> in Emacs for the last time after 20
years of daily use. Though <a href="/blog/2017/04/01/">nearly half that time</a> was gradually
retiring it, switching to modal editing, then to Vim. Emacs is a platform,
and I’d grown accustomed to its applications, especially those I built
myself. There was no particular hurry, so replacements came slowly. With
my <a href="/blog/2026/03/29/">newly-acquired superpowers</a> I could knock out the last two pieces
in a few days’ work, namely <a href="/blog/2009/06/23/"><code>M-x calc</code></a> with <strong><a href="https://github.com/skeeto/stackcalc">stackcalc</a></strong> and
<a href="/blog/2013/09/04/">Elfeed</a> with <strong><a href="https://github.com/skeeto/Elfeed2">Elfeed2</a></strong>. I’m especially excited about the
latter because it already exceeds the original. Both are multi-platform,
native C++ GUI applications using native UI components.</p>

<!--more-->

<p><img src="/img/elfeed2.png" alt="" /></p>

<p>These <a href="https://melpa.org/#/?q=skeeto">actively-in-use</a> packages require new maintainers (apply on
the project’s issues/discussion):</p>

<ul>
  <li><a href="https://github.com/skeeto/at-el/">@</a> (<a href="/blog/2013/04/07/">about</a>)</li>
  <li><del><a href="https://github.com/skeeto/emacs-aio/">aio</a> (<a href="/blog/2019/03/10/">about</a>)</del></li>
  <li><a href="https://github.com/skeeto/bitpack/">bitpack</a></li>
  <li><del><a href="https://github.com/skeeto/elfeed/">Elfeed</a> (<a href="https://github.com/skeeto/elfeed/discussions/563">apply here</a>)</del></li>
  <li><del><a href="https://github.com/skeeto/impatient-mode/">Impatient</a> (<a href="/blog/2012/08/20/">about</a>)</del></li>
  <li><a href="https://github.com/skeeto/javadoc-lookup/">javadoc-lookup</a> (<a href="/blog/2013/01/30/">about</a>)</li>
  <li><a href="https://github.com/skeeto/elisp-json-rpc/">json-rpc</a></li>
  <li><a href="https://github.com/skeeto/emacs-memoize/">memoize</a> (<a href="/blog/2010/07/26/">about</a>)</li>
  <li><del><a href="https://github.com/skeeto/nasm-mode/">nasm-mode</a> (<a href="/blog/2015/04/19/">about</a>)</del></li>
  <li><del><a href="https://github.com/skeeto/emacs-web-server/">simple-httpd</a> (<a href="/blog/2012/08/20/">about</a>)</del></li>
  <li><a href="https://github.com/skeeto/skewer-mode/">Skewer</a> (<a href="/blog/2012/10/31/">about</a>)</li>
  <li><del><a href="https://github.com/skeeto/elisp-weak-ref/">weak-ref</a> (<a href="/blog/2012/12/17/">about</a>)</del></li>
  <li><a href="https://github.com/skeeto/x86-lookup/">x86-lookup</a> (<a href="/blog/2015/11/21/">about</a>)</li>
</ul>

<p>No wonder it took so long for me to move on! I’m not handing these off to
just anyone, and you’ll need to establish your reputation. Having already
made contributions is a good sign, even if never merged. I’m willing to
transfer them off my namespace, though you’ll need to manage the Melpa
hand-off (on which I’ll sign-off). If there are no takers, these projects
will be archived but not deleted.</p>

<h3 id="trying-out-wxwidgets">Trying out wxWidgets</h3>

<p>The Emacs Calculator is amazing and the best calculator I’ve ever used,
which is why nothing I could find was going to replace it. My clone uses
GMP and MPFR for multi-precision, so it’s far faster, as to be expected,
but it’s not nearly at feature parity. It’s missing esoteric features
including symbolic processing. Though it’s enough to cover all of my own
usage. I can add more features later. The Emacs Calculator manual served
as a specification when building stackcalc.</p>

<p>Elfeed has been a cornerstone of my daily routines for the past 13 years.
Nothing else I’ve found scratches that itch for me, so I’ve always known
it would require a rewrite someday. Knowing it would take a few weeks of
work, and that I <em>already had the feed reader I wanted</em>, made motivation
difficult to find. Though now that I can accomplish ~3 weeks of old-way
work in a new-way day, this sort of project becomes that much easier to
start and finish. Though it’s not yet at a 1.0 release, after a couple
days Elfeed2 was working well enough to replace the original Elfeed.</p>

<p>While <a href="https://github.com/ocornut/imgui">Dear ImGui</a> was the right choice for <a href="https://github.com/skeeto/dcmake">dcmake</a>, it would not be
so for these two applications. Active rendering doesn’t suit a feed reader
left running all day, and I needed a richer toolkit. Professionally I work
in Qt, but I wanted something lighter-weight for my projects, accessible
via CMake <code>FetchContent</code>. That naturally led to <a href="https://wxwidgets.org/">wxWidgets</a>. While it
has issues — mitigatable character encoding problems, accidental quadratic
time in many places — it’s worked better than I anticipated, letting me
rapidly produce native-looking applications on Windows, macOS, and Linux.</p>

<p>Unlike Dear ImGui, wxWidgets is a platform, including <a href="/blog/2021/12/30/">sane</a> I/O and
path handling. I <em>mostly</em> don’t need platform layers when building
applications like these. I can simply rely on wxWidgets’ utilities.</p>

<p>Both of these projects build out-of-the-box on <a href="https://github.com/skeeto/w64devkit">w64devkit</a> thanks to the
dependencies being <code>FetchContent</code>-compatible. On all platforms you just
need a C++ toolchain and CMake:</p>

<pre class="highlight"><code>$ cmake -B build
$ cmake --build build
</code></pre>

<p>Now that I have experience with wxWidgets, learning its limitations and
capabilities, it’s likely to be a foundation of most of my GUI projects to
come, except where something like Dear ImGui is a better git.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>My brave new code-signing world</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/04/25/"/>
    <id>urn:uuid:9db72e48-92f3-4426-a18f-9a317354e2c8</id>
    <updated>2026-04-25T18:12:29Z</updated>
    <category term="ai"/><category term="cpp"/><category term="crypto"/>
    <content type="html">
      <![CDATA[<p>The new <a href="https://github.com/skeeto/w64devkit">w64devkit</a> release two weeks ago is the first to be code-signed
with my identity, verified by Microsoft’s certificate chain. Currently
only the release packaging is signed — the self-extracting archive <em>and</em>
its payload — but I will soon code-sign individual EXEs and DLLs within
the distribution. In fact, <em>all</em> Windows builds of my project releases
have been code-signed the past two weeks, including <a href="/blog/2026/04/07/">dcmake</a>, and so
should everything going forward. My signing identity builds reputation
with each download, so users will have an easier time with SmartScreen,
and security software generally. <a href="https://learn.microsoft.com/en-us/azure/artifact-signing/overview">Azure Artifact Signing</a> creates the
actual signature, but the rest is done with new infrastructure I built
myself, <strong><a href="https://github.com/skeeto/aas-sign">aas-sign</a></strong>. As is often the case, the existing options were
deficient for my needs, so I had to build it myself.</p>

<p><strong>This code-signing is not free</strong>, and simply having <code>aas-sign</code> on hand,
or using the GitHub Actions action, is insufficient. You must be serious
enough to spend US$10/month for the Azure subscription. After that you are
subjected to the labyrinth that is the Azure portal, the most confusing UI
I’ve ever used. Luckily we live in <a href="/blog/2026/03/29/">an age of wonders</a>, and I could
describe to Claude in Chrome what I wanted and it would happen (Sonnet
works better than Opus for this). It took as much time to figure out Azure
as I spent creating a fully-functional, native debugger front-end. Clear
your schedule if you’re going to try it yourself. If it weren’t for AI
assistance I would have given up.</p>

<p>The one-time setup process is <strong>only open to North America</strong>, and involves
sharing identify documents (i.e. driver’s license) with Microsoft. Unlike
the rest of Azure, that part was streamlined and fairly painless. Between
the cost and this requirement, <em>this is a niche space</em>.</p>

<p>However, if this is your niche, aas-sign is currently the best software
available. <em>It’s the tool Microsoft should have written</em>, but didn’t due
to ongoing institutional failures. The alternatives are a pair of tools:
<a href="https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest">Azure CLI</a> (Python) combined with either <a href="https://github.com/ebourg/jsign">Jsign</a> (Java) or
<a href="https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe">SignTool.exe</a> (Windows only). All impose artificial runtime constraints
hostile to build pipeline composablility. Poor engineering. In contrast,
aas-sign is a native, multi-platform, single-file application.</p>

<p>If you know this space, <a href="https://github.com/mtrojnar/osslsigncode">osslsigncode</a> probably comes to mind, but it
produces signatures itself. It doesn’t interface with Azure and so has no
role here aside from semi-reliable validation. The most popular use case
is code-signing with self-signed certificates, but <a href="https://www.bcs.org/articles-opinion-and-research/what-happens-when-microsoft-defender-flags-your-software/">that actually makes
everything worse</a>.</p>

<p>There are two modes for aas-sign: Laptop and Action. Laptop mode is the
most compelling, so we’ll start with that, but Action mode is the most
useful in practice.</p>

<h3 id="laptopdesktop-mode">Laptop/desktop mode</h3>

<p>Suppose you built an EXE or DLL, and would like to code-sign and publish
it. Typically that looks like this:</p>

<pre class="highlight"><code>$ aas-sign sign myapp.exe myapp.dll
</code></pre>

<p>It computes an Authenticode for each (concurrently), sends it off to
Azure, gets back a signature, then a countersignature, and embeds the
signatures in the images. If you have multiple signing identities then you
might use <code>--as</code> (“sign as”):</p>

<pre class="highlight"><code>$ aas-sign sign --as eus:contoso:jdoe myapp.exe myapp.dll
</code></pre>

<p>The colon-delimited triple is my own invention to combine region (East
US), tenant (Contoso), and profile (J. Doe) into one string. The first
time you use it, and every ~90 days thereafter, you’ll need to
authenticate with Azure first:</p>

<pre class="highlight"><code>$ aas-sign login
</code></pre>

<p>This will open a browser (just like <code>az login</code>) to log in, from which it
will obtain a token than can be used to obtain signing tokens. (Yes, a
token to get tokens; I’m concealing as much complexity as possible.) You
might also want to establish a default identity, as typically you’d only
have one:</p>

<pre class="highlight"><code>$ aas-sign config eus:contoso:jdoe
</code></pre>

<p>Or all at once:</p>

<pre class="highlight"><code>$ aas-sign login eus:contoso:jdoe
</code></pre>

<p>My goal was, after enduring the Azure portal sign-up, to maximally
streamline code-signing.</p>

<h3 id="action-mode">Action mode</h3>

<p>Manually building, signing, and publishing releases is easy and might be
fine if you’re not releasing too frequently — or too <em>in</em>infrequently that
you forget how to do it — but likely you’d want to automate this process.
I was stubborn about it myself, until <a href="https://peter0x44.github.io/">Peter0x44</a> pushed me hard enough
to take it seriously, for which I’m grateful. There’s an official GitHub
Action to code-sign with Azure, but it requires a Windows runner, fatally
limiting for my own needs. So aas-sign also defines a code-signing action.
The previous example would have this in its own action:</p>

<pre class="highlight"><code>  - <span class="v">name</span>: Sign
    <span class="v">uses</span>: skeeto/aas-sign@v1.0.0
    <span class="v">with</span>:
      <span class="v">endpoint</span>:  ${{ secrets.TRUSTED_SIGNING_ENDPOINT }}
      <span class="v">account</span>:   ${{ secrets.TRUSTED_SIGNING_ACCOUNT }}
      <span class="v">profile</span>:   ${{ secrets.CERTIFICATE_PROFILE }}
      <span class="v">client-id</span>: ${{ secrets.AZURE_CLIENT_ID }}
      <span class="v">tenant-id</span>: ${{ secrets.AZURE_TENANT_ID }}
      <span class="v">files</span>: <span class="p">|</span>
        <span class="s">myapp.exe</span>
        <span class="s">myapp.dll</span>
</code></pre>

<p>The secrets are bunch of strings you (or your AI agent) retrieve from the
Azure portal. You also need to create Federated Identity Credential (FIC)
for each repository, which I suggest triggering on an environment. (This
all may <a href="https://www.youtube.com/watch?v=y8OnoxKotPQ">sound like a joke</a> but it’s real.) Again, just ask an AI to
do all this stuff. The mandatory Azure interfacing limits how much I can
streamline this process. Then aas-sign combines these with per-job tokens
GitHub injects into the runner to authenticate (via the FIC) and sign.</p>

<p>I’ve gone through this a number of times, and the AI breezes through the
GitHub UI, but struggles through the Azure portal — objective evidence of
how awful it is. Idea for a UI benchmark: How many AI tokens does it take
to accomplish typical activities?</p>

<p>For w64devkit, my plan is to run aas-sign inside the Docker build and sign
executables in the container before it’s SFX-packaged. This is impossible
with SignTool.exe and needlessly frictional with Jsign (requires at least
a JRE if not a JDK). The easiest path forward was to literally build my
own tool from scratch.</p>

<p>I’m considering <code>aas-sign</code> as a new w64devkit command, but it’s so niche
that I’m likely to be its sole user. On the other hand, those already
running w64devkit in GitHub Actions could use it in Action mode to
code-sign their builds without any additional tools.</p>

]]>
    </content>
  </entry>
  <entry>
    <title>dcmake: a new CMake debugger UI</title>
    <link rel="alternate" type="text/html" href="https://nullprogram.com/blog/2026/04/07/"/>
    <id>urn:uuid:eb448519-0a55-4c1c-bc55-17a65634224f</id>
    <updated>2026-04-07T03:04:02Z</updated>
    <category term="cpp"/>
    <content type="html">
      <![CDATA[<p>CMake has a <a href="https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-debugger"><code>--debugger</code> mode</a> since <a href="https://cmake.org/cmake/help/latest/release/3.27.html#debugger">3.27</a> (July 2023),
allowing software to manipulate it interactively through the <a href="https://microsoft.github.io/debug-adapter-protocol/">Debugger
Adaptor Protocol</a> (DAP), an HTTP-like protocol passing JSON messages.
Debugger front-ends can start, stop, step, breakpoint, query variables,
etc. a live CMake. When I came across this mode, I immediately conceived a
project putting it to use. Thanks to <a href="/blog/2026/03/29/">recent leaps in software engineering
productivity</a>, I had a working prototype in 30 minutes, and by the
end of that same day, a complete, multi-platform, native, GUI application.
I named it <strong><a href="https://github.com/skeeto/dcmake">dcmake</a></strong> (“debugger for CMake”). I’ve tested it on macOS,
Windows, and Linux. Despite only being couple days old, it’s one of the
coolest things I’ve ever built. Prior to 2026, I estimate it would have
taken me a month to get the tool to this point.</p>

<p><a href="/img/dcmake/dcmake.png"><img src="/img/dcmake/dcmake-thumb.png" alt="" /></a></p>

<p>It has a <a href="https://github.com/ocornut/imgui">Dear ImGui</a> interface, which I’ve experienced as a user but
never built on myself before. Specifically the <a href="https://github.com/ocornut/imgui/wiki/Docking">docking branch</a>. In a
sense it’s a toolkit for building debuggers, so it’s playing an enormous
role in how quickly I put this project together. All of the “windows” tear
out and may be free-floating or docked wherever you like, closely matching
the classic Visual Studio UI. I borrowed all the same keybindings: F10 to
step over, F11 to step in, F5 to start/continue, shift+F5 to stop. Click
on line numbers to toggle breakpoints, right click to run-to-line, hover
over variables with the mouse to see their values. Nearly every every UI
state persists across sessions, and it opens nearly instantly.</p>

<video src="/vid/dcmake.mp4" loop="" muted="" autoplay=""></video>

<p>This is just one of many situations I’ve used AI the past month for UI
development, and it’s been shockingly effective. I can describe roughly
the interface I want, and the AI makes it happen in a matter of minutes.
It understands what I mean, filling in the details, sometimes anticipating
what I’ll ask for next. If I’m unsure how I want a UI to work, it also
offers good advice. If I need simple icons and such, it can draw those,
too. It’s all incredibly empowering.</p>

<p>On macOS and Linux it runs on top of GLFW with OpenGL 3 rendering, and on
Windows it uses native Win32 windowing and DirectX 11 rendering.</p>

<p>Program arguments given to dcmake populate the top-left arguments text
input, which go straight into CMake on start. So you can prepend <code>d</code> to
your CMake configuration command to run it inside the debugger. Passing no
arguments sets it up for “standard” <code>-B build</code> configuration.</p>

<p>In general, if you don’t have anywhere in particular to look, likely the
first thing to do after starting dcmake (in a project) is press F10. It
starts CMake paused on the first line of <code>CMakeLists.txt</code>, or whatever
script you’re debugging. If you’re trying out dcmake for the first time,
that’s a good place to start. Keep pressing F10 to step through that
script, watching it run through its configuration. If you F11 through the
script then you’ll dive deeper and deeper into CMake itself, which can be
insightful.</p>

<p>There is no point in trying to debug <code>--build</code> invocations. It’s just a
uniform interface to the underlying build tool, and there is no CMake left
to debug at that point. However, it <em>does</em> work with <code>-P</code> <a href="https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-P">script mode</a>
invocations. CMake can operate as a <a href="https://claude.ai/public/artifacts/06b50c8f-ff71-4562-8ab5-80adaddff9b7">platform-agnostic shell script-like
tool</a>, but unlike shell scripts you can step through them with a
debugger like dcmake.</p>

<p>On Windows it supports Unicode paths all the way through, without <a href="/blog/2021/12/30/">a UTF-8
manifest</a>. This took some <a href="/blog/2022/02/18/">special care</a>, in particular
avoiding any C++ standard library I/O functionality. Current frontier AI
cannot handle this detail on their own. The macOS platform required a bit
of Objective-C, as it often does, and I’m happy I didn’t have to figure
that part out myself.</p>

<p>The next release of <a href="https://github.com/skeeto/w64devkit">w64devkit</a> will include dcmake, complementing its
recent addition of CMake. This new tool has already proven useful in its
own development.</p>

]]>
    </content>
  </entry>

</feed>
