<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://fzakaria.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://fzakaria.com/" rel="alternate" type="text/html" /><updated>2026-11-07T08:33:34-08:00</updated><id>https://fzakaria.com/feed.xml</id><title type="html">Farid Zakaria’s Blog</title><subtitle>I&apos;m a software engineer, father and wishful amateur surfer. If you&apos;ve come seeking my political views, you&apos;ve found the wrong &lt;a href=&quot;https://fareedzakaria.com/&quot;&gt;Fareed&lt;/a&gt;.</subtitle><entry><title type="html">The intensional model in disguise</title><link href="https://fzakaria.com/2026/11/06/the-intensional-model-in-disguise" rel="alternate" type="text/html" title="The intensional model in disguise" /><published>2026-11-06T10:00:00-08:00</published><updated>2026-11-06T10:00:00-08:00</updated><id>https://fzakaria.com/2026/11/06/the-intensional-model-in-disguise</id><content type="html" xml:base="https://fzakaria.com/2026/11/06/the-intensional-model-in-disguise"><![CDATA[<p>Despite how different they look on the surface, Nix, Bazel and buck2 are remarkably similar under the hood. Don’t believe me?
I built a prototype in <a href="/2026/10/17/one-store-two-frontends">one store, two frontends</a> to prove it. Turns out though that the similarities are even deeper than I realized.</p>

<!--more-->

<p>Upon further reflection, I realized that the intensional model is an even more substantial common denominator. What is the intensional model?</p>

<blockquote class="alert alert-tip">
  <p><strong>TIP</strong>
I’ve written before about <a href="/2025/03/08/demystifying-nix-s-intensional-model">Nix’s intensional model</a>, better known as content-addressed (CA) derivations. It has been “experimental” in Nix for years. 🧪</p>
</blockquote>

<p>In the intensional model, outputs are addressed by their <em>content</em>, and actions refer to their inputs by content as well. The extensional model instead addresses an output by the <em>recipe</em> and <em>inputs</em> that produced it. Nix also calls this “content-addressed derivations” (CA derivations). Bazel and buck2 don’t name their model, but they use content-addressing as well.
Consider this coincidence: <a href="https://edolstra.github.io/pubs/phd-thesis.pdf">Eelco’s PhD thesis</a> was published in 2006, the same year that Google started development on Blaze, the predecessor of Bazel [<a href="https://medium.com/better-programming/from-blaze-to-buck2-a-brief-history-of-modern-monorepo-build-systems-563becbcb987">cite</a>].</p>

<p>Let’s take a closer look at the three systems with a simple C example and see how all three implement the intensional model. 🥸</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// greet.h</span>
<span class="kt">void</span> <span class="nf">greet</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">);</span>

<span class="c1">// greet.c</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"greet.h"</span><span class="cp">
</span>
<span class="cm">/* say hi */</span>
<span class="kt">void</span> <span class="nf">greet</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"hello, %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// main.c</span>
<span class="cp">#include</span> <span class="cpf">"greet.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">greet</span><span class="p">(</span><span class="s">"world"</span><span class="p">);</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the traditional extensional model (input-addressed), a change anywhere upstream of a build action invalidates the action’s output. This occurs even if the change
is irrelevant to the output, such as a comment change in a source file. In contrast, the intensional model (content-addressed) only invalidates an action’s output if the content of the output itself changes.</p>

<p>In our C example, if we were to change the comment in <code class="language-plaintext highlighter-rouge">greet.c</code> from <code class="language-plaintext highlighter-rouge">say hi</code> to <code class="language-plaintext highlighter-rouge">say hello</code>, the compiled object file <code class="language-plaintext highlighter-rouge">greet.o</code> would still be byte-for-byte identical, as the comment does not affect the compiled output. In the ideal world, the linking of <code class="language-plaintext highlighter-rouge">hello</code> would not need to be redone, as its inputs have not changed in content.</p>

<details>
  <summary>Nix example</summary>
  <div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="nv">pkgs</span> <span class="o">?</span> <span class="kr">import</span> <span class="o">&lt;</span><span class="nv">nixpkgs</span><span class="o">&gt;</span> <span class="p">{},</span> <span class="nv">ca</span> <span class="o">?</span> <span class="kc">false</span> <span class="p">}:</span>
<span class="kd">let</span>
  <span class="nv">extra</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">lib</span><span class="o">.</span><span class="nv">optionalAttrs</span> <span class="nv">ca</span> <span class="p">{</span> <span class="nv">__contentAddressed</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="p">};</span>
<span class="kn">in</span> <span class="kr">rec</span> <span class="p">{</span>
  <span class="c"># The `cp` is needed otherwise the source file's store path ends up</span>
  <span class="c"># in the object file, which is a leak of the extensional model.</span>
  <span class="nv">greet</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">runCommandCC</span> <span class="s2">"greet"</span> <span class="nv">extra</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    cp </span><span class="si">${</span><span class="sx">./greet.h</span><span class="si">}</span><span class="s2"> greet.h; cp </span><span class="si">${</span><span class="sx">./greet.c</span><span class="si">}</span><span class="s2"> greet.c</span><span class="err">
</span><span class="s2">    mkdir -p $out</span><span class="err">
</span><span class="s2">    cc -c greet.c -o $out/greet.o</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>
  <span class="nv">hello</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">runCommandCC</span> <span class="s2">"hello"</span> <span class="nv">extra</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    cp </span><span class="si">${</span><span class="sx">./greet.h</span><span class="si">}</span><span class="s2"> greet.h; cp </span><span class="si">${</span><span class="sx">./main.c</span><span class="si">}</span><span class="s2"> main.c</span><span class="err">
</span><span class="s2">    mkdir -p $out/bin</span><span class="err">
</span><span class="s2">    cc main.c </span><span class="si">${</span><span class="nv">greet</span><span class="si">}</span><span class="s2">/greet.o -o $out/bin/hello</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<p>By default, Nix uses the <em>extensional</em> (input-addressed) model. If we <em>edit the comment</em> and rebuild:</p>

<div data-mark="3" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix-build <span class="nt">-A</span> hello
<span class="go">building '/nix/store/3hwrlpnji5bh5bajdg52s7zbg7ihs6j6-greet.drv'...
building '/nix/store/2k5is7yim1j8pb3qjz89bw45cqzcy2y5-hello.drv'...
/nix/store/122jc3iz6qkrgm907kdz7rrk3lp4qrma-hello
</span></code></pre></div></div>

<p>Everything rebuilt. 😩</p>

<p>The store path in the extensional model is a hash of the <em>recipe</em>, and the recipe for <code class="language-plaintext highlighter-rouge">hello</code> includes the recipe for <code class="language-plaintext highlighter-rouge">greet</code>. Recursion is fun!
Change one byte anywhere upstream and every path downstream changes with it. 🌀</p>

<figure class="graphviz" style="--graphviz-height: 6rem"><img src="https://fzakaria.com/assets/figures/0cb4e88545d5e7ee.svg" alt="Figure" width="571" height="44">
</figure>

<blockquote class="alert alert-warning">
  <p><strong>Warning</strong>
 The two <code class="language-plaintext highlighter-rouge">hello</code> binaries are not even byte-identical: the cc-wrapper puts <code class="language-plaintext highlighter-rouge">$out/lib</code> in the <code class="language-plaintext highlighter-rouge">RUNPATH</code>, so the binary contains its own store path. The extensional model leaks into the bytes. 🙃</p>
</blockquote>

<p>If we enable the build to use content-addressed derivations we observe <code class="language-plaintext highlighter-rouge">greet</code> rebuilt, but <code class="language-plaintext highlighter-rouge">hello</code> did not. 🎉</p>

<div data-mark="3" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix-build <span class="nt">--extra-experimental-features</span> ca-derivations <span class="nt">-A</span> hello <span class="nt">--arg</span> ca <span class="nb">true</span>
<span class="go">building '/nix/store/qpa1q7g5yjvhdbnfap4y3r2glzyflyv4-greet.drv'...
</span><span class="gp">resolved derivation: '/nix/store/1xnbxrrprs50879ivzmjpv7cbk44w258-hello.drv' -&gt;</span><span class="w"> </span><span class="s1">'/nix/store/rs2j6r2gvz2vi7nfvqj1s3yxx4lqpa3m-hello.drv'</span>...
<span class="go">/nix/store/l2nrycgcg97vrz4nb59icwz9i04ys8jg-hello
</span></code></pre></div></div>

<p>You can read <a href="/2025/03/08/demystifying-nix-s-intensional-model">demystifying Nix’s intensional model</a> for a more detailed explanation of how CA derivations work, but the short version is that Nix rewrites <code class="language-plaintext highlighter-rouge">hello.drv</code> to point at <code class="language-plaintext highlighter-rouge">greet</code>’s <em>content-addressed</em> output instead of <code class="language-plaintext highlighter-rouge">greet.drv</code>. The resolved <code class="language-plaintext highlighter-rouge">rs2j6r…-hello.drv</code> is the same one we built before the edit.</p>

<p>Since no “input” changed, Nix avoids the rebuild.</p>

<p>This is the same behavior as Bazel and buck2, which are intensional by default.</p>

<details>
  <summary>BUILD example</summary>
  <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">cc_library</span><span class="p">(</span>
    <span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">greet</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">srcs</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">greet.c</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">hdrs</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">greet.h</span><span class="sh">"</span><span class="p">],</span>
<span class="p">)</span>

<span class="nf">cc_binary</span><span class="p">(</span>
    <span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">hello</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">srcs</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">main.c</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">deps</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">:greet</span><span class="sh">"</span><span class="p">],</span>
<span class="p">)</span>
</code></pre></div>  </div>
</details>

<p>If we edit the comment and rebuild with Bazel or buck2, we see that the compile ran, but the link was an <em>action cache hit</em>.</p>

<div data-mark="2,8" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>bazel build //:hello <span class="nt">--explain</span><span class="o">=</span>explain.log
<span class="go">INFO: 2 processes: 1 action cache hit, 1 internal, 1 linux-sandbox.

</span><span class="gp">$</span><span class="w"> </span><span class="nb">cat </span>explain.log
<span class="go">Executing action 'Compiling greet.c': action changed since cached execution.

</span><span class="gp">$</span><span class="w"> </span>buck2 build //:hello
<span class="go">Commands: 1 (cached: 0, remote: 0, local: 1)
BUILD SUCCEEDED

</span><span class="gp">$</span><span class="w"> </span>buck2 log what-ran
<span class="go">build  root//:greet (c_compile greet.c (pic))  local  ... clang -c ./greet.c ...
</span></code></pre></div></div>

<p>Bazel and buck2 do not have an input-addressed mode. They always use the intensional model. An action’s key is a hash of its command line (to force rebuilds) and the <em>content digests</em> of its inputs.</p>

<figure class="graphviz" style="--graphviz-height: 6rem"><img src="https://fzakaria.com/assets/figures/6c21e0b9634f31ff.svg" alt="Figure" width="816" height="44">
</figure>

<p>A change only travels as far as the bytes actually change. This optimization is called <em>early cutoff</em>, and it is the whole selling point of the intensional model.</p>

<p>There is a clear mapping between Nix’s CA derivations and Bazel’s action cache.</p>

<table>
  <thead>
    <tr>
      <th>Nix (intensional)</th>
      <th>Bazel / buck2 (REAPI)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>resolved derivation</td>
      <td><code class="language-plaintext highlighter-rouge">Action</code>: command + input digests</td>
    </tr>
    <tr>
      <td>realisation: <code class="language-plaintext highlighter-rouge">drv output → store path</code></td>
      <td>action cache: <code class="language-plaintext highlighter-rouge">action digest → output digests</code></td>
    </tr>
    <tr>
      <td>content-addressed store path</td>
      <td>blob in the CAS</td>
    </tr>
  </tbody>
</table>

<p>A resolved derivation <em>is</em> a Bazel/buck2 action. 🤯</p>

<figure class="graphviz" style="--graphviz-height: 8rem"><img src="https://fzakaria.com/assets/figures/1aa2ae2ca242f9c0.svg" alt="Figure" width="496" height="174">
</figure>

<p>You may see the output paths in Bazel and buck2 and think they are extensional (i.e. <code class="language-plaintext highlighter-rouge">bazel-out/k8-fastbuild/bin/hello</code>), but they are just a convenient place on disk. The real addresses are the digests. <code class="language-plaintext highlighter-rouge">buck2</code> can even make this explicit with <code class="language-plaintext highlighter-rouge">declare_output(has_content_based_path=True)</code>.</p>

<p>Until <a href="/2025/03/11/nix-dynamic-derivations-a-practical-application">dynamic derivations</a> stabilize, people will continue to reach for Bazel and buck2 for the granularity on large builds. The intensional model is a big part of why they are so fast, and Nix has the same model. The two implementations are nearly identical in concept.</p>

<p>Nix described the intensional model in 2006. Google started building on it the same year. Twenty years later, maybe it’s time they compared notes. 🥸</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Despite how different they look on the surface, Nix, Bazel and buck2 are remarkably similar under the hood. Don’t believe me? I built a prototype in one store, two frontends to prove it. Turns out though that the similarities are even deeper than I realized.]]></summary></entry><entry><title type="html">Rewinding by fast-forwarding</title><link href="https://fzakaria.com/2026/11/05/rewinding-by-fast-forwarding" rel="alternate" type="text/html" title="Rewinding by fast-forwarding" /><published>2026-11-05T09:00:00-08:00</published><updated>2026-11-05T09:00:00-08:00</updated><id>https://fzakaria.com/2026/11/05/rewinding-by-fast-forwarding</id><content type="html" xml:base="https://fzakaria.com/2026/11/05/rewinding-by-fast-forwarding"><![CDATA[<p>Debuggers are an incredibly powerful and useful tool, but they mostly show you only the present and let you move forward.<sup id="fnref:rr"><a href="#fn:rr" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> Inspecting the present can help you understand the past, but is that a limitation we must accept?</p>

<p>What if you could travel back in time to see what happened? What if you could see the exact moment a value was written, and who wrote it? What if you could run your debugger <em>backwards</em>?</p>

<p><a href="https://rewindvm.dev">Rewind</a> is a deterministic virtual machine that records a program’s execution once, and then lets you replay it identically, many times. This lets you run the debugger backwards, and see the history of a value in memory, including who last read and wrote it.</p>

<p>Turns out that you can go backwards by fast-forwarding.</p>

<p>Going backwards changes how you debug: you can see how a value got there, not just what it is now. In <a href="/2026/10/23/who-wrote-this-byte">who wrote this byte?</a> <code class="language-plaintext highlighter-rouge">rewind blame</code> found the program that wrote a byte of a file. That was the easy case. Every write to a file goes through the kernel, and Rewind’s kernel logs each one. Writes to memory don’t go through the kernel, so there is no log to read.</p>

<p>So instead we go backwards, <em>by running forwards again</em>. This is how <code class="language-plaintext highlighter-rouge">rewind blame</code> can tell us who last read and wrote a value in memory, and it is the same trick that lets gdb run backwards as well. Let’s see how it works.</p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
New to Rewind? Start with <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a> and <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a></p>
</blockquote>

<h2 id="two-tellers-one-account">Two tellers, one account</h2>

<p>Let’s use the <a href="https://github.com/fzakaria/rewindvm/tree/main/examples/bank"><code class="language-plaintext highlighter-rouge">bank</code></a> example from <a href="/2026/10/07/nix-wrote-half-of-my-debugger">Nix wrote half of my debugger</a>. Two tellers each deposit 10, twenty times, into one account:</p>

<div data-mark="5,10" class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">long</span> <span class="n">balance</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">deposit</span><span class="p">(</span><span class="kt">int</span> <span class="n">teller</span><span class="p">,</span> <span class="kt">long</span> <span class="n">amount</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">long</span> <span class="n">seen</span> <span class="o">=</span> <span class="n">balance</span><span class="p">;</span>
	<span class="kt">char</span> <span class="n">line</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
	<span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">snprintf</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="k">sizeof</span> <span class="n">line</span><span class="p">,</span> <span class="s">"teller %d: %ld + %ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">teller</span><span class="p">,</span> <span class="n">seen</span><span class="p">,</span> <span class="n">amount</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">balance</span> <span class="o">=</span> <span class="n">seen</span> <span class="o">+</span> <span class="n">amount</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In our example, crucially, there is no lock around the read and write of <code class="language-plaintext highlighter-rouge">balance</code>. If the other teller runs in between, one of the deposits is lost.</p>

<p><code class="language-plaintext highlighter-rouge">rewind check</code> runs the program under different thread schedules until one fails:</p>

<div data-mark="6" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--epoch</span> 1790985600 github:fzakaria/rewindvm#bank
<span class="go">schedule   0: exited:0             4547 steps  2ffd6b62eb30  run b1d6981401d5d15f  holds
schedule   1: exited:2             3768 steps    run 36036ed36994d7d8  violates same-outcome, sdk
</span><span class="c">...
</span><span class="go">failing: run 240115d5505149aa, schedule 1 over steps 3245..3246
failing run violates sdk: step 3292: always "balance-adds-up" failed: balance 310, expected 400
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">check</code> narrows schedule 1’s failure down to steps 3,245 and 3,246 and records that run, <code class="language-plaintext highlighter-rouge">240115d5</code>, which is the one we will dig into. We expected 400 and got 310. Nine deposits are gone. 😬</p>

<h2 id="rewind-blame-at">rewind blame –at</h2>

<p>Our program prints the balance at step 3,291. We can run <code class="language-plaintext highlighter-rouge">rewind blame</code> at that step and the variable<sup id="fnref:expression"><a href="#fn:expression" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> to see who last wrote it:</p>

<div data-mark="4-6" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind blame 240115d5 <span class="nt">--at</span> 3291 balance
<span class="go">balance = 310   8 bytes at 0x56055a0ef030, thread 139, bank.c:69

last written at step 3282 by thread 141 (bank)
</span><span class="gp">  bank.c:41  teller  balance = seen + amount;</span><span class="w">
</span><span class="go">  was 300

other threads just after the write, in step 3282:
  thread 139  bank.c:65  main  __GI___clone3

searched back to step 3210, 1 keyframe window, 1 write, 3.1s
</span></code></pre></div></div>

<p>Rewind goes “backwards” through the run, looking for the last write to that value. We can see that the last write was at step 3,282, by teller 2 (thread 141) on line 41.</p>

<p>But wait, 300 + 10 is 310. That write is fine! 🤔</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_spiderman_balance_meme.png"><img src="/assets/images/rewind_spiderman_balance_meme.png" alt="Spider-Man pointing at Spider-Man meme: one Spider-Man labelled &quot;teller 1: I wrote balance&quot;, the other &quot;teller 2: no, I wrote balance&quot;" /></a></p>

<p>The last write is rarely the bug. The money went missing earlier, when a teller wrote a value it had read much earlier. We need to augment <code class="language-plaintext highlighter-rouge">blame</code> with the reads too, to see that.</p>

<h2 id="who-read-it">Who read it?</h2>

<p><code class="language-plaintext highlighter-rouge">--reads</code> lists every read and write of the value, back to the start of the process. Here are the rows around where it goes wrong:</p>

<div data-mark="5,12" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind blame 240115d5 <span class="nt">--at</span> 3291 balance <span class="nt">--reads</span>
<span class="go">  step  thread           access  value               where
</span><span class="c">...
</span><span class="gp">  3244  140 bank         read    110                 bank.c:36  teller  long seen = balance;</span><span class="w">  </span>balance
<span class="gp">  3248  141 bank         read    110                 bank.c:36  teller  long seen = balance;</span><span class="w">  </span>balance
<span class="gp">  3250  140 bank         write   120                 bank.c:41  teller  balance = seen + amount;</span><span class="w">  </span>balance
<span class="gp">  3250  140 bank         read    120                 bank.c:36  teller  long seen = balance;</span><span class="w">  </span>balance
<span class="c">...
</span><span class="gp">  3257  140 bank         write   190                 bank.c:41  teller  balance = seen + amount;</span><span class="w">  </span>balance
<span class="gp">  3257  140 bank         read    190                 bank.c:36  teller  long seen = balance;</span><span class="w">  </span>balance
<span class="gp">  3258  140 bank         write   200                 bank.c:41  teller  balance = seen + amount;</span><span class="w">  </span>balance
<span class="gp">  3263  141 bank         write   120                 bank.c:41  teller  balance = seen + amount;</span><span class="w">  </span>balance
<span class="c">...
</span><span class="go">searched back to step 3210, 1 keyframe window, 40 writes, 41 reads, 1.8s
</span></code></pre></div></div>

<p>Reading the chronology, we can see that teller 2 read the balance at step 3,248, and then teller 1 made nine deposits, taking the balance from 110 to 200. Then at step 3,263 teller 2 wrote its stale value of 120, overwriting the balance and losing the nine deposits (90). 💣</p>

<p>In this example, the bug is obvious, but in a real program it can be hard to find. The <code class="language-plaintext highlighter-rouge">--reads</code> option shows you the whole history of a value, and you can often see where it went wrong without ever setting a breakpoint. You can even see the bug in the plot of the balance over time:</p>

<figure class="plotnine" title="The balance after each of the 40 writes, by step. Teller 1's writes climb from 10 to 200 by step 3,258. Teller 2 read 110 at step 3,248, and at step 3,263 it writes 120, dropping the balance from 200. It then climbs to 310, while the dashed expected line ends at 400."><img src="https://fzakaria.com/assets/figures/dd38f7f64ca83583.svg" alt="The balance after each of the 40 writes, by step. Teller 1's writes climb from 10 to 200 by step 3,258. Teller 2 read 110 at step 3,248, and at step 3,263 it writes 120, dropping the balance from 200. It then climbs to 310, while the dashed expected line ends at 400." width="518" height="245">
</figure>

<p>The example is simple and the fix is obvious: a lock around the read and write. In a real program, the bug can be subtle and the fix complex. Seeing the history of a value, and who last wrote it, can be invaluable in tracking down the root cause.</p>

<h2 id="in-the-app">In the app</h2>

<p>The <a href="https://rewindvm.dev">Rewind app</a> gives the same information but in a more interactive way. The source panel underlines every value that <code class="language-plaintext highlighter-rouge">blame</code> can watch:</p>

<p style="--image-width: 30rem"><a href="/assets/images/rewind-blame-hover.png"><img src="/assets/images/rewind-blame-hover-crop.png" alt="The Rewind app's source panel at bank.c:69, the printf of the balance, with balance underlined and a tooltip: balance = 310. Click to see who last wrote it." /></a></p>

<p>You can click on the value to see who wrote it, and then click <em>Show reads too</em> to see the whole history of that value.</p>

<p style="--image-width: 30rem"><a href="/assets/images/rewind-blame-answer.png"><img src="/assets/images/rewind-blame-answer-crop.png" alt="The app's answer for balance: became 310 at step 3,282, 9 steps before here, written by thread 141 in teller at bank.c:41, balance = seen + amount, was 300, with buttons Go to the write, Show reads too and Blame the earlier value" /></a></p>

<p><em>Show reads too</em> draws every access, one lane per thread. Teller 2’s lone read sits in the middle of teller 1’s deposits, and that one dot is the bug. 🙌</p>

<p style="--image-width: 30rem"><a href="/assets/images/rewind-blame-reads.png"><img src="/assets/images/rewind-blame-reads-crop.png" alt="The app's accesses view for balance: three lanes. Thread 140 has two runs of reads and writes, thread 141 has one read between them and then its own run of reads and writes, and thread 139 reads once at the end. Searched back to step 3,210, 1 keyframe window, 40 writes, 41 reads, 0.9 s" /></a></p>

<h2 id="gdb-backwards">gdb, backwards</h2>

<p>If you would rather drive, <code class="language-plaintext highlighter-rouge">rewind gdb</code> can go backwards too by setting a watchpoint and <code class="language-plaintext highlighter-rouge">reverse-continue</code>. Here it is forked at step 3,264, just after teller 2’s stale write:</p>

<div data-mark="8-9" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind gdb 240115d5 3264
<span class="go">(gdb) watch balance
Hardware watchpoint 1: balance
(gdb) reverse-continue

Thread 3 hit Hardware watchpoint 1: balance

Old value = 120
New value = 200
0x000056055a0ec50d in deposit (teller=2, amount=10) at bank.c:41
</span><span class="gp">41			balance = seen + amount;</span><span class="w">
</span></code></pre></div></div>

<p>You can navigate the stack, inspect variables, and step backwards with <code class="language-plaintext highlighter-rouge">reverse-step</code> and <code class="language-plaintext highlighter-rouge">reverse-next</code>.</p>

<h2 id="rewinding-by-fast-forwarding">Rewinding by fast-forwarding</h2>

<p>How does this all work? How do you run a VM backwards?</p>

<p>You don’t. 😅</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_astronaut_reverse_meme.png"><img src="/assets/images/rewind_astronaut_reverse_meme.png" alt="Always has been meme: an astronaut looking at Earth asks &quot;Wait, running backwards is just running forward?&quot; and the astronaut behind him, holding a gun, says &quot;Always has been&quot;" /></a></p>

<p>Rewind replays are completely deterministic: the same run produces the same result every time. Turns out that a simpler way to go backwards is to run forwards again, but stop one instruction sooner than last time. That is all <code class="language-plaintext highlighter-rouge">blame</code> and <code class="language-plaintext highlighter-rouge">reverse-continue</code> do.</p>

<p>Rewind keeps a <em>checkpoint</em> of the machine at the start of a step. It rewinds to that checkpoint and then runs forward back to the step you want to inspect making note of every read and write along the way. If no reads or writes are found, it rewinds to the previous checkpoint and runs forward again. It keeps doing this until it finds the last read or write before the step you want to inspect.<sup id="fnref:cost"><a href="#fn:cost" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 830 300" role="img" class="ff" style="display:block;margin-inline:auto;width:100%;height:auto" aria-label="How rewind blame and reverse-continue go back. A timeline of the bank run's steps, with the step asked about, 3291, on the right. Step one, the rewind: restore the latest keyframe before the step. Step two, the fast-forward: replay forward from it to the step with the CPU's debug registers watching balance; each of the 40 writes traps, teller 1's in red from step 3234 to 3258 and teller 2's in teal from 3263 to 3282. The last trap before the step is the answer, step 3282, teller 2, 300 to 310. Only if the window held no trap does the search restore the keyframe before and replay that window.">
<style>
  /* Inline in the post, so these rules reach the whole page: every class is
     prefixed, and colours read the site's tokens, which follow its theme. */
  .ff { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
  .ff-t { font-size: 12px; }
  .ff-s { font-size: 10.5px; }
  .ff-b { font-weight: bold; }
  .ff-ink { fill: var(--ink, #1a1815); }
  .ff-muted { fill: var(--ink-muted, #565046); }
  .ff-faint { fill: var(--ink-faint, #6f685b); }
  .ff-accent { fill: var(--accent, #9e3413); }
  .ff-window { fill: var(--paper-raised, #ebe6da); }
  .ff-before { fill: none; stroke: var(--rule-strong, #c2b9a6); stroke-dasharray: 4 3; }
  .ff-axis { stroke: var(--ink-faint, #6f685b); stroke-width: 1; }
  .ff-line-ink { stroke: var(--ink, #1a1815); }
  .ff-line-accent { stroke: var(--accent, #9e3413); fill: none; }
  .ff-t1 { fill: #b1201d; }
  .ff-t2 { fill: #2f6f73; }
</style>
<defs>
  <marker id="ff-ink-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" class="ff-ink" />
  </marker>
  <marker id="ff-accent-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" class="ff-accent" />
  </marker>
</defs>
<!-- The window searched first, and the one before it, faint. -->
<rect x="250" y="166" width="440" height="60" rx="4" class="ff-window" />
<rect x="40" y="166" width="202" height="60" rx="4" class="ff-before" />
<line x1="40" y1="196" x2="690" y2="196" class="ff-axis" />
<!-- Keyframes. -->
<path d="M250,187 l9,9 l-9,9 l-9,-9 z" class="ff-ink" />
<path d="M44,187 l9,9 l-9,9 l-9,-9 z" class="ff-faint" />
<text x="250" y="242" class="ff-s ff-muted" text-anchor="middle">keyframe</text>
<text x="44" y="242" class="ff-s ff-faint" text-anchor="start">keyframe</text>
<!-- The step asked about. -->
<line x1="690" y1="152" x2="690" y2="230" class="ff-line-accent" stroke-width="1.6" />
<text x="700" y="192" class="ff-s ff-b ff-accent">step 3291:</text>
<text x="700" y="207" class="ff-s ff-b ff-accent">who wrote balance?</text>
<!-- ① The rewind: restore the keyframe. -->
<path d="M684,148 C610,18 330,18 256,180" class="ff-line-accent" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#ff-accent-arrow)" />
<text x="470.0" y="30" class="ff-t ff-accent" text-anchor="middle"><tspan class="ff-b">1</tspan>  restore the latest keyframe before the step</text>
<!-- ② The fast-forward: replay with the debug registers set. -->
<line x1="262" y1="140" x2="682" y2="140" class="ff-line-ink" stroke-width="1.4" marker-end="url(#ff-ink-arrow)" />
<text x="470.0" y="158" class="ff-t ff-ink" text-anchor="middle"><tspan class="ff-b">2</tspan>  replay forward, debug registers watching balance</text>
<!-- Every write traps; the last before the step is the answer. -->
<circle cx="304.2" cy="196" r="3.4" class="ff-t1" />
<circle cx="310.9" cy="196" r="3.4" class="ff-t1" />
<circle cx="317.7" cy="196" r="3.4" class="ff-t1" />
<circle cx="324.5" cy="196" r="3.4" class="ff-t1" />
<circle cx="331.2" cy="196" r="3.4" class="ff-t1" />
<circle cx="338.0" cy="196" r="3.4" class="ff-t1" />
<circle cx="344.8" cy="196" r="3.4" class="ff-t1" />
<circle cx="351.5" cy="196" r="3.4" class="ff-t1" />
<circle cx="358.3" cy="196" r="3.4" class="ff-t1" />
<circle cx="365.1" cy="196" r="3.4" class="ff-t1" />
<circle cx="371.8" cy="196" r="3.4" class="ff-t1" />
<circle cx="412.5" cy="196" r="3.4" class="ff-t1" />
<circle cx="419.2" cy="196" r="3.4" class="ff-t1" />
<circle cx="426.0" cy="196" r="3.4" class="ff-t1" />
<circle cx="432.8" cy="196" r="3.4" class="ff-t1" />
<circle cx="439.5" cy="196" r="3.4" class="ff-t1" />
<circle cx="446.3" cy="196" r="3.4" class="ff-t1" />
<circle cx="453.1" cy="196" r="3.4" class="ff-t1" />
<circle cx="459.8" cy="196" r="3.4" class="ff-t1" />
<circle cx="466.6" cy="196" r="3.4" class="ff-t1" />
<circle cx="500.5" cy="196" r="3.4" class="ff-t2" />
<circle cx="507.2" cy="196" r="3.4" class="ff-t2" />
<circle cx="514.0" cy="196" r="3.4" class="ff-t2" />
<circle cx="520.8" cy="196" r="3.4" class="ff-t2" />
<circle cx="527.5" cy="196" r="3.4" class="ff-t2" />
<circle cx="534.3" cy="196" r="3.4" class="ff-t2" />
<circle cx="541.1" cy="196" r="3.4" class="ff-t2" />
<circle cx="547.8" cy="196" r="3.4" class="ff-t2" />
<circle cx="554.6" cy="196" r="3.4" class="ff-t2" />
<circle cx="561.4" cy="196" r="3.4" class="ff-t2" />
<circle cx="568.2" cy="196" r="3.4" class="ff-t2" />
<circle cx="574.9" cy="196" r="3.4" class="ff-t2" />
<circle cx="581.7" cy="196" r="3.4" class="ff-t2" />
<circle cx="588.5" cy="196" r="3.4" class="ff-t2" />
<circle cx="595.2" cy="196" r="3.4" class="ff-t2" />
<circle cx="602.0" cy="196" r="3.4" class="ff-t2" />
<circle cx="608.8" cy="196" r="3.4" class="ff-t2" />
<circle cx="615.5" cy="196" r="3.4" class="ff-t2" />
<circle cx="622.3" cy="196" r="3.4" class="ff-t2" />
<circle cx="629.1" cy="196" r="3.4" class="ff-t2" />
<circle cx="629.1" cy="196" r="8" fill="none" class="ff-line-accent" stroke-width="1.6" />
<line x1="629.1" y1="205" x2="629.1" y2="258" class="ff-line-accent" stroke-width="1" />
<text x="623.1" y="272" class="ff-t ff-accent" text-anchor="end"><tspan class="ff-b">3</tspan>  the last trap is the answer: step 3282, teller 2, 300 → 310</text>
<!-- Legend and the fallback. -->
<circle cx="258" cy="286" r="3.4" class="ff-t1" />
<text x="266" y="290" class="ff-s ff-muted">teller 1 writes</text>
<circle cx="378" cy="286" r="3.4" class="ff-t2" />
<text x="386" y="290" class="ff-s ff-muted">teller 2 writes</text>
<text x="141.0" y="190" class="ff-s ff-faint" text-anchor="middle">no trap in the window?</text>
<text x="141.0" y="206" class="ff-s ff-faint" text-anchor="middle">do it again from here</text>
</svg>

<p>Deterministic replay is what makes this work, and it keeps the implementation simple: going back is just going forward again from earlier.</p>

<p>I’ve been having a lot of fun debugging my own programs this way. It’s a bit like time travel without the paradoxes. You can see the whole history of a value, and who wrote it, without setting a breakpoint and restarting the program.</p>

<p>It’s git blame for your program’s memory.  🔍</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:rr">
      <p>Tools like <a href="https://rr-project.org/">rr</a> and <a href="https://undo.io/">Undo</a> record a process and let you run its replay backwards. Rewind records the whole machine, the kernel included, so the same inputs make the same run every time. <a href="#fnref:rr" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:expression">
      <p>The expression can be a variable, a pointer dereference, or a struct field. <a href="#fnref:expression" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cost">
      <p>A step can be millions of instructions, so this can take a little bit. <a href="#fnref:cost" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Debuggers are an incredibly powerful and useful tool, but they mostly show you only the present and let you move forward.1 Inspecting the present can help you understand the past, but is that a limitation we must accept? Tools like rr and Undo record a process and let you run its replay backwards. Rewind records the whole machine, the kernel included, so the same inputs make the same run every time. &#8617;]]></summary></entry><entry><title type="html">Why does shuffling the schedule find so many bugs?</title><link href="https://fzakaria.com/2026/11/03/why-does-shuffling-the-schedule-find-so-many-bugs" rel="alternate" type="text/html" title="Why does shuffling the schedule find so many bugs?" /><published>2026-11-03T09:00:00-08:00</published><updated>2026-11-03T09:00:00-08:00</updated><id>https://fzakaria.com/2026/11/03/why-does-shuffling-the-schedule-find-so-many-bugs</id><content type="html" xml:base="https://fzakaria.com/2026/11/03/why-does-shuffling-the-schedule-find-so-many-bugs"><![CDATA[<p>Here is something to think about: bugs are not rare, but rather the orderings of events that trigger them are. What do I mean by this?</p>

<!--more-->

<p>Let’s start with a simple story. When I worked at AWS on RDS (Relational Database Service), we built the control plane for the service using workflows, of course using SWF (Simple Workflow). The workflows would execute a series of steps to create, modify or delete a database instance. Every so often we’d see something that looked impossible: a workflow that seemed to run its steps out of order.</p>

<p>What we actually observed was a pause. A worker would pick up a step and then stop for an unexpectedly long time. While paused it stopped heartbeating, so SWF decided it was dead and reassigned the step to another worker. The new worker finished the step and the workflow moved on. Then the first worker woke up. As far as it could tell no time had passed, so it carried on with the step it was holding, causing havoc in its wake.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/d538c6f3a7d7efa3.svg" alt="A timeline with three lanes: worker 1, SWF and worker 2. Worker 1 runs snapshot, starts apply config, then pauses. SWF hears no heartbeat, times the step out and hands apply config to worker 2, which runs it and then runs reboot. Worker 1 wakes up and finishes apply config after the reboot, and SWF rejects its result. The instance saw snapshot, apply config, reboot, apply config." width="720" height="236">
</figure>

<p>We suddenly realized that we were not adequately testing all the possible schedules of events. The workflow code was correct, but the schedule that triggered the bug was not one we had ever seen in our tests. It was a rare schedule, but it was not a rare bug. So we began adding idempotency tokens to every step and making sure that every step could be run multiple times without breaking anything. It was a lot of work, but it made the system much more robust.</p>

<p>What’s the purpose of this story?</p>

<p>I am building <a href="https://rewindvm.dev">Rewind VM</a>, a deterministic VM. It helps find bugs in software primarily by running the same code under many different schedules.<sup id="fnref:schedule"><a href="#fn:schedule" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> The mere act of shuffling the schedule has found <a href="https://rewindvm.dev/bugs.html">many bugs</a> thus far.</p>

<p>The software under test is not even a distributed system, such as the workflow example above, and yet many of the bugs are still schedule-dependent. No crazy Jepsen test suite needed. How can this be? All these projects have tests that validate the code on every change. Why does shuffling the schedule find so many bugs that all those tests never did?</p>

<p>The bugs aren’t rare; the schedules your tests run are. An idle, fast machine runs the same handful of orderings nearly every time. A test passes because the order never changes, and the code may only ever have handled that one order.</p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
New to Rewind? Start with <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a> and <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a>.</p>
</blockquote>

<p>It takes different schedules or unforeseen long pauses, similar to the workflow story above, to trigger these bugs. The bugs are not rare, but the schedules that trigger them are. How rare are they?</p>

<p>The smallest race I know is <code class="language-plaintext highlighter-rouge">counter++</code> from two threads.</p>

<div class="side-by-side">

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="n">counter</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">increment</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">counter</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">increment:</span>
    <span class="nf">mov</span>  <span class="nb">rax</span><span class="p">,</span> <span class="p">[</span><span class="nv">counter</span><span class="p">]</span>  <span class="c1">; load</span>
    <span class="nf">add</span>  <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>          <span class="c1">; add</span>
    <span class="nf">mov</span>  <span class="p">[</span><span class="nv">counter</span><span class="p">],</span> <span class="nb">rax</span>  <span class="c1">; store</span>
    <span class="nf">ret</span>
</code></pre></div>  </div>

</div>

<p>Two threads run <code class="language-plaintext highlighter-rouge">increment</code> once each. Each thread’s three steps execute in order, but the two threads can interleave. An ordering is a choice of which 3 of the 6 slots belong to thread A, so there are <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mrow><mo>&#x00028;</mo><mfrac linethickness="0"><mrow><mn>6</mn></mrow><mrow><mn>3</mn></mrow></mfrac><mo>&#x00029;</mo><mo>&#x0003D;</mo><mn>20</mn></mrow></math> of them.</p>

<p>We can visualize the 20 choices as paths on a grid. A step right is a step of thread A, a step down is a step of thread B, and every path from the top-left corner to the bottom-right one is one ordering.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/7e57f7a084c36e55.svg" alt="Left: a 3 by 3 grid. Moving right is a step of thread A, moving down a step of thread B, so every path from the top-left corner to the bottom-right corner is one interleaving, 20 in all. The four inner points, where both threads have loaded and neither has stored, are shaded: every path through them loses an update. Only the two paths along the edges, A then B and B then A, avoid them. Right: the 20 orderings listed, each with the counter it ends with. AAABBB and BBBAAA end at 2; the other 18 end at 1." width="720" height="300">
</figure>

<p>The shaded square holds the four points where both threads have loaded the counter and neither has stored it. Any path that enters it ends with the counter at 1, because the second writer stores the stale original value.</p>

<p>Only the two paths along the edges provide the “correct result” of 2, A entirely before B or B entirely before A. That is 18 of 20 orderings that lose an update. If every ordering were equally likely, this race would fire in 9 runs out of 10. 😱</p>

<p>What does this look like on a real machine?</p>

<p>Let’s build a small program that runs the two threads and records the order of their steps. The program is a bit more complicated than the simple <code class="language-plaintext highlighter-rouge">counter++</code> above, because it has to record the order of the steps. It also has to be careful not to introduce any new races in the recording itself. The program leverages Rewind’s <code class="language-plaintext highlighter-rouge">rewind_point()</code> to give Rewind VM a place to switch threads.<sup id="fnref:barrier"><a href="#fn:barrier" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<details>
  <summary>race.c</summary>

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * Two threads each add one to a shared counter in three steps: load the
 * counter, add one, store it back. No lock, so a thread that runs between
 * the other's load and store writes a stale value over it.
 *
 * Each step first takes the next slot of a shared atomic sequence and
 * writes its thread's letter there, so the program prints the order the
 * six steps ran in, such as AABABB, and the final counter.
 *
 * rewind_point() between the steps gives Rewind VM a place to switch
 * threads. main() calls it once before the threads start, so the one
 * failed open it makes outside Rewind happens there; on the host the
 * points inside the threads are a load and a branch.
 *
 * Built with -DSTART_BARRIER, both threads wait on a barrier before the
 * first step, so they start as close together as the host allows.
 *
 * The exit status encodes the outcome so `rewind check` reports it
 * without reading output: ordering index (0..19) * 2 + (1 if an update
 * was lost).
 */</span>
<span class="cp">#include</span> <span class="cpf">&lt;pthread.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdatomic.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span>
<span class="cp">#include</span> <span class="cpf">"rewind.h"</span><span class="cp">
</span>
<span class="k">enum</span> <span class="p">{</span> <span class="n">THREADS</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">STEPS</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="n">MARKS</span> <span class="o">=</span> <span class="n">THREADS</span> <span class="o">*</span> <span class="n">STEPS</span> <span class="p">};</span>

<span class="k">static</span> <span class="k">volatile</span> <span class="kt">long</span> <span class="n">counter</span><span class="p">;</span>
<span class="k">static</span> <span class="n">atomic_int</span> <span class="n">next_mark</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">char</span> <span class="n">order</span><span class="p">[</span><span class="n">MARKS</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>

<span class="cp">#ifdef START_BARRIER
</span><span class="k">static</span> <span class="n">pthread_barrier_t</span> <span class="n">start</span><span class="p">;</span>
<span class="cp">#endif
</span>
<span class="cm">/* Record that thread `who` is taking its next step. */</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">mark</span><span class="p">(</span><span class="kt">char</span> <span class="n">who</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">order</span><span class="p">[</span><span class="n">atomic_fetch_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">next_mark</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span> <span class="o">=</span> <span class="n">who</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="o">*</span><span class="nf">increment</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">char</span> <span class="n">who</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>

<span class="cp">#ifdef START_BARRIER
</span>	<span class="n">pthread_barrier_wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">start</span><span class="p">);</span>
<span class="cp">#endif
</span>	<span class="n">mark</span><span class="p">(</span><span class="n">who</span><span class="p">);</span>
	<span class="kt">long</span> <span class="n">seen</span> <span class="o">=</span> <span class="n">counter</span><span class="p">;</span> <span class="cm">/* load */</span>
	<span class="n">rewind_point</span><span class="p">();</span>
	<span class="n">mark</span><span class="p">(</span><span class="n">who</span><span class="p">);</span>
	<span class="n">seen</span> <span class="o">=</span> <span class="n">seen</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="cm">/* add */</span>
	<span class="n">rewind_point</span><span class="p">();</span>
	<span class="n">mark</span><span class="p">(</span><span class="n">who</span><span class="p">);</span>
	<span class="n">counter</span> <span class="o">=</span> <span class="n">seen</span><span class="p">;</span> <span class="cm">/* store */</span>
	<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* The index of `s` among the 20 orderings of AAABBB, in sorted order. */</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">ordering_index</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">a</span> <span class="o">&lt;</span> <span class="mi">64</span><span class="p">;</span> <span class="n">a</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">char</span> <span class="n">candidate</span><span class="p">[</span><span class="n">MARKS</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>
		<span class="kt">int</span> <span class="n">as</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

		<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">MARKS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">candidate</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">a</span> <span class="o">&gt;&gt;</span> <span class="p">(</span><span class="n">MARKS</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">i</span><span class="p">))</span> <span class="o">&amp;</span> <span class="mi">1</span> <span class="o">?</span> <span class="sc">'B'</span> <span class="o">:</span> <span class="sc">'A'</span><span class="p">;</span>
			<span class="n">as</span> <span class="o">+=</span> <span class="n">candidate</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'A'</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">candidate</span><span class="p">[</span><span class="n">MARKS</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">as</span> <span class="o">!=</span> <span class="n">STEPS</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">continue</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">candidate</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="n">index</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">index</span><span class="o">++</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">names</span><span class="p">[</span><span class="n">THREADS</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="sc">'A'</span><span class="p">,</span> <span class="sc">'B'</span> <span class="p">};</span>
	<span class="n">pthread_t</span> <span class="n">threads</span><span class="p">[</span><span class="n">THREADS</span><span class="p">];</span>

	<span class="cm">/* Open /dev/rewind, or find it missing, before any thread runs. */</span>
	<span class="n">rewind_point</span><span class="p">();</span>

<span class="cp">#ifdef START_BARRIER
</span>	<span class="n">pthread_barrier_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">start</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">THREADS</span><span class="p">);</span>
<span class="cp">#endif
</span>	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">THREADS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">threads</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">increment</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">names</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
	<span class="p">}</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">THREADS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">pthread_join</span><span class="p">(</span><span class="n">threads</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="kt">int</span> <span class="n">lost</span> <span class="o">=</span> <span class="n">counter</span> <span class="o">!=</span> <span class="n">THREADS</span><span class="p">;</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"%s %ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">order</span><span class="p">,</span> <span class="n">counter</span><span class="p">);</span>
	<span class="k">return</span> <span class="n">ordering_index</span><span class="p">(</span><span class="n">order</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="n">lost</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>I ran both builds, with and without the barrier, 10,000 times each on my laptop, a Ryzen 7 7840U with 16 idle hardware threads.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>10000<span class="si">)</span><span class="p">;</span> <span class="k">do</span> ./race<span class="p">;</span> <span class="k">done</span> | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-c</span> | <span class="nb">sort</span> <span class="nt">-rn</span>
<span class="go">   9985 AAABBB 2
     15 BBBAAA 2
</span><span class="gp">$</span><span class="w"> </span><span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>10000<span class="si">)</span><span class="p">;</span> <span class="k">do</span> ./race-barrier<span class="p">;</span> <span class="k">done</span> | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-c</span> | <span class="nb">sort</span> <span class="nt">-rn</span>
<span class="go">   9957 BBBAAA 2
     39 AAABBB 2
      2 BBAAAB 1
      1 BAAABB 1
      1 BAABAB 1
</span></code></pre></div></div>

<figure class="plotnine" title="Two panels of runs per ordering, on a log scale, across the 20 orderings. With pthread_create alone, 9,985 runs are AAABBB and 15 are BBBAAA. With a barrier, 9,957 are BBBAAA, 39 are AAABBB, and 4 runs spread over three orderings that lose an update. The other orderings never ran."><img src="https://fzakaria.com/assets/figures/6b885b421d58969d.svg" alt="Two panels of runs per ordering, on a log scale, across the 20 orderings. With pthread_create alone, 9,985 runs are AAABBB and 15 are BBBAAA. With a barrier, 9,957 are BBBAAA, 39 are AAABBB, and 4 runs spread over three orderings that lose an update. The other orderings never ran." width="518" height="346">
</figure>

<p>Across 20,000 runs the idle laptop ran 5 of the 20 orderings and lost an update 4 times.
Interestingly, the barrier was supposed to make them collide. Instead it flipped the order such that 9,957 runs were <code class="language-plaintext highlighter-rouge">BBBAAA</code>.</p>

<p>A busy machine is a little different. With <code class="language-plaintext highlighter-rouge">stress-ng --cpu 16</code> keeping every hardware thread busy, the same two binaries ran 13 and 16 of the orderings, and lost an update in 32 and 66 of 10,000 runs. That’s still fewer than 1 run in 100 losing an update.</p>

<p style="--image-width: 20rem"><a href="/assets/images/rewind_buzz_aaabbb_meme.png"><img src="/assets/images/rewind_buzz_aaabbb_meme.png" alt="X, X everywhere meme: Buzz Lightyear, his arm around a worried Woody, points into the distance: &quot;AAABBB&quot;, &quot;AAABBB everywhere&quot;" /></a></p>

<p>Rewind runs the guest on one virtual CPU and only switches threads at an exit, such as a system call (see <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a>) or a <code class="language-plaintext highlighter-rouge">rewind_point</code> SDK call. This gives Rewind a chance to modify the schedule.</p>

<p>We can then run the same binary with Rewind to see how many of the 20 orderings it can reach.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--epoch</span> 1790985600 <span class="nt">--root</span> root <span class="nt">--schedules</span> 9999 <span class="se">\</span>
<span class="go">    --all --no-narrow -- /bin/race
</span><span class="c">...
</span><span class="go">schedule 9999: exited:0              354 steps    run 9b248f72f742b07f
1492 of 9999 other schedules ended differently
</span></code></pre></div></div>

<p>Across 10,000 runs Rewind reached 15 of the 20 orderings and lost an update in 1,371 of them. With a barrier it reached all 20 orderings and lost an update in 2,997 of them.</p>

<figure class="plotnine" title="Two panels of runs per ordering, on a log scale, under rewind check. With pthread_create alone, 8,508 runs are AAABBB and the rest spread over 14 more orderings, 1,371 of them losing an update. With a barrier, all 20 orderings appear, from 5,715 runs of AAABBB down to 1 of BABABA, and 2,997 runs lose an update."><img src="https://fzakaria.com/assets/figures/1012003739fad8dc.svg" alt="Two panels of runs per ordering, on a log scale, under rewind check. With pthread_create alone, 8,508 runs are AAABBB and the rest spread over 14 more orderings, 1,371 of them losing an update. With a barrier, all 20 orderings appear, from 5,715 runs of AAABBB down to 1 of BABABA, and 2,997 runs lose an update." width="518" height="346">
</figure>

<p>We can observe that Rewind’s perturbations reach more orderings than the host, and that it loses an update more often, which helps us find the bug. The table below summarizes the results:</p>

<table>
  <thead>
    <tr>
      <th>Where</th>
      <th>Start</th>
      <th>Orderings seen</th>
      <th>Runs that lost an update</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>idle laptop</td>
      <td>create</td>
      <td>2 of 20</td>
      <td>0 of 10,000</td>
    </tr>
    <tr>
      <td>idle laptop</td>
      <td>barrier</td>
      <td>5 of 20</td>
      <td>4 of 10,000</td>
    </tr>
    <tr>
      <td>busy laptop</td>
      <td>create</td>
      <td>13 of 20</td>
      <td>32 of 10,000</td>
    </tr>
    <tr>
      <td>busy laptop</td>
      <td>barrier</td>
      <td>16 of 20</td>
      <td>66 of 10,000</td>
    </tr>
    <tr>
      <td>Rewind</td>
      <td>create</td>
      <td>15 of 20</td>
      <td>1,371 of 10,000</td>
    </tr>
    <tr>
      <td>Rewind</td>
      <td>barrier</td>
      <td>20 of 20</td>
      <td>2,997 of 10,000</td>
    </tr>
  </tbody>
</table>

<p>Interleavings, though, are not the only way to trigger bugs. Long pauses are orderings too, and they can be even rarer than interleavings.</p>

<p>A long pause sits at the far edge of the grid. Thread A stops, and everything else takes hundreds of steps before A takes its next one. A path like that runs along one edge of the grid for a long time. A short perturbation hands the other side a few steps, so it rarely walks that far.</p>

<p>Rewind has knobs to make long pauses more likely. The <code class="language-plaintext highlighter-rouge">--stall-max</code> knob raises the maximum stall time, and the <code class="language-plaintext highlighter-rouge">--stall-min</code> knob lowers the minimum stall time. These settings can help explore even more unlikely schedules to discover bugs that only appear under extreme conditions.</p>

<p>It’s kind of surprising that with all the hype around property-based testing, fuzzing, and Jepsen, a simple deterministic VM that shuffles the schedule finds so many bugs.
Our hardware is so fast that we’ve become accustomed to thinking that the order of events is always the same. Adding a little randomness to the schedule can expose bugs that have been hiding in plain sight.</p>

<p>Bugs aren’t rare, but the schedules that trigger them are.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:schedule">
      <p>I say <em>schedule</em> rather than <em>thread interleaving</em> on purpose. Most of the bugs on that page are races between processes, or between a process and a signal. Only a few are between two threads of one program. <a href="#fnref:schedule" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:barrier">
      <p>The program also has a version that uses a barrier so the threads collide on purpose, which should make the race even more likely. <a href="#fnref:barrier" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Here is something to think about: bugs are not rare, but rather the orderings of events that trigger them are. What do I mean by this?]]></summary></entry><entry><title type="html">A clock made of branches</title><link href="https://fzakaria.com/2026/11/01/a-clock-made-of-branches" rel="alternate" type="text/html" title="A clock made of branches" /><published>2026-11-01T09:00:00-08:00</published><updated>2026-11-01T09:00:00-08:00</updated><id>https://fzakaria.com/2026/11/01/a-clock-made-of-branches</id><content type="html" xml:base="https://fzakaria.com/2026/11/01/a-clock-made-of-branches"><![CDATA[<p><em>What is time inside a machine you want to replay?</em> 🧘</p>

<p>When building <a href="http://rewindvm.dev/">Rewind</a>, a deterministic VM, you come face-to-face with the idea of how to represent time very quickly. You need a way to measure time inside the guest so that on subsequent replays, the guest sees the same time and makes the same decisions. Time is the ultimate variable.</p>

<!--more-->

<p>You can’t simply record the wall-clock time of the host because time spent between two interception points depends on things outside the guest, like host load, CPU frequency, caches and other processes. Therefore the same guest work can take a different amount of time on the host.</p>

<p>In <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a> I explained how the clock inside <a href="https://rewindvm.dev">Rewind VM</a> moves 5 microseconds per step. More specifically, that clock, <em>exit time</em>, moves only when the guest exits such as from a <code class="language-plaintext highlighter-rouge">syscall</code>. It treats everything the program does between two exits as <strong>instant</strong>. Therefore a program that is CPU bound such as doing nothing but math for a full second between two system calls, sees only 5µs go by. 🤦🏻‍♂️</p>

<p>Rewind has a second clock mode as well, <em>counter time</em>, that tries to account for the work. The clock advances one nanosecond for every <strong>conditional branch</strong> the guest retired.<sup id="fnref:rr"><a href="#fn:rr" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p style="--image-width: 20rem"><a href="/assets/images/rewind_morpheus_time_meme.png"><img src="/assets/images/rewind_morpheus_time_meme.png" alt="Matrix Morpheus meme: Morpheus in his sunglasses says &quot;What if I told you time is just counting branches&quot;" /></a></p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
New to Rewind? Start with <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a> and <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a></p>
</blockquote>

<p>Still confused about what I mean by “free” and “branches”? Let’s look at a simple example to distinguish between the two clocks.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/d4be2da3c7c76fbb.svg" alt="A guest makes four exits with work between them: 2,000 branches, then one second of math worth a billion branches, then 3,000 branches. Exit time charges 5 microseconds for each exit and nothing for the work, so the clock reads 20 microseconds at the end. Counter time charges 5 microseconds for each exit plus a nanosecond per branch of work: 2 microseconds, 1 second and 3 microseconds, so the clock reads 1.000025 seconds." width="720" height="250">
</figure>

<p>The counter time tries to account for the work whereas the exit time only advanced the clock on exits.<sup id="fnref:exits"><a href="#fn:exits" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>Why branches, though?</p>

<h2 id="what-is-time-in-a-replayed-machine">What is time in a replayed machine?</h2>

<p>We already understand that we cannot rely on the wall-clock time of the host. We need something to measure that is the same for every run of the same program so that we can equate it to time spent.</p>

<p>What <em>is</em> the same on every run?</p>

<p>The guest’s instruction stream! The CPU is a deterministic machine: the same input, the same registers, the same memory, and the same instructions produce the same next state.</p>

<p>We can measure time by measuring progress through the instruction stream. 💡</p>

<p>Turns out that CPU manufacturers even ship a hardware counter for exactly that. Is it reliable? Let’s see. 🧐</p>

<p>Let’s use this simple program: count down from a billion.</p>

<div class="side-by-side">

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">long</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1000000000</span><span class="p">;</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="n">i</span><span class="o">--</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">);</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">_start:</span>
    <span class="nf">mov</span>  <span class="nb">rcx</span><span class="p">,</span> <span class="mi">1000000000</span>
<span class="nl">.loop:</span>
    <span class="nf">dec</span>  <span class="nb">rcx</span>
    <span class="nf">jnz</span>  <span class="nv">.loop</span>
    <span class="nf">mov</span>  <span class="nb">eax</span><span class="p">,</span> <span class="mi">60</span>    <span class="c1">; exit(0)</span>
    <span class="nf">xor</span>  <span class="nb">edi</span><span class="p">,</span> <span class="nb">edi</span>
    <span class="nf">syscall</span>
</code></pre></div>  </div>

</div>

<p>The assembly equivalent contains a <code class="language-plaintext highlighter-rouge">mov</code>, <strong>a billion</strong> <code class="language-plaintext highlighter-rouge">dec</code>+<code class="language-plaintext highlighter-rouge">jnz</code> (conditional branch) pairs, then three instructions to exit for a total of <strong>2,000,000,004</strong> instructions.</p>

<p>Let’s run it on the CPU twice and count the instructions and conditional branches with <code class="language-plaintext highlighter-rouge">perf stat</code>:</p>

<div data-mark="3,7" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nasm <span class="nt">-felf64</span> loop.asm <span class="o">&amp;&amp;</span> ld <span class="nt">-o</span> loop loop.o
<span class="gp">$</span><span class="w"> </span>taskset <span class="nt">-c</span> 5 perf <span class="nb">stat</span> <span class="nt">-e</span> instructions:u,ex_ret_cond:u ./loop
<span class="go">     2,000,001,025      instructions:u
     1,000,000,000      ex_ret_cond:u

</span><span class="gp">$</span><span class="w"> </span>taskset <span class="nt">-c</span> 5 perf <span class="nb">stat</span> <span class="nt">-e</span> instructions:u,ex_ret_cond:u ./loop
<span class="go">     2,000,001,054      instructions:u
     1,000,000,000      ex_ret_cond:u
</span></code></pre></div></div>

<p>The same program and input gives the same conditional branch count, but the instruction count is off by a thousand or so. Why?</p>

<p style="--image-width: 16rem"><a href="/assets/images/rewind_panik_instructions_meme.png"><img src="/assets/images/rewind_panik_instructions_meme.png" alt="Panik kalm panik meme: &quot;My clock counts instructions&quot; (panik), &quot;The program can't see interrupts&quot; (kalm), &quot;The instruction counter can&quot; (panik)" /></a></p>

<p>Although this loop is CPU bound and only decrements a register, it is still not isolated from the effects of the outside world. Timers tick, network packets arrive, other cores send inter-processor interrupts (IPIs). The program can’t see any of that, but the instruction counter we are probing can.</p>

<p>I pinned the loop to a single CPU and counted how many interrupts that CPU took during each run, from <code class="language-plaintext highlighter-rouge">/proc/interrupts</code>.</p>

<figure class="plotnine" title="Two panels of 120 runs each, plotting extra counts against the interrupts taken during the run. Instructions sit on a dashed line of one extra per interrupt, from about 60 to 1,300. Conditional branches are zero in every run."><img src="https://fzakaria.com/assets/figures/c0b6a2a88aeffb3f.svg" alt="Two panels of 120 runs each, plotting extra counts against the interrupts taken during the run. Instructions sit on a dashed line of one extra per interrupt, from about 60 to 1,300. Conditional branches are zero in every run." width="518" height="230">
</figure>

<p>The dashed line is <em>one extra count per interrupt</em>, and the instruction counter is almost perfectly on it.<sup id="fnref:slope"><a href="#fn:slope" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> 
The conditional branch counter on the other hand, stays at zero no matter how many interrupts land.</p>

<p>Interrupts from the outside-world are not the only thing that can skew the instruction count. The program can also page fault, which is an interrupt too. The instruction counter sees the kernel’s detour to handle the fault, but the program does not.</p>

<blockquote class="alert alert-tip">
  <p><strong>Tip</strong>
Out of the box, AMD Zen also overcounts conditional branches, a few in tens of millions, from a speculation optimization known as SpecLockMap. The fix is to set a particular bit in the PMU MSR, which Rewind does automatically as does <a href="https://github.com/rr-debugger/rr/issues/2034">rr#2034</a>.</p>
</blockquote>

<h2 id="so-what-time-is-it">So, what time is it?</h2>

<p>In order to rewind history, we need a measurement of time that is reliable and deterministic. Our traditional wall-clock time is unreliable due to the effects of the outside world but we do have a reliable measurement of progress through the instruction stream: the conditional branch counter.</p>

<p>Using the conditional branch counter as a proxy for time turns out to be a relatively decent approximation.</p>

<table>
  <thead>
    <tr>
      <th>Clock</th>
      <th>Time inside the VM</th>
      <th>Time on the host</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Exit time</td>
      <td>2.1 s</td>
      <td>15.7 s</td>
    </tr>
    <tr>
      <td>Counter time</td>
      <td>11.7 s</td>
      <td>14.2 s</td>
    </tr>
  </tbody>
</table>

<p>Why do we care about having a “realistic” representation of time?</p>

<p>Plenty of programs read the clock and make decisions with it, so the guest’s clock has to be at least somewhat realistic. Under exit time, computing is “free”. A test that checks some work “finishes within 1 s” always passes. If the bug you’re chasing is a timeout, exit time will never reproduce it. 🙃</p>

<p>Races get skewed too with an unrealistic clock. With exit time, a whole burst of work lands in the same instant, which crowds the threads together, so a race can fire far more often in the VM than it ever would on real hardware.</p>

<p>The closer we can emulate the real world, the more likely we are to reproduce a bug that only happens in the real world. Counter time is a better approximation of the real world than exit time, and it is deterministic, so it is the default clock in Rewind.</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_distracted_branches_meme.png"><img src="/assets/images/rewind_distracted_branches_meme.png" alt="Distracted boyfriend meme: the boyfriend, labelled &quot;Rewind's clock&quot;, turns to look at a woman labelled &quot;conditional branches&quot; while his girlfriend, labelled &quot;instructions retired&quot;, glares at him" /></a></p>

<p>Some say time is an illusion. I say it’s a <code class="language-plaintext highlighter-rouge">jnz</code> counter.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:rr">
      <p>This is the same approach popularized by the <a href="https://rr-project.org/">rr</a> debugger. The <a href="https://arxiv.org/abs/1705.05937">rr paper</a> calls it <em>deterministic time</em>. <a href="#fnref:rr" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:exits">
      <p>The 5µs was my guess at a reasonable cost of a system call and context switch on current hardware. The 1ns is roughly what a conditional branch costs on a current core. <a href="#fnref:exits" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:slope">
      <p>The fit is 0.94 rather than 1 because <code class="language-plaintext highlighter-rouge">/proc/interrupts</code> also catches the interrupts that land while <code class="language-plaintext highlighter-rouge">perf</code> and <code class="language-plaintext highlighter-rouge">taskset</code> start up and tear down, before and after the loop runs. <a href="#fnref:slope" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[What is time inside a machine you want to replay? 🧘 When building Rewind, a deterministic VM, you come face-to-face with the idea of how to represent time very quickly. You need a way to measure time inside the guest so that on subsequent replays, the guest sees the same time and makes the same decisions. Time is the ultimate variable.]]></summary></entry><entry><title type="html">Reading the code is the hard part</title><link href="https://fzakaria.com/2026/10/28/reading-the-code-is-the-hard-part" rel="alternate" type="text/html" title="Reading the code is the hard part" /><published>2026-10-28T10:00:00-07:00</published><updated>2026-10-28T10:00:00-07:00</updated><id>https://fzakaria.com/2026/10/28/reading-the-code-is-the-hard-part</id><content type="html" xml:base="https://fzakaria.com/2026/10/28/reading-the-code-is-the-hard-part"><![CDATA[<p>I have been stress testing <a href="https://rewindvm.dev">Rewind VM</a>, a deterministic VM, to work through the kinks, bugs and <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">dog-fooding</a> the user-experience. Even having developed it myself, I continue to be wild-eye amazed at the magic of what it can do.</p>

<!--more-->

<p>Despite the magic, the ease of claiming to have a bug “reproduced deterministically”, and the ease of narrowing it down to a single step, the hard part is still reading the code and understanding why it failed. Rewind can tell you what went wrong, but it cannot read the code.</p>

<p>This is a particular area that is ripe for AI assistance. Agents can already read the code and often find bugs. Unsurprisingly, when given a wealth of reliable data about what went wrong, they can pinpoint the bug in a way that is much more efficient than reading the code alone. The combination of deterministic runs and AI agents is incredibly powerful. 🪄</p>

<p>Finding the bugs in Rewind is easy. <code class="language-plaintext highlighter-rouge">rewind check</code> replays a test, script, or binary under many different thread schedules until one fails, then narrows the failure down to the single step. That leaves you, though, with a step number and potentially a large codebase, one you are unfamiliar with. The hard part is reading the code and understanding why it failed.</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_gru_plan_meme.png"><img src="/assets/images/rewind_gru_plan_meme.png" alt="Gru's plan meme: &quot;rewind check finds a failing schedule&quot;, &quot;It narrows it to step 2682&quot;, &quot;Now I read 3,000 lines of git&quot;, and Gru staring back at &quot;Now I read 3,000 lines of git?&quot;" /></a></p>

<p>Rewind can tell you what went wrong, but it cannot read the code for you. Rewind does however give you, <em>or an agent</em>, a lot of information about what went wrong. I pointed <a href="https://rewindvm.dev">Rewind VM</a> at a cornicopia of tools, and unsurprisingly, it found bugs…<em>many bugs</em>. The best part is that it found them in a way that is reproducible and debuggable, which has led to the agents also easily providing the fixes as well.<sup id="fnref:bugs"><a href="#fn:bugs" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
New to Rewind? Start with <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a> and <a href="/2026/10/24/dafuq-is-a-deterministic-vm">dafuq is a deterministic VM?</a></p>
</blockquote>

<p>This is a small example of a bug that Rewind found in git, and how I used Rewind to find the bug and the line of code that caused it. The bug is in <code class="language-plaintext highlighter-rouge">git notes</code>, which attaches a note to a commit without changing the commit. The bug is that two notes, on two different commits, at the same time, can lose one of the notes.<sup id="fnref:git"><a href="#fn:git" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<h2 id="the-bug">The bug</h2>

<p><a href="https://git-scm.com/docs/git-notes"><code class="language-plaintext highlighter-rouge">git notes</code></a> attaches a note to a commit without changing the commit.</p>

<p>We can start with an idea for a simple test<sup id="fnref:test"><a href="#fn:test" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>: create two notes, on two different commits, at the same time concurrently.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git notes add <span class="nt">-m</span> <span class="s2">"note 1"</span> HEAD~0 &amp;
git notes add <span class="nt">-m</span> <span class="s2">"note 2"</span> HEAD~1 &amp;
<span class="nb">wait</span>

<span class="c"># both commands exited 0, so both notes should be there</span>
git notes show HEAD~0 <span class="o">&amp;&amp;</span> git notes show HEAD~1
</code></pre></div></div>

<div data-mark="5,7" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--all</span> <span class="nt">--schedules</span> 32 <span class="nt">--property</span> exit-zero .#notes2
<span class="go">schedule   1: exited:1             3167 steps    run e5e5a1ecd0195f8f  violates exit-zero
schedule   2: exited:1             3312 steps    run ea6889e56564e374  violates exit-zero
</span><span class="c">...
</span><span class="go">15 of 32 other schedules violated a property

step 2682 decides it: a request to switch threads there makes the run violate exit-zero
</span></code></pre></div></div>

<p>When executed by Rewind, fifteen of thirty-two schedules lose a git-note, yet every <code class="language-plaintext highlighter-rouge">git notes add</code> still exits successfully.<sup id="fnref:normal"><a href="#fn:normal" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>Now what?</p>

<h2 id="ask-the-runs-not-the-code">Ask the runs, not the code</h2>

<p>A run in Rewind is a full trace of the build: every process, every file it wrote, and any file’s contents at any step, that you replay to any point in time. <strong>That is a lot of data</strong>. Rewind does make it a little simpler to comb through the data though that help narrow the culprit.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/d0b7a17807cc3fe7.svg" alt="Three boxes in a row joined by arrows. One: what ended differently, a file, an output or an exit status. Two: who wrote it, the process and the step, which rewind blame answers. Three: which line wrote it, which rewind where answers." width="720" height="150">
</figure>

<p>None of those three commands knows anything about git, locks or races. They work on any program.</p>

<h3 id="what-ended-differently">What ended differently?</h3>

<p><code class="language-plaintext highlighter-rouge">rewind diff --outputs</code> compares the files the two runs left behind. This is like <a href="https://diffoscope.org/">diffoscope</a> but on steroids 💉: it can compare any two runs, and it can compare the files at any step in the run, not just the final outputs.<sup id="fnref:byte"><a href="#fn:byte" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<div data-mark="4" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind diff <span class="nt">--outputs</span> 94c59fdf987c4944 ad0de5aec5a0be2d
<span class="go">3 of the 30 files the runs left behind differ:
  /build/r/.git/logs/refs/notes/commits: 36 of 465 bytes differ, first at byte 351
  /build/r/.git/refs/notes/commits: 36 of 41 bytes differ, first at byte 0
  /nix/store/014fz1lj7hmk5h3rzcaw0w4576md3bg9-git-notes2: only in the right run
</span></code></pre></div></div>

<p>Our clue is that <code class="language-plaintext highlighter-rouge">.git/refs/notes/commits</code> is the ref that points at the latest notes commit, and it points at different commits in the two runs.</p>

<h3 id="who-wrote-it-and-what-did-it-read">Who wrote it, and what did it read?</h3>

<p><code class="language-plaintext highlighter-rouge">rewind blame</code> takes two runs and a file:</p>

<div data-mark="5-8" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind blame 94c59fdf987c4944 ad0de5aec5a0be2d /build/r/.git/refs/notes/commits
<span class="go">commits differs at byte 0. git notes (pid 56) wrote that byte at step
2,751 in run 94c59fdf and at step 2,717 in run ad0de5ae.
</span><span class="c">...
</span><span class="go">Likely cause: git notes (pid 56) read /build/r/.git/refs/notes/commits
before git notes (pid 55) changed it at step 2,721 in run 94c59fdf,
and after that change in run ad0de5ae. It read an older version of
the file in run 94c59fdf.
</span></code></pre></div></div>

<p>In this example, Rewind has given us pretty close to the smoking gun. The second <code class="language-plaintext highlighter-rouge">git notes add</code> read the notes before the first one had saved its own, then saved its version over the top: a read-modify-write race.</p>

<p>We can visualize this exact step in the Rewind GUI at the deciding step: both <code class="language-plaintext highlighter-rouge">git notes add</code> processes are alive (top box), and the first is halfway through saving, with its lock file written but not yet renamed into place (bottom box).</p>

<p><a href="/assets/images/rewind-git-notes-race.png"><img src="/assets/images/rewind-git-notes-race-crop.png" alt="The Rewind app at step 2,682 of the failing run: a box around both git notes add processes in the process tree, and a box around refs/notes/commits.lock in the files list" /></a></p>

<h3 id="which-line-wrote-it">Which line wrote it?</h3>

<p>If we have access to debug symbols, which Nixpkgs provides,  <code class="language-plaintext highlighter-rouge">rewind where</code> shows where in git’s own code a process was at a step. Here is pid 56 when it wrote the ref:</p>

<div data-mark="10" class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind where 94c59fdf987c4944 2754 <span class="nt">--pid</span> 56 <span class="nt">--frames</span> 8
<span class="go">process 56 (git), thread 56, at step 2754
</span><span class="c">#1 rename_tempfile (tempfile.c:347)
</span><span class="go">called from #2 commit_lock_file_to (/build/source/lockfile.h:364)
called from #3 commit_lock_file (lockfile.c:358)
called from #4 commit_ref (refs/files-backend.c:1889)
called from #5 files_transaction_finish (refs/files-backend.c:3381)
called from #6 ref_transaction_commit (refs.c:2806)
called from #7 refs_update_ref (refs.c:1592)
called from #8 commit_notes (notes-utils.c:64)
</span></code></pre></div></div>

<p>We can then look at the code at a very specific line, and try to understand why it failed. 
In this case, <a href="https://github.com/git/git/blob/8d1b215c01512b9658d4525c54ff9b9245f9a7e8/notes-utils.c#L64"><code class="language-plaintext highlighter-rouge">notes-utils.c:64</code></a> updates the ref without checking that it still points where it did when the command loaded the notes with a lock.</p>

<p>Three questions, three commands, and I read one line of git. 🤓</p>

<h2 id="who-reads-all-of-this">Who reads all of this?</h2>

<p>The git notes failure is 2,936 steps and 1,937 events in one run, and <code class="language-plaintext highlighter-rouge">rewind check</code> made 33 runs. Going through one run is a lot of work, and going through all thirty-three is unrealistic.</p>

<p>Recently, a friend told me AI has been really useful for parsing the <a href="https://en.wikipedia.org/wiki/CAN_bus">CAN bus</a> of cars. The CAN bus is a firehose of messages from the car from every sensor, every button, every switch, and every light. LLMs have made managing the firehose of data easier.</p>

<p>I see the same emergence in Rewind. The run is also a firehose of events, and similarly, LLMs can help parse the firehose of events and find the bugs. Telling an LLM to investigate a bug with Rewind has been very effective. Narrowing the bug down to a single step and source lines, is especially helpful for an LLM as it keeps the context small and focused. At that point, the LLM can read the code and understand what went wrong, and very likely suggest a fix.</p>

<p>LLMs have a lot of stochasticity built into them. I have found that pairing them with more deterministic technologies such as <a href="https://nixos.org/">Nix</a> and Rewind makes them more effective. I’m very optimistic about the
leveraging the combination of deterministic runs and LLMs to find and fix bugs in large codebases.</p>

<p>LLMs induce many of these bugs but now they can also fix them. 🙃</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:bugs">
      <p>You can find all the bugs on the <a href="https://rewindvm.dev/bugs.html">bugs page</a>. <a href="#fnref:bugs" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:git">
      <p>I submitted an <a href="https://lore.kernel.org/git/20261028-fz-notes-concurrent-write-v1-1-3d513e1fdbb7@gmail.com/T/#u">upstream patch</a> for the bug as well. <a href="#fnref:git" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:test">
      <p>This test demonstrates the race condition in <code class="language-plaintext highlighter-rouge">git notes</code> when adding notes concurrently. We can easily build this as a Nix derivation using <a href="https://nixos.org/manual/nix/stable/#sec-runcommand"><code class="language-plaintext highlighter-rouge">runCommand</code></a>. <a href="#fnref:test" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:normal">
      <p>On a normal Linux machine, 25 of 30 runs lose one too, so this is not a Rewind-onlybug. <a href="#fnref:normal" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:byte">
      <p>I have a <a href="/2026/10/23/who-wrote-this-byte">lengthier post</a> about how Rewind can help make your build bit-for-bit reproducible. <a href="#fnref:byte" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[I have been stress testing Rewind VM, a deterministic VM, to work through the kinks, bugs and dog-fooding the user-experience. Even having developed it myself, I continue to be wild-eye amazed at the magic of what it can do.]]></summary></entry><entry><title type="html">Dafuq is a deterministic VM?</title><link href="https://fzakaria.com/2026/10/24/dafuq-is-a-deterministic-vm" rel="alternate" type="text/html" title="Dafuq is a deterministic VM?" /><published>2026-10-24T10:00:00-07:00</published><updated>2026-10-24T10:00:00-07:00</updated><id>https://fzakaria.com/2026/10/24/dafuq-is-a-deterministic-vm</id><content type="html" xml:base="https://fzakaria.com/2026/10/24/dafuq-is-a-deterministic-vm"><![CDATA[<p><em>The life of a system call inside Rewind VM.</em></p>

<p>Since I started writing about <a href="https://rewindvm.dev">Rewind VM</a>, the question I get most from friends is not “does it work?” but “how does it <em>actually</em> work?” 🤔</p>

<p>D’uh! <u>It is a virtual machine that only gets interrupted when it asks to be</u>.</p>

<!--more-->

<p style="--image-width: 18rem"><a href="/assets/images/rewind_dafuq_meme.png"><img src="/assets/images/rewind_dafuq_meme.png" alt="Dafuq did I just read meme: Samuel Johnson squints at a book that says &quot;It is a VM that only gets interrupted when it asks to be&quot;, then looks up, baffled: &quot;Dafuq did I just read&quot;" /></a></p>

<p>That seems to not have helped much, so let’s go deeper.</p>

<p>We will follow a tiny C program through the machine and, at each step, look at where non-determinism would have crept in and what Rewind does about it.</p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
If you have not seen Rewind before, start with <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a>.</p>
</blockquote>

<h2 id="life-is-deterministic">Life is deterministic</h2>

<p>CPUs must be deterministic. If they were not, we would have a very bad time trusting them to do anything.</p>

<p>Give a CPU the same registers, the same memory and the same instructions, and it computes the same result.</p>

<p>What does change? Why are machines not deterministic?</p>

<p>They are not closed systems. They interact with the outside world and that interaction is not deterministic. A timer interrupt can land at any instruction or a disk read can complete at any time.</p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
Computers can’t actually make random numbers. A deterministic machine can only fake it with <em>pseudo</em>-randomness: run the same algorithm from the same seed and you get the same numbers back. The real stuff has to come from the physical world. My favorite example is how Cloudflare keeps <a href="https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/">a wall of 100 lava lamps</a> in its San Francisco office with a camera pointed at it, feeding its encryption keys.</p>
</blockquote>

<p>If we could capture all external influences, we could replay the external world and get the same run again. That is what Rewind does.</p>

<p>Every source from the outside world is either removed or replaced with a value derived from a stored seed. Time, for instance, is a clock managed by Rewind. Interrupts arrive only at points we choose. ⏱️</p>

<p>One more outside influence is easy to miss: other CPUs. On two cores, which thread wins a race comes down to cache misses and pipeline stalls, so Rewind provides a single virtual CPU and the threads take turns on it.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/d9df1991fa0f2dff.svg" alt="Left: two cores run thread A and thread B at the same time, both reach the same lock at nearly the same moment, and which one wins depends on cache timing. Right: Rewind's single virtual CPU runs A, then B, then A, switching only at exits, in the same order every run." width="720" height="190">
</figure>

<h2 id="a-step-is-a-vm-exit">A step is a VM exit</h2>

<p>To control the outside world, Rewind needs moments where the software stops and Rewind can step in. At those moments it writes down what just happened, hands over the current time, or delivers an interrupt. So how do you stop the software at the same place on every run?</p>

<p>One answer is to make the hardware do it. CPUs have performance counters that can interrupt after a set number of instructions. 
Unfortunately, the interrupt may arrive a few instructions late, and the amount varies.<sup id="fnref:intel"><a href="#fn:intel" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p>Rewind uses a <em>cooperative</em> approach: let the software tell us when something happens!</p>

<p>Rewind runs the guest under KVM, the hypervisor built into Linux. The guest’s code runs directly on the real CPU until it touches a device. The CPU then stops the guest and hands control to the program running the VM, Rewind’s monitor (<code class="language-plaintext highlighter-rouge">rewind-vmm</code>). That hand-off is a <em>VM exit</em>.</p>

<p>Rewind patches the guest kernel so that whenever it needs something only the outside world has, such as the time, a timer, or “I have nothing to do”, it makes an exit and asks the monitor. Reporting what happened, like a process starting or a line of output, is an exit too.</p>

<p>Each exit the guest makes is called a <strong>step</strong>, and they are numbered starting from boot.</p>

<p>If we <strong>assume</strong> the guest is deterministic up to step <em>N</em>, step <em>N</em> happens at the same instruction on every run.</p>

<p>The monitor’s loop, sketched in Python:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">step</span><span class="p">,</span> <span class="n">clock</span><span class="p">,</span> <span class="n">deadline</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="bp">None</span>

<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="c1"># The guest runs natively until it exits.
</span>    <span class="nb">exit</span> <span class="o">=</span> <span class="n">vcpu</span><span class="p">.</span><span class="nf">run</span><span class="p">()</span>

    <span class="c1"># The host's own business: not a step, and nothing is sent.
</span>    <span class="k">if</span> <span class="nb">exit</span><span class="p">.</span><span class="n">reason</span> <span class="o">==</span> <span class="n">HOST_INTERRUPT</span><span class="p">:</span>
        <span class="k">continue</span>

    <span class="c1"># An exit the guest made. Time moves only here.
</span>    <span class="n">step</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="n">clock</span> <span class="o">+=</span> <span class="n">QUANTUM</span>

    <span class="c1"># Read the clock, arm a timer, log an event...
</span>    <span class="n">deadline</span> <span class="o">=</span> <span class="nf">handle</span><span class="p">(</span><span class="nb">exit</span><span class="p">,</span> <span class="n">step</span><span class="p">)</span>

    <span class="c1"># Interrupts go in only here, with the guest stopped.
</span>    <span class="k">if</span> <span class="n">deadline</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">clock</span> <span class="o">&gt;=</span> <span class="n">deadline</span><span class="p">:</span>
        <span class="n">vcpu</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="n">TIMER_INTERRUPT</span><span class="p">)</span>
        <span class="n">deadline</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>

<figure>
<img src="https://fzakaria.com/assets/figures/2351cc55e9b61677.svg" alt="Two runs of the same guest in a normal VM and two in Rewind, each drawn as a line of guest instructions with the guest's exits at the same five places. In the normal VM the timer interrupt lands between exits, at a different instruction in each run. In Rewind the interrupt is delivered at an exit, the same step in the recording and in the replay." width="720" height="330">
</figure>

<p>This is true on the first run too. Nothing gets recorded, and a replay just runs the same inputs again.</p>

<p>Three pieces make this work, and your program doesn’t need to know about any of them.</p>

<p><strong>A patched Linux kernel.</strong> The guest kernel is the piece that cooperates. A <a href="https://github.com/fzakaria/rewindvm/blob/main/guest/linux/rewind-guest.patch">small patch</a> teaches it to take its clock, its timer and its idle loop from the monitor instead of from hardware, and to report what processes do: exec, exit, output, files written. The monitor also hides the CPU features that would leak the outside world in, such as the hardware cycle counter and the random number instruction, so the kernel never uses them.</p>

<p><strong>rewind-init.</strong> The special PID 1 is a small program that mounts the input image (a Nix closure or a root filesystem), runs the command, reports its exit status and powers the machine off. The image is mapped straight into guest memory, so reading a file is reading memory, and there is no disk to finish a read at a random moment.</p>

<p><strong>rewind-vmm.</strong> The monitor owns time, decides when interrupts go in, and keeps the trace of everything the kernel reports.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/6d1763f5f4070bc1.svg" alt="Rewind VM's architecture. Inputs, a Nix closure or a root filesystem packed as erofs, go into the guest VM: one vCPU on stock KVM, running natively on the real CPU. Inside it are the build or test processes, rewind-init as PID 1, and a patched Linux kernel with the Rewind platform, whose clock, timer, idle and events go through the host. Each VM exit is one step down to the host's rewind-vmm, which owns time, ordering and memory: a virtual clock from exits and the branch count, an event trace, keyframes in a deduplicated page store, and seeded randomness, so the same inputs and seed give the same run. The monitor sends its interrupt back up only at an exit." width="680" height="600">
</figure>

<h2 id="the-life-of-a-system-call">The life of a system call</h2>

<p>Here is the program. It reads the clock, draws a random number, prints both with the address of a stack variable, sleeps for a second, then reads the clock again.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/random.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">struct</span> <span class="n">timespec</span> <span class="n">ts</span><span class="p">;</span>
  <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">r</span><span class="p">;</span>

  <span class="n">clock_gettime</span><span class="p">(</span><span class="n">CLOCK_REALTIME</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ts</span><span class="p">);</span>
  <span class="n">getrandom</span><span class="p">(</span><span class="o">&amp;</span><span class="n">r</span><span class="p">,</span> <span class="k">sizeof</span> <span class="n">r</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"time=%ld.%09ld random=%u stack=%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ts</span><span class="p">.</span><span class="n">tv_sec</span><span class="p">,</span> <span class="n">ts</span><span class="p">.</span><span class="n">tv_nsec</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">r</span><span class="p">);</span>
  <span class="n">fflush</span><span class="p">(</span><span class="n">stdout</span><span class="p">);</span>

  <span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>

  <span class="n">clock_gettime</span><span class="p">(</span><span class="n">CLOCK_REALTIME</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ts</span><span class="p">);</span>
  <span class="n">printf</span><span class="p">(</span><span class="s">"time=%ld.%09ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ts</span><span class="p">.</span><span class="n">tv_sec</span><span class="p">,</span> <span class="n">ts</span><span class="p">.</span><span class="n">tv_nsec</span><span class="p">);</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>On my laptop every run prints something new. In Rewind, every run prints the same thing, down to the nanosecond, until you change the seed:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind run <span class="nt">--root</span> root <span class="nt">--</span> /bin/hello
<span class="go">time=1792886400.001006990 random=166963147 stack=0x7fff2bad5e4c
time=1792886401.001132684
rewind: run 85a03383f7d917d9 exited:0 after 432 steps, 1.002s virtual, 1.139s wall (poweroff)

</span><span class="gp">$</span><span class="w"> </span>rewind run <span class="nt">--root</span> root <span class="nt">--</span> /bin/hello
<span class="go">time=1792886400.001006990 random=166963147 stack=0x7fff2bad5e4c
time=1792886401.001132684
rewind: run 85a03383f7d917d9 exited:0 after 432 steps, 1.002s virtual, 0.394s wall (poweroff)

</span><span class="c"># a different seed is a different input, so a different run
</span><span class="gp">$</span><span class="w"> </span>rewind run <span class="nt">--root</span> root <span class="nt">--seed</span> 1 <span class="nt">--</span> /bin/hello
<span class="go">time=1792886400.001006990 random=2117056732 stack=0x7ffdd58e5bdc
time=1792886401.001132680
rewind: run 59c7da84895c210a exited:0 after 432 steps, 1.002s virtual, 0.848s wall (poweroff)
</span></code></pre></div></div>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_same_picture_meme.png"><img src="/assets/images/rewind_same_picture_meme.png" alt="They're the same picture meme: Pam holds up two papers labelled &quot;the run that failed on CI&quot; and &quot;rewind replay of that run&quot;, and says &quot;They're the same picture.&quot;" /></a></p>

<p>Same inputs, same run id, same 432 steps. Oh, and the program slept for a second, yet the whole VM finished in 0.39 seconds. 🤓</p>

<p>Normally, reading the time never enters the kernel: a bit of code in every process reads a hardware counter directly. Rewind hides that counter, so the read goes to the kernel, and the kernel asks the monitor.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/bebabefcc874f6c7.svg" alt="A sequence diagram of clock_gettime in Rewind across four lanes: hello in user space, the guest kernel, KVM in the host kernel, and rewind-vmm on the host. hello makes a system call because there is no hardware counter to read. The guest kernel reads the clock port, which makes KVM exit to rewind-vmm. The monitor counts a step, adds 5 microseconds plus the work done to its clock, leaves the time in memory the guest can read, and sends an interrupt if a timer is due. It resumes the guest, and the kernel returns the time to hello." width="760" height="470">
</figure>

<p>Rewind hides the CPU’s random number instruction, so the only thing
available to the guest kernel is the seed Rewind passes at boot. The kernel’s random number generator is seeded with that value, so every run of the same program with the same seed gets the same random numbers.</p>

<p>Most system calls never leave the guest. The few that need the outside world make an exit, and the monitor writes down what happened. The guest kernel also reports when it is idle, so the monitor can jump its clock to the next timer interrupt.<sup id="fnref:sleep"><a href="#fn:sleep" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>Control those few doors and the guest is deterministic: the outside world shows up at the same step on every run.</p>

<p>So, dafuq is a deterministic VM? Like I said at the start, it’s a VM that only gets interrupted when it asks to be. Hopefully that makes a bit more sense now. 😅</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_expanding_brain_meme.png"><img src="/assets/images/rewind_expanding_brain_meme.png" alt="Expanding brain meme: &quot;Re-run the flaky test until it passes&quot;, &quot;Add sleep(1) before the assert&quot;, &quot;Record where every interrupt landed and replay it&quot;, and, brightest, &quot;Only take an interrupt when the guest asks for one&quot;" /></a></p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:intel">
      <p>Intel’s counter is good enough that you can single-step the rest of the way to the exact instruction. I have an AMD laptop. 🙃 <a href="#fnref:intel" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:sleep">
      <p><code class="language-plaintext highlighter-rouge">sleep(N)</code> costs no real time. The monitor jumps its clock straight to the deadline and delivers the timer interrupt. <a href="#fnref:sleep" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[The life of a system call inside Rewind VM. Since I started writing about Rewind VM, the question I get most from friends is not “does it work?” but “how does it actually work?” 🤔 D’uh! It is a virtual machine that only gets interrupted when it asks to be.]]></summary></entry><entry><title type="html">Who wrote this byte?</title><link href="https://fzakaria.com/2026/10/23/who-wrote-this-byte" rel="alternate" type="text/html" title="Who wrote this byte?" /><published>2026-10-23T10:00:00-07:00</published><updated>2026-10-23T10:00:00-07:00</updated><id>https://fzakaria.com/2026/10/23/who-wrote-this-byte</id><content type="html" xml:base="https://fzakaria.com/2026/10/23/who-wrote-this-byte"><![CDATA[<p><em>git blame for your build’s bytes.</em> 🔍</p>

<p>A build is <em>bit reproducible</em> when the same inputs give the same bytes.</p>

<p>Why does reproducibility matter? They provide certainty that software is genuine and has not been tampered with. See <a href="https://reproducible-builds.org">reproducible-builds.org</a>.</p>

<p>Nix is infamously <strong>not</strong> bit reproducible, but rather <em>input-addressed</em>: the hash of a package is calculated from the inputs rather than the output.</p>

<!--more-->

<p>We still prefer though to have bit reproducibility, because it allows us to verify that the package we downloaded is the same as the one built from source. It is something many packages aspire to achieve.</p>

<p>When the outputs differ, you want to know why. The first step is to find the bytes that differ, and the second is to find the program that wrote them.</p>

<p>Finding the cause of a difference has traditionally been the challenging part of process making builds reproducible…until now!</p>

<p>I have been building <a href="https://rewindvm.dev">Rewind VM</a>, a deterministic virtual machine that can record and replay builds, and it now has a new command to find the program that wrote a byte that differs between two runs: <code class="language-plaintext highlighter-rouge">rewind blame</code>.</p>

<blockquote class="alert alert-note">
  <p><strong>Aside:</strong>
I wrote about <a href="https://rewindvm.dev">Rewind VM</a> first in <a href="/2026/10/03/rewind-vm-a-flaky-build-you-only-catch-once">a flaky build you only catch once</a>, and then about <a href="/2026/10/14/always-sometimes-never">properties</a>.</p>
</blockquote>

<p>Let’s see how it works, first with a small example.</p>

<h2 id="a-small-build-that-is-not-reproducible">A small build that is not reproducible</h2>

<p>Let’s start with a made-up library, <a href="https://github.com/fzakaria/rewindvm/tree/main/examples/tiles"><code class="language-plaintext highlighter-rouge">tiles</code></a>, that writes its pkg-config file with a short
Python script.<sup id="fnref:tiles"><a href="#fn:tiles" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> The script collects the packages the library links against into a
<code class="language-plaintext highlighter-rouge">set</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">requires</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>
<span class="k">for</span> <span class="n">deps</span> <span class="ow">in</span> <span class="n">PARTS</span><span class="p">.</span><span class="nf">values</span><span class="p">():</span>
    <span class="n">requires</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">deps</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Requires.private: </span><span class="si">{</span><span class="sh">'</span><span class="s">, </span><span class="sh">'</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">requires</span><span class="p">)</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>Python iterates a set of strings in an order drawn from its <em>hash seed</em>, which is random
for every new process.</p>

<p>In Rewind the seed is an input: <code class="language-plaintext highlighter-rouge">--seed N</code> decides every random
number the build draws. Let’s build <code class="language-plaintext highlighter-rouge">tiles</code> under eight seeds:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--epoch</span> 1790985600 <span class="nt">--seeds</span> 8 <span class="nt">--schedules</span> 0 <span class="nt">--all</span> <span class="nt">--no-narrow</span> <span class="se">\</span>
<span class="gp">    github:fzakaria/rewindvm#</span>tiles
<span class="go">rewind: packing 75 store paths for tiles-0.2.0
rewind: 8 random seeds, each run under schedule 0 and 0 other schedules: 8 runs
seed  0 schedule   0: exited:0       4035 steps  d20b5efd388d  run 8d29a01cc44264b4
seed  1 schedule   0: exited:0       4035 steps  b4501793e383  run c4bd9260b7719942
seed  2 schedule   0: exited:0       4035 steps  0cd5db6c5c2c  run a5448b8d2ca19c18
seed  3 schedule   0: exited:0       4035 steps  24f18f09c329  run b03811e63cc68d97
seed  4 schedule   0: exited:0       4035 steps  b4501793e383  run ed28383bea49e84f
seed  5 schedule   0: exited:0       4035 steps  5e1f0a457cbe  run f5e8d2ac2c4b7782
seed  6 schedule   0: exited:0       4035 steps  d20b5efd388d  run af24df10ae7ac1e8
seed  7 schedule   0: exited:0       4035 steps  b4501793e383  run b5268e20063c5a9e
6 of 7 other runs ended differently
</span></code></pre></div></div>

<p>The third column from the end is the start of the output’s hash (i.e. <code class="language-plaintext highlighter-rouge">d20b5efd388d</code>), and the last is the run id.</p>

<p>We did eight builds and got five different outputs. 😬
Our build is not reproducible.</p>

<p><code class="language-plaintext highlighter-rouge">rewind diff</code> shows what differs between two runs, without leaving Rewind:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind diff <span class="nt">--file</span> /build/tiles/tiles.pc 8d29a01c c4bd9260
<span class="go">tiles.pc differs, first at line 7.
</span><span class="c">...
</span><span class="go">  Name: tiles
  Description: Tile a large image into small ones
  Version: 0.2.0
- Requires.private: pixman-1, libpng, zlib
+ Requires.private: zlib, pixman-1, libpng
</span><span class="gp">  Libs: -L$</span><span class="o">{</span>libdir<span class="o">}</span> <span class="nt">-ltiles</span>
</code></pre></div></div>

<p>You could ascertain this from tools like <a href="https://diffoscope.org/">diffoscope</a> but would be left to figure out what program wrote the differing bytes. <code class="language-plaintext highlighter-rouge">rewind blame</code> does that for you.</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_drake_blame_meme.png"><img src="/assets/images/rewind_drake_blame_meme.png" alt="Drake meme. Drake turns away from &quot;diffoscope: the two files differ&quot; and points approvingly at &quot;rewind blame: python3 wrote that byte at step 2,909&quot;." /></a></p>

<h2 id="rewind-blame">rewind blame</h2>

<p><code class="language-plaintext highlighter-rouge">rewind blame</code> takes two runs and a file, and names the program that wrote the first byte
that differs:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind blame 8d29a01c c4bd9260 <span class="se">\</span>
<span class="go">    /nix/store/ihhhzc5q...-tiles-0.2.0/lib/pkgconfig/tiles.pc
tiles.pc differs at byte 178. python3 (pid 112) wrote that byte at step
2,909 in run 8d29a01c and at step 2,909 in run c4bd9260.

22 of 227 bytes differ. The bytes from byte 178:
  8d29a01c  70 69 78 6d 61 6e 2d 31 2c 20 6c 69 62 70 6e 67  |pixman-1, libpng|
  c4bd9260  7a 6c 69 62 2c 20 70 69 78 6d 61 6e 2d 31 2c 20  |zlib, pixman-1, |

Who wrote the file in run 8d29a01c, from the last writer back:
  install (pid 122) copied it from /build/tiles/tiles.pc, starting at
    step 3,157. Its source already differs, so the copy did not cause
    the difference.
  bash (pid 112) wrote /build/tiles/tiles.pc, starting at step 2,796. It
    is the first writer, and the file already differs when it is done.
Run c4bd9260 made the file the same way, with the same programs.

In both runs, one write of 227 bytes, from byte 0, set byte 178.

python3 ran with the same command line in both runs, so the difference
comes from something it read or from randomness.

What python3 read:
  1 file that the build made, all the same in both runs:
    /build/tiles/gen-pc.py.
  31 other files that the build did not make, such as programs and
    libraries.

Likely cause: something inside python3 (pid 112), such as randomness or
the order its threads ran in. Its command line and the files the build
made that it read are the same in both runs.
</span></code></pre></div></div>

<p>The output is a little long, but it is all relevant and the first line has the answer: <code class="language-plaintext highlighter-rouge">python3</code> wrote that byte at step 2,909.<sup id="fnref:wrapped"><a href="#fn:wrapped" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>The rest of the text is how the tool determined the provenance of the byte change.</p>

<ol>
  <li>The file in <code class="language-plaintext highlighter-rouge">$out</code> was copied there by <code class="language-plaintext highlighter-rouge">install</code>. The file it copied from already
differed, so the copy is not to blame.</li>
  <li>That file, <code class="language-plaintext highlighter-rouge">/build/tiles/tiles.pc</code>, was first written by pid 112. It was still <code class="language-plaintext highlighter-rouge">bash</code>
when it opened the file for the <code class="language-plaintext highlighter-rouge">&gt;</code> redirect, and then became <code class="language-plaintext highlighter-rouge">python3</code>. One write of
227 bytes set byte 178.</li>
  <li><code class="language-plaintext highlighter-rouge">python3</code> had the same command line in both runs, and the only file from the build it
read, <code class="language-plaintext highlighter-rouge">gen-pc.py</code>, was the same in both runs.</li>
</ol>

<p>The tool highlights that the difference was made <em>inside</em> python3, and we know that the seed is the one input that changed. 🎯</p>

<figure>
<img src="https://fzakaria.com/assets/figures/69bd090e10a33d98.svg" alt="The chain blame follows for tiles. tiles.pc in the output differs at byte 178. install copied it at step 3,157 from /build/tiles/tiles.pc, which already differed, so the copy is not the cause. python3 gen-pc.py wrote /build/tiles/tiles.pc at step 2,909 and was the first writer. python3 had the same command line and read the same gen-pc.py in both runs, so the difference comes from inside python3: its hash seed." width="620" height="330">
</figure>

<p>The fix is straightforward, we wrap the list with <code class="language-plaintext highlighter-rouge">sorted()</code>, and every seed then builds the same <code class="language-plaintext highlighter-rouge">tiles.pc</code>. 🙌</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-print(f"Requires.private: {', '.join(requires)}")
</span><span class="gi">+print(f"Requires.private: {', '.join(sorted(requires))}")
</span></code></pre></div></div>

<h2 id="how-it-knows">How it knows</h2>

<p>How can the command deduce the lineage of a byte?</p>

<p>The trick is that blame does not have to <em>search</em> for the write. The run already recorded
it.😯</p>

<p>Every Rewind run records each program’s starts, exits, opens for writing and renames,
each at its step.<sup id="fnref:step"><a href="#fn:step" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> For <code class="language-plaintext highlighter-rouge">blame</code>, the guest’s Linux kernel also keeps two kinds
of record:</p>

<ul>
  <li>a <em>write record</em> for every write to a file: which thread, which file, where in the file
and how many bytes;</li>
  <li>an <em>open record</em> for every file a program opens, for reading or for writing.</li>
</ul>

<p>The records are written to a buffer in the guest kernel’s memory, a 1MiB buffer, and Rewind copies them into <code class="language-plaintext highlighter-rouge">trace.bin</code> at the intervals it has stop the VM anyways. This keeps the runs identical and performant.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/32becad3e203143c.svg" alt="A timeline of a build. Programs write and open files between steps, and each write or open adds a record to a buffer in the guest kernel's memory. At each step Rewind already takes, it copies the buffer into trace.bin and empties it. No step is added for the records." width="620" height="230">
</figure>

<p><em>I measured the cost of the records in Rewind 0.9.0, and it is small enough to leave them on for every run.</em></p>

<p>With the records, blame finds the write that set the byte by reading the trace, without
replaying either run. It only replays to read a file’s contents at a step.</p>

<h2 id="in-the-app">In the app</h2>

<p>The Rewind app shows the same thing visually. Open the two runs side by side:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind open 8d29a01c <span class="nt">--compare</span> c4bd9260
</code></pre></div></div>

<p>The <em>Compare</em> tab now lists the files that differ between the two runs:</p>

<p style="--image-width: 30rem"><a href="/assets/images/rewind-repro-files.png"><img src="/assets/images/rewind-repro-files-crop.png" alt="The Rewind app's Files that differ tab for seed 0's and seed 1's tiles runs: 2 of the 5 files the runs left behind differ, /build/tiles/tiles.pc and the installed copy, each with 22 of 227 bytes differing, first at byte 178" /></a></p>

<p>We can also click a file to see it from both runs. Here that is step 2,909, with <code class="language-plaintext highlighter-rouge">python3 gen-pc.py</code> in the process tree and its command in the build log:</p>

<p><a href="/assets/images/rewind-repro-write.png"><img src="/assets/images/rewind-repro-write-crop.png" alt="The Rewind app at step 2,909: the process tree with python3 gen-pc.py, pid 112, under bash and make, and tiles.pc from both runs one under the other, with this run's Requires.private line in amber and the other run's in blue" /></a></p>

<p>At that moment, we can explore the complete VM state: the files as they were, the process tree, a fork, or gdb. The app shows the same information as <code class="language-plaintext highlighter-rouge">rewind blame</code>, but visually and interactively.</p>

<h2 id="beyond-seeds">Beyond seeds</h2>

<p>In order to capture possibilities of non-determinism, we need to vary any input that can change the output.  The seed decides random numbers. <code class="language-plaintext highlighter-rouge">rewind check</code> also varies the <em>schedule</em>, the order threads take turns, which is how parallel <code class="language-plaintext highlighter-rouge">make</code> and <code class="language-plaintext highlighter-rouge">ninja</code> end up with different
outputs.</p>

<p>Turns out there is a third input that can change the output: the order of directories. The order of files in a directory is not guaranteed to be the same across machines, or even across runs on the same machine although many builds assume it to be.</p>

<p>We can also vary the order of directories in Rewind which helps catch even more non-determinism with <code class="language-plaintext highlighter-rouge">--dirents</code>. This is useful for builds that depend on the order of files in a directory.</p>

<p>The <a href="https://github.com/fzakaria/rewindvm/tree/main/examples/plugins"><code class="language-plaintext highlighter-rouge">plugins</code></a> example writes an index of the plugins it ships, in the order <code class="language-plaintext highlighter-rouge">os.listdir</code> returns. The example builds the same output under every seed, but not under every directory order:<sup id="fnref:plugins"><a href="#fn:plugins" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--epoch</span> 1790985600 <span class="nt">--seeds</span> 1 <span class="nt">--schedules</span> 0 <span class="nt">--all</span> <span class="nt">--no-narrow</span> <span class="se">\</span>
<span class="gp">    --dirents 3 github:fzakaria/rewindvm#</span>plugins
<span class="go">dirents  0 schedule   0: exited:0       3885 steps  77b96f07540a  run 8d64b8b46c5c8b68
dirents  1 schedule   0: exited:0       3885 steps  3d555b541296  run e4c221b26df20a9c
dirents  2 schedule   0: exited:0       3885 steps  925dde4ef23a  run 593a9430de55e469
dirents  3 schedule   0: exited:0       3885 steps  abd7877043a8  run 067e05c27c9ceb09
3 of 3 other runs ended differently
</span></code></pre></div></div>

<p>We have four directory orders, and four different outputs. Each one lists the same five plugins in another order:</p>

<figure>
<img src="https://fzakaria.com/assets/figures/9a8a5c8877c4fdd0.svg" alt="The order plugins.txt lists the five plugins in. Stock order: sharpen, rotate, resize, crop, blur. Under rewind.dirents=1: sharpen, crop, blur, rotate, resize. Under rewind.dirents=2: sharpen, resize, crop, rotate, blur. With sorted(): blur, crop, resize, rotate, sharpen, under every key." width="620" height="184">
</figure>

<p>The fix is the same as before: <code class="language-plaintext highlighter-rouge">sorted(os.listdir(directory))</code>, and every key builds the same index.</p>

<p>Look closely and both examples are the same bug. Python iterates a set in hash order, and ext4 lists a directory in hash order. Change the seed behind the hash and the order changes with it.</p>

<p style="--image-width: 22rem"><a href="/assets/images/rewind_always_hash_order_meme.png"><img src="/assets/images/rewind_always_hash_order_meme.png" alt="meme about hash order for directories" /></a></p>

<h2 id="try-it">Try it</h2>

<p>Finding the cause of a difference has traditionally been the challenging part of process making builds reproducible. Many <a href="https://reproducible-builds.org/tools/#tools-to-compare-build-artifacts-andor-detect-nondeterminism">tools</a> exist to help detect nondeterminism but few actually give you the root cause.</p>

<p>Lowering the barrier to making builds reproducible is a goal of Rewind VM, and <code class="language-plaintext highlighter-rouge">rewind blame</code> is a step in that direction. Rewind VM is purpose built to work in the Nix ecosystem and helping make more Nix packages reproducible is a worthy goal.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>curl <span class="nt">-fsSL</span> https://rewindvm.dev/install | sh
<span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--seeds</span> 8 <span class="nt">--schedules</span> 0 <span class="nt">--all</span> <span class="nt">--no-narrow</span> <span class="se">\</span>
<span class="gp">    github:fzakaria/rewindvm#</span>tiles
</code></pre></div></div>

<p>If you want a deeper dive on this subject, check out the <a href="https://rewindvm.dev/tutorials/repro.html">reproducible builds tutorial</a>, which walks through
both examples step by step, including the directory-order one.</p>

<p>If you maintain a package that does not reproduce and you do not know why, try blame on
it, and tell me what it finds. I would love to add it to the
<a href="https://rewindvm.dev/bugs.html">leaderboard</a>. 🏆</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:tiles">
      <p><a href="https://github.com/nixos/nixpkgs">nixpkgs</a> sets <code class="language-plaintext highlighter-rouge">PYTHONHASHSEED=0</code> that circumvents this bug. <a href="#fnref:tiles" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:wrapped">
      <p>The output is real, with long lines wrapped to fit and the store path shortened. <a href="#fnref:wrapped" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:step">
      <p>A <em>step</em> is a moment the virtual machine stops and hands control to Rewind, such as when a program starts or writes to the terminal. Step 2,909 is the same moment every time you replay the run. <a href="#fnref:step" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:plugins">
      <p>It is the second example in the <a href="https://rewindvm.dev/tutorials/repro.html">reproducible builds tutorial</a>. <a href="#fnref:plugins" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[git blame for your build’s bytes. 🔍 A build is bit reproducible when the same inputs give the same bytes. Why does reproducibility matter? They provide certainty that software is genuine and has not been tampered with. See reproducible-builds.org. Nix is infamously not bit reproducible, but rather input-addressed: the hash of a package is calculated from the inputs rather than the output.]]></summary></entry><entry><title type="html">No quarter for gaps</title><link href="https://fzakaria.com/2026/10/20/no-quarter-for-gaps" rel="alternate" type="text/html" title="No quarter for gaps" /><published>2026-10-20T09:00:00-07:00</published><updated>2026-10-20T09:00:00-07:00</updated><id>https://fzakaria.com/2026/10/20/no-quarter-for-gaps</id><content type="html" xml:base="https://fzakaria.com/2026/10/20/no-quarter-for-gaps"><![CDATA[<p>The advent of large language models has changed the economics of closing gaps in software stacks.</p>

<p><a href="/assets/images/nixpkgs_ruby_manual_bundix.png"><img src="/assets/images/nixpkgs_ruby_manual_bundix.png" alt="a photo of nixpkgs ruby manual showing bundix not working for native gems" /></a></p>

<p>This is in the nixpkgs manual, in the Ruby section, under
“Platform-specific gems” <a href="https://nixos.org/manual/nixpkgs/unstable/#ruby-platform-specif-gems">[ref]</a>. The pull request (<a href="https://github.com/nix-community/bundix/pull/68">PR#68</a>) it points to was opened in October 2019 and closed, unmerged, in August 2026, with: “Closing as stale. I no longer work with bundix. Feel free to take it over.” <a href="https://github.com/nix-community/bundix/pull/68#issuecomment-5166951772">[cite]</a></p>

<blockquote class="alert alert-note">
  <p><strong>Aside</strong>
There are alternatives that have sprung up but they all have unique gaps or are
not widely communicated as <em>blessed</em> solutions. The manual itself also does
recommend a workaround for the issue, but I found it to not work in practice.</p>
</blockquote>

<p>Every ecosystem has gaps everyone has learned to step around. They survive because there
is just <em>too much stuff</em> to do. We live with these warts and gaps because the cost of fixing them is either too high or there are more important things to do.</p>

<p>That economics is gone.</p>

<p style="--image-width: 20rem"><a href="/assets/images/dhh_yelling_about_doing_things.png"><img src="/assets/images/dhh_yelling_about_doing_things.png" alt="dhh yelling about how little he is doing" /></a></p>

<p>We can have and expect amazing perfectly correct software stacks, because the cost of fixing gaps is now low enough that it is worth doing. The economics of software have changed.</p>

<p>The multiple days of typing are now an afternoon, <em>or less</em>, of directing and reading to an AI agent. In the case of Nix and Ruby, I wrote the Nix side of a Bundler lockfile solution, <a href="https://github.com/fzakaria/gemlock.nix">gemlock.nix</a>, within an hour, removing days of misery I hit while running this blog.</p>

<p>While AI generated code <em>may</em> suffer from slop, it is a growing skill how to wield AI to produce correct verifyable code. Nix is amazingly well-suited as it gives the agents a reproducible test-bed with which to verify the correctness of the code they produce.</p>

<p>The economics of software have changed, and we can now expect to have <em>no quarter</em> for gaps in our stacks.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The advent of large language models has changed the economics of closing gaps in software stacks.]]></summary></entry><entry><title type="html">One store, two frontends</title><link href="https://fzakaria.com/2026/10/17/one-store-two-frontends" rel="alternate" type="text/html" title="One store, two frontends" /><published>2026-10-17T09:00:00-07:00</published><updated>2026-10-17T09:00:00-07:00</updated><id>https://fzakaria.com/2026/10/17/one-store-two-frontends</id><content type="html" xml:base="https://fzakaria.com/2026/10/17/one-store-two-frontends"><![CDATA[<p>One of the big ideas I think the Nix community should pursue is <a href="/2026/06/21/nix-needs-relocatable-binaries">relocatable binaries</a>. I put my money where my mouth is and collaborated with the VFS Linux maintainer <a href="https://brauner.io/">Christian Brauner</a> to get <code class="language-plaintext highlighter-rouge">$ORIGIN</code> support in <code class="language-plaintext highlighter-rouge">PT_INTERP</code> and the shebang via EBPF 🐝. That work is in Linux 7.3 which is about to be released.</p>

<p>Despite the amazing potential this unlocks, I found few to be as enthusiastic as I was. Instead of excitement, I was met with skepticism: <em>“What’s the point beyond it being a cool technical trick?”</em></p>

<p>First off, <em>“The pursuit of excellence does not need justification.”</em> by <a href="https://x.com/mitchellh/status/2074225453217505494?lang=en">Mitchell Hashimoto</a> should be enough.</p>

<p>There is however a very practical reason to pursue relocatable binaries: it allows Nix to be a package set for other build systems, specifically Bazel and buck2. The two frontends can share the same store, and the machine doing the compiling does not need to have Nix installed.</p>

<h1 id="the-wall">The wall</h1>

<p>The integration of Nix and buck2 or Bazel is not novel or new. Many have tried and most have failed, or have seen mixed success.</p>

<dl>
  <dt><a href="https://www.tweag.io/blog/2024-02-29-remote-execution-rules-nixpkgs/">rules_nixpkgs</a></dt>
  <dd>Brings nixpkgs toolchains to Bazel and has been incompatible with remote execution since 2022.</dd>
  <dt><a href="https://tweag.io/blog/2025-07-31-buck2-nix/">buck2.nix</a></dt>
  <dd>Brings nixpkgs toolchains to buck2 as symlinks into the local store, which works on exactly one machine.</dd>
  <dt><a href="https://github.com/MercuryTechnologies/snowydeer">Snowydeer</a></dt>
  <dd>Imports buck2 outputs into Lix as content-addressed store paths, scanning the NAR for hash parts to recover the references.</dd>
</dl>

<p>The wall they all face is that a nixpkgs output is pinned to <code class="language-plaintext highlighter-rouge">/nix/store</code> by three absolute strings: the <code class="language-plaintext highlighter-rouge">RUNPATH</code> the loader reads, the <code class="language-plaintext highlighter-rouge">PT_INTERP</code> the kernel reads, and the symlinks that point at siblings.</p>

<p>A remote executor has none of those paths. you either ship the whole closure inside the executor image, mount the store over the network, or <code class="language-plaintext highlighter-rouge">chroot</code> every action into a fake root.</p>

<p style="--image-width: 20rem"><a href="/assets/images/galaxy_brain_relocatable_meme.png"><img src="/assets/images/galaxy_brain_relocatable_meme.png" alt="Expanding brain meme. Small brain: bake the toolchain into the executor image. Bigger brain: mount /nix/store over NFS on every worker. Glowing brain: chroot every action into its input root. Galaxy brain: make the binaries relocatable." /></a></p>

<p>They all work but have unfortunate side effects. A new compiler version means a new executor image. An NFS mount means a shared filesystem under a system whose entire design is that it does not need one. Requiring <code class="language-plaintext highlighter-rouge">chroot</code> for a worker means that Nix is now a leaky abstraction.</p>

<p>Relocatable binaries gets rid of the wall by circumventing the problem entirely. 🤯</p>

<h1 id="a-closure-is-a-layout-not-a-location">A closure is a layout, not a location</h1>

<p>Absolute paths are not necessary and we can make them relative. It requires work to find all the places a path is baked in, but this is the plight of a Nix enthusiast: we patch and fix the world.</p>

<dl>
  <dt><code class="language-plaintext highlighter-rouge">RUNPATH</code></dt>
  <dd>Already supports relative paths; the loader expands <code class="language-plaintext highlighter-rouge">$ORIGIN</code> to the directory of the executable.</dd>
  <dt><code class="language-plaintext highlighter-rouge">PT_INTERP</code></dt>
  <dd>Linux 7.3 supports a <code class="language-plaintext highlighter-rouge">binfmt_misc</code> handler written in BPF that replaces <code class="language-plaintext highlighter-rouge">$ORIGIN</code> in the interpreter to the directory of the executable.</dd>
</dl>

<p style="--image-width: 18rem"><a href="/assets/images/panik_kalm_panik_pt_interp_meme.png"><img src="/assets/images/panik_kalm_panik_pt_interp_meme.png" alt="Panik, kalm, panik meme. Panik: the worker has no /nix/store. Kalm: RUNPATH is $ORIGIN-relative, the loader finds glibc by itself. Panik: the kernel still opens PT_INTERP as a literal path." /></a></p>

<p>When we make all absolute paths relative, a beautiful thing happens:
the directory <code class="language-plaintext highlighter-rouge">nix/store/&lt;hash&gt;-name</code> stops being an address and becomes a <em>shape</em>.</p>

<p>We can put the shape under <code class="language-plaintext highlighter-rouge">/</code>, you have a Nix store. If we put it under <code class="language-plaintext highlighter-rouge">buck-out/v2/gen/toolchains/…/closure/</code>, you have a toolchain artifact. If we put it under a remote worker’s input root, you have an action that runs.</p>

<p>It is the same beauty of what made Nix hermetic but taken to the next maximum: the shape is now relocatable. The same bytes can be in many places at once, and the loader and kernel do not care which one they are in.</p>

<figure>
<img src="https://fzakaria.com/assets/figures/b237741d968d5467.svg" alt="A file tree. Under an unnamed root: nix/store, containing gcc, glibc and gmp. gcc's bin/gcc has PT_INTERP $ORIGIN/../../glibc/lib/ld-linux-x86-64.so.2 and RUNPATH $ORIGIN/../../glibc/lib; both arrows land inside the sibling glibc directory. cc1, five levels deeper in libexec, has RUNPATH $ORIGIN/../../../../../gmp/lib, and its arrow lands inside the sibling gmp directory. The root is labelled: slash, or buck-out/…/closure/, or an action's input root: the same shape." width="760" height="400">
</figure>

<h1 id="two-frontends">Two frontends</h1>

<p>Once a closure is a shape, the two build systems stop competing for the same job. 👏</p>

<p>Everyone loves Nix for its replicability but are frustrated by the needless rebuilds due to the input-address (extensional) model.</p>

<p>On the other hand, tools like buck2 and Bazel only allow content-addressed (intensional) inputs. Coupled with a fine-grained action graph, this allows for incremental builds and remote execution, but they don’t truly model the world of packages and their dependencies, which is where Nix excels.</p>

<p>Each has spent years badly imitating the other. Buck2 users hand-roll toolchain downloads; Nix users wait for a whole derivation to rebuild because one file changed.</p>

<p>What if Nix and buck2 could <em>natively</em> share the same store?</p>

<figure class="graphviz"><img src="https://fzakaria.com/assets/figures/5a9a9454632c08be.svg" alt="Figure" width="446" height="397">
</figure>

<p>Nix could provide buck2 a closure, and buck2 would treat it like any other input. On the way out, buck2 hands Nix the name of an output, and Nix fetches it like any other store path.</p>

<p>The “store” in the middle is not a daemon or a filesystem. It is an agreement about layout. One build, seen through that agreement:</p>

<figure>
<img src="https://fzakaria.com/assets/figures/e39f1f790fd85d8b.svg" alt="An animated data-flow diagram. On the host, the gcc closure in /nix/store is declared to buck2 by its digest and uploaded to a content-addressed store if it is not there already. A remote worker fetches it into an action's input root, the kernel resolves gcc's $ORIGIN interpreter, gcc runs and links hello. hello is uploaded to the content-addressed store. buck2 reports its digest to the host, and Nix substitutes /nix/store/&lt;D&gt;-hello straight out of that store, with glibc recorded as a reference." width="820" height="400">
<figcaption>Both ends are a store path on the host. Everything in between is a digest.</figcaption>
</figure>

<p>I have a proof-of-concept implementation of this idea, and it is surprisingly small. The <code class="language-plaintext highlighter-rouge">BUCK</code> file is the whole user-facing surface.  The gcc closure is one buck2 tree artifact provided as a toolchain.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">load</span><span class="p">(</span><span class="sh">"</span><span class="s">@rules//:closure.bzl</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nix_closure</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">load</span><span class="p">(</span><span class="sh">"</span><span class="s">@rules//:cc.bzl</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">cc_binary</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nix_cc_toolchain</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">run_remote</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># The gcc closure, pinned by hash, as one tree artifact.
</span><span class="nf">nix_closure</span><span class="p">(</span>
  <span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">closure</span><span class="sh">"</span><span class="p">,</span>
  <span class="n">roots</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">gcc</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">/nix/store/5q6bdx…-gcc-16.2.0</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">glibc</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">/nix/store/h4wfwi…-glibc-2.44-25</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">...},</span>
  <span class="c1"># every store path the roots need, 17 of them
</span>  <span class="n">closure</span> <span class="o">=</span> <span class="p">[...],</span>
<span class="p">)</span>

<span class="nf">nix_cc_toolchain</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">cc</span><span class="sh">"</span><span class="p">,</span> <span class="n">closure</span> <span class="o">=</span> <span class="sh">"</span><span class="s">:closure</span><span class="sh">"</span><span class="p">)</span>

<span class="nf">cc_binary</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">hello</span><span class="sh">"</span><span class="p">,</span> <span class="n">srcs</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">hello.c</span><span class="sh">"</span><span class="p">])</span>

<span class="c1"># Run the binary on the worker and keep what it prints.
</span><span class="nf">run_remote</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">hello-report</span><span class="sh">"</span><span class="p">,</span> <span class="n">exe</span> <span class="o">=</span> <span class="sh">"</span><span class="s">:hello</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>Every one of those paths is a projection of the closure artifact rather than an absolute <code class="language-plaintext highlighter-rouge">/nix/store</code> string, so buck2 can ship the closure wherever the compiler runs. This is most easily seen in the <code class="language-plaintext highlighter-rouge">PT_INTERP</code> and <code class="language-plaintext highlighter-rouge">RUNPATH</code> strings provided by the linker:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-Wl,--dynamic-linker=$ORIGIN/../../h4wfwi…-glibc-2.44-25/lib/ld-linux-x86-64.so.2
-Wl,-rpath,$ORIGIN/../../h4wfwi…-glibc-2.44-25/lib
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">hello</code> is only told where to look within the closure, it has no idea it was a <code class="language-plaintext highlighter-rouge">/nix/store</code> to begin with.</p>

<p>The sample program <code class="language-plaintext highlighter-rouge">hello.c</code> reports its environment to drive home the point.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Where did the kernel find me?</span>
<span class="n">readlink</span><span class="p">(</span><span class="s">"/proc/self/exe"</span><span class="p">,</span> <span class="n">exe</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">exe</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>

<span class="c1">// Which libc did the loader actually map?</span>
<span class="n">dl_iterate_phdr</span><span class="p">(</span><span class="n">report_libc</span><span class="p">,</span> <span class="n">out</span><span class="p">);</span>

<span class="c1">// Does the store path gcc was told about exist on this machine at all?</span>
<span class="n">access</span><span class="p">(</span><span class="n">NIX_GCC</span><span class="p">,</span> <span class="n">F_OK</span><span class="p">);</span>
</code></pre></div></div>

<p>The worker is a NixOS VM running <a href="https://github.com/TraceMachina/nativelink">NativeLink</a> on a 7.3 kernel with the EBPF <code class="language-plaintext highlighter-rouge">binfmt_misc</code> handler registered at boot. Its store is an erofs image of its own closure: 441 paths but crucially gcc is not present: <code class="language-plaintext highlighter-rouge">ls /nix/store | grep gcc</code> comes out empty.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>buck2 run //:hello-report <span class="nt">--show-output</span>
<span class="go">Commands: 3 (cached: 0, remote: 3, local: 0)
BUILD SUCCEEDED

hello from a relocated store path
kernel:         Linux 7.3.0-rc7
hostname:       nix-origin-worker
/proc/self/exe: /var/lib/nativelink/work/8f7d…/buck-out/v2/gen/root/…/__hello__/root/nix/store/hello/bin/hello
libc:           /var/lib/nativelink/work/8f7d…/buck-out/v2/gen/root/…/__hello__/root/nix/store/hello/bin/../../h4wfwi…-glibc-2.44-25/lib/libc.so.6
/nix/store/5q6bdx…-gcc-16.2.0 on this machine: absent
</span></code></pre></div></div>

<h1 id="back-into-the-store">Back into the store</h1>

<p>Getting a buck2 output into the Nix store is where the two frontends have to agree on a name.</p>

<p>buck2 already has one. Every artifact it produces is identified by a digest, a Merkle hash over the output tree, and the CAS is keyed by it. Nix already supports a few ways of addressing a path by its content. Add the buck2 digest to that list and the two systems name the same bytes with the same number.</p>

<p>The <code class="language-plaintext highlighter-rouge">cc_binary</code> rule already shapes its output like <code class="language-plaintext highlighter-rouge">mkDerivation</code> would, <code class="language-plaintext highlighter-rouge">nix/store/&lt;name&gt;/bin/hello</code>. A buck2 output’s store path is therefore <code class="language-plaintext highlighter-rouge">/nix/store/&lt;digest&gt;-hello</code>, and both sides compute <code class="language-plaintext highlighter-rouge">&lt;digest&gt;</code> without asking the other.</p>

<p>The rule also knows the references, because it put glibc in <code class="language-plaintext highlighter-rouge">RUNPATH</code>. It writes them in a small file next to the output, together with the path and the digest. That file is the <a href="https://nix.dev/manual/nix/2.35/protocols/binary-cache/narinfo.html">narinfo</a>. 🤯</p>

<p>The way back to the host store is a substitution. The CAS is configured as a substituter, the same way <code class="language-plaintext highlighter-rouge">cache.nixos.org</code> is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>substituters = reapi://cluster:50051?instance=main https://cache.nixos.org
</code></pre></div></div>

<p>On the Nix side a buck2 target is a derivation that we reference by its digest.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">packages</span><span class="o">.</span><span class="nv">hello</span> <span class="o">=</span> <span class="nv">buck2</span><span class="o">.</span><span class="nv">output</span> <span class="p">{</span>
  <span class="nv">target</span> <span class="o">=</span> <span class="s2">"//:hello"</span><span class="p">;</span>
  <span class="c"># the store path is /nix/store/&lt;digest&gt;-hello</span>
  <span class="nv">digest</span> <span class="o">=</span> <span class="s2">"sha256-…"</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Evaluation never runs buck2: <code class="language-plaintext highlighter-rouge">nix build</code> asks the substituter for the pinned path and the tree streams out of the CAS, references included:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build .#hello
<span class="gp">copying path '/nix/store/&lt;digest&gt;</span><span class="nt">-hello</span><span class="s1">' from '</span>reapi://cluster:50051<span class="s1">'...
</span><span class="gp">$</span><span class="w"> </span><span class="s1">nix path-info --json ./result | jq -r '</span>.[].references[]<span class="s1">'
</span><span class="go">/nix/store/h4wfwic161kxrr74jlzla5lsm28hgary-glibc-2.44-25
/nix/store/j7qx4s4mr17j1wqgvqdzj33lmrnzb387-gcc-16.2.0-lib
</span><span class="gp">$</span><span class="w"> </span>./result/bin/hello
<span class="gp">argv[0]:  /nix/store/&lt;digest&gt;</span><span class="nt">-hello</span>/bin/hello
<span class="gp">libc:     /nix/store/&lt;digest&gt;</span><span class="nt">-hello</span>/bin/../../h4wfwi…-glibc-2.44-25/lib/libc.so.6
</code></pre></div></div>

<p>Nothing was rewritten, rebuilt or re-hashed on the way back. The shape of the closure survived because it only ever used relative paths. The bytes the worker produced are the bytes Nix serves, under the name buck2 already knew.</p>

<p>The two stores become one. 🫂</p>

<p>A <code class="language-plaintext highlighter-rouge">cc_binary</code> and a <code class="language-plaintext highlighter-rouge">mkDerivation</code> end up as the same kind of object: a tree named by its contents, with relative paths to its neighbours. Which frontend wrote it stops mattering once it is in the store.<sup id="fnref:selfref"><a href="#fn:selfref" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p>Two frontends, one store.</p>

<p style="--image-width: 22rem"><a href="/assets/images/epic_handshake_one_store_meme.png"><img src="/assets/images/epic_handshake_one_store_meme.png" alt="Epic handshake meme. One arm is labelled Nix, the other buck2, and the clasp reads: a store path is a shape, not an address." /></a></p>

<h1 id="patch-the-world">Patch the world</h1>

<p>We can think big for how we want to build software. Nix affords us a world where we can make software fit beautifully together. Nix is the one ecosystem where <em>patch the world</em> is a reasonable plan. We can change the kernel, the loader, the compiler wrapper and the package set in a single commit.</p>

<p>Let’s not have static paths in the store limit our possibilities.
The valhalla of incrementality awaits and we can maybe achieve it without <a href="/2025/03/10/an-early-look-at-nix-dynamic-derivations">dynamic derivations</a>.</p>

<p>The yellow brick road to a two-frontend store is paved and clear:</p>

<ol>
  <li><strong>nixpkgs: write relative <code class="language-plaintext highlighter-rouge">RUNPATH</code>.</strong> <a href="https://github.com/NixOS/nixpkgs/pull/534339">#534339</a> is the opt-in version; the default is the goal. The hash stays in the string so no cache is invalidated and the reference scanner keeps working.</li>
  <li><strong>nixpkgs: a location-relative <code class="language-plaintext highlighter-rouge">cc-wrapper</code>.</strong> The wrapper and <code class="language-plaintext highlighter-rouge">bintools-wrapper</code> embed absolute paths in shell. Deriving them from <code class="language-plaintext highlighter-rouge">$(dirname "$0")</code> turns the toolchain into something we can <code class="language-plaintext highlighter-rouge">cp</code>.</li>
  <li><strong>nixpkgs: <code class="language-plaintext highlighter-rouge">relocatable = true</code>, enforced.</strong> A fixup hook that fails any output claiming the flag while still containing the literal store prefix, in ELF or in text.</li>
  <li><strong>NixOS: the handler at boot.</strong> Once 7.3 reaches nixpkgs, a module that registers the BPF handler, gated on a <code class="language-plaintext highlighter-rouge">PT_INTERP_NIX</code> marker so nothing changes for binaries that did not ask.</li>
  <li><strong>Nix: one hash.</strong> A content-addressing method that matches an REAPI directory digest, so a buck2 output in the CAS is a <code class="language-plaintext highlighter-rouge">/nix/store</code> path.</li>
  <li><strong>Build systems: closures as artifacts.</strong> The buck2 rules in this post are a few hundred lines. The Bazel equivalent is a <code class="language-plaintext highlighter-rouge">repository_rule</code> away, and it would be the first <code class="language-plaintext highlighter-rouge">rules_nixpkgs</code> that works with remote execution.</li>
</ol>

<p>A store path that can live anywhere is a store path everyone can use. Let’s go (re)write some strings. ✍️</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:selfref">
      <p>Relocatable outputs also fix the hardest part of Nix’s own content-addressed derivations: self-reference, an output containing its own hash. <code class="language-plaintext highlighter-rouge">$ORIGIN/../lib</code> is how a relocatable output can point at itself. <a href="#fnref:selfref" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[One of the big ideas I think the Nix community should pursue is relocatable binaries. I put my money where my mouth is and collaborated with the VFS Linux maintainer Christian Brauner to get $ORIGIN support in PT_INTERP and the shebang via EBPF 🐝. That work is in Linux 7.3 which is about to be released.]]></summary></entry><entry><title type="html">Always, sometimes, never</title><link href="https://fzakaria.com/2026/10/14/always-sometimes-never" rel="alternate" type="text/html" title="Always, sometimes, never" /><published>2026-10-14T11:00:00-07:00</published><updated>2026-10-14T11:00:00-07:00</updated><id>https://fzakaria.com/2026/10/14/always-sometimes-never</id><content type="html" xml:base="https://fzakaria.com/2026/10/14/always-sometimes-never"><![CDATA[<p><em>Exit 0 means your program exited 0. That’s it. That’s all it means.</em> 🍞</p>

<p>I have been working on a deterministic VM called <a href="https://rewindvm.dev">Rewind VM</a>, where “a run” (i.e. test, command etc.) is a function of its inputs including the thread schedule. The determinism comes from the tool recording every step which lets you replay it exactly. This alone is a pretty good way to find flaky tests: run the test under Rewind, then <code class="language-plaintext highlighter-rouge">rewind check</code> runs it under many different thread schedules, with some slight latency added, and failures often crop up. 🐛</p>

<!--more-->

<p>Feedback however was that it was not enough: the tool could find a failing schedule, but it could not tell you if that schedule was <em>actually</em> a bug. It only knew that the run ended differently than the original schedule, which could be a crash, a timeout, or just a different exit code. That is not enough to say “this is a bug”.</p>

<p>Someone pointed out that I had built most of a <strong>property-based
testing</strong> tool, but I only had one property: matching exit codes.</p>

<p>What is <em>property-based testing</em>?</p>

<p>If you have used <a href="https://hackage.haskell.org/package/QuickCheck">QuickCheck</a> or
<a href="https://hypothesis.readthedocs.io">Hypothesis</a>, you have already used the idea. You write a property, or constraint, that must hold for every input. The tool generates lots of inputs, and when one breaks the property, it <em>shrinks</em> it down to the smallest input that still fails.</p>

<p><code class="language-plaintext highlighter-rouge">rewind check</code> already did the generating and the shrinking. The inputs are thread
schedules, and narrowing shrinks a failing schedule down to the one step that causes the failure. The only property though was “the run ends the same way schedule 0 did”: the same exit code and the same outputs.</p>

<p>That’s a pretty low bar, but easy to improve. 🙃</p>

<p>Rewind now lets specify properties that must be true for every run, or must be true for at least one run, or must never be true in any run.</p>

<p><a href="/assets/images/rewind_sliced_bread_meme.png"><img src="/assets/images/rewind_sliced_bread_meme.png" alt="A photo of a bag of Wonder bread with the logo changed to REWIND and the tagline to 11 Built-in Properties, SDK, JUnit, Seeds. The meme caption reads: now with property-based testing, the best thing since sliced bread." /></a></p>

<h2 id="two-tellers-java-edition-">Two tellers: Java Edition ☕</h2>

<p>Let’s keep the bank from <a href="/2026/10/07/nix-wrote-half-of-my-debugger">last time</a>: two tellers deposit into one account from two threads, <strong>without a lock</strong>.</p>

<p>This time the example is in Java as a JUnit 5 suite to appeal to my Enterprise brethren.<sup id="fnref:example"><a href="#fn:example" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">deposit</span><span class="o">(</span><span class="kt">int</span> <span class="n">teller</span><span class="o">,</span> <span class="kt">long</span> <span class="n">amount</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">seen</span> <span class="o">=</span> <span class="n">balance</span><span class="o">;</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"teller "</span> <span class="o">+</span> <span class="n">teller</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span>
                        <span class="n">seen</span> <span class="o">+</span> <span class="s">" + "</span> <span class="o">+</span> <span class="n">amount</span><span class="o">);</span>
    <span class="n">balance</span> <span class="o">=</span> <span class="n">seen</span> <span class="o">+</span> <span class="n">amount</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>A teller that runs between the other’s read and store writes a stale balance over the other’s
deposit. 💥</p>

<p>We include a “typical” test suite: one teller, two tellers concurrently, and a new account being empty. We run it and see all tests pass and head home for the day. 🍺</p>

<p>Is the code correct? Let’s use Rewind to dig further.</p>

<h2 id="properties-that-come-in-the-box">Properties that come in the box</h2>

<p>Before even looking at the balance, Rewind knows a lot about what a run <em>did</em>. Every open,
write, rename, close, fork and exit is in the trace, with the step it happened at. Rewind ships
properties computed from the trace alone:</p>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>A run violates it when</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">no-crash</code></td>
      <td>a process dies of a signal such as SIGSEGV, even if a retry hid it</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">no-orphans</code></td>
      <td>a process the job started is still running when the job exits</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">single-writer=GLOB</code></td>
      <td>two processes have a matching file open for writing at once</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">atomic-replace=GLOB</code></td>
      <td>a matching file is truncated in place instead of renamed over</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">no-read-while-written=GLOB</code></td>
      <td>a matching file is opened for reading while another process writes it</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">writes-within=DIR:DIR</code></td>
      <td>the job writes outside these directories</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">junit=GLOB</code></td>
      <td>a test that passed on schedule 0 fails, by the JUnit XML reports</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">script=PATH</code></td>
      <td>a program of yours exits 1</td>
    </tr>
  </tbody>
</table>

<p>Let’s throw a few at the bank and only run schedule 0:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--schedules</span> 0 <span class="nt">--property</span> no-crash <span class="nt">--property</span> no-orphans <span class="se">\</span>
<span class="gp">    --property 'writes-within=/build' github:fzakaria/rewindvm#</span>bank-java
<span class="go">schedule   0: exited:0     148479 steps  ba3f68ae3869  run bd3b527f0a837af8  violates writes-within
  writes-within: step 2710: process 111 (javac) opened /tmp/hsperfdata_nixbld/111 for writing, outside /build
never true in any run: sometimes "deposits-concurrent"
every schedule violated a property, as schedule 0 did
</span></code></pre></div></div>

<p>Interesting! <code class="language-plaintext highlighter-rouge">javac</code> writes its performance counters to <code class="language-plaintext highlighter-rouge">/tmp/hsperfdata_&lt;user&gt;/&lt;pid&gt;</code><sup id="fnref:perfdata"><a href="#fn:perfdata" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>. It has nothing to do with our bug, but I included it because I learned something new and it could have been a bug if it did not include the process ID.</p>

<p>The file properties shine for bugs between processes. The
<a href="https://rewindvm.dev/tutorials/properties.html">properties tutorial</a> has a program that
rewrites its config in place while another process rereads it. Its own test fails only
when a read lands between the truncate and the last write, but <code class="language-plaintext highlighter-rouge">atomic-replace</code> flags it on
the very first run. 😲</p>

<h2 id="asserting-from-the-inside">Asserting from the inside</h2>

<p>The bank’s real invariant is not in any syscall though. It lives in memory in our example: <em>the balance variable must add up</em>.</p>

<p>For that there is the <a href="https://github.com/fzakaria/rewindvm/tree/main/sdk">Rewind SDK</a>, a tiny library you call from your program. Each call
writes one line to <code class="language-plaintext highlighter-rouge">/dev/rewind</code>, a device in Rewind’s VM, and the trace keeps it with the
step, process and thread that wrote it.<sup id="fnref:outside"><a href="#fn:outside" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Account.deposit: another teller is mid-deposit.</span>
<span class="kt">int</span> <span class="n">others</span> <span class="o">=</span> <span class="n">depositing</span><span class="o">.</span><span class="na">getAndIncrement</span><span class="o">();</span>
<span class="nc">Rewind</span><span class="o">.</span><span class="na">sometimes</span><span class="o">(</span><span class="n">others</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">,</span> <span class="s">"deposits-concurrent"</span><span class="o">);</span>

<span class="c1">// BankTest.twoTellersDeposit</span>
<span class="nc">Rewind</span><span class="o">.</span><span class="na">always</span><span class="o">(</span><span class="n">balance</span> <span class="o">==</span> <span class="n">expected</span><span class="o">,</span> <span class="s">"balance-adds-up"</span><span class="o">,</span>
        <span class="o">()</span> <span class="o">-&gt;</span> <span class="s">"balance "</span> <span class="o">+</span> <span class="n">balance</span> <span class="o">+</span> <span class="s">", expected "</span> <span class="o">+</span> <span class="n">expected</span><span class="o">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">always</code> must be true every time the program reaches it. <code class="language-plaintext highlighter-rouge">unreachable</code> is its opposite: a run that reaches it at all fails. These are your typical <code class="language-plaintext highlighter-rouge">assertions</code>, except we get the whole replay if it fails.</p>

<p><code class="language-plaintext highlighter-rouge">sometimes</code> is the one I find the most interesting and tough to think through at times. 🤓 It tests the search: its condition must be true in at least one of the runs check tries, and it never fails a run. In model-checking terms, <code class="language-plaintext highlighter-rouge">always</code> is a <em>safety property</em> (nothing bad happens in any run) and <code class="language-plaintext highlighter-rouge">sometimes</code> is a <em>reachability property</em> (some run gets there). Here it asks whether any run had both tellers inside <code class="language-plaintext highlighter-rouge">deposit</code> at the same time.</p>

<p>Look at the output above again:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">never true in any run: sometimes "deposits-concurrent"
</span></code></pre></div></div>

<p>The suite passed under schedule 0, but one teller always finished its deposit before the
other started. That pass told us nothing about the race. 🤷</p>

<p style="--image-width: 24rem"><a href="/assets/images/rewind_anakin_padme_meme.png"><img src="/assets/images/rewind_anakin_padme_meme.png" alt="Anakin and Padme meme. Anakin: my tests passed. Padme, smiling: so the deposits overlapped, right? Anakin says nothing. Padme: they overlapped, right?" /></a></p>

<p>Let <code class="language-plaintext highlighter-rouge">check</code> try its default 64 schedules:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check github:fzakaria/rewindvm#bank-java
<span class="c">...
</span><span class="go">true in 11 of 17 runs that checked it: sometimes "deposits-concurrent"
</span><span class="c">...
</span><span class="go">step 145417 decides it: a reschedule and a 10 µs stall there makes the run violate same-outcome, sdk

passing: run 61643f768ec51337, schedule 2 over steps 144694..145417
failing: run 643a298ef0ec3f7b, schedule 2 over steps 144694..145418
the two are the same run until step 145417
failing run violates same-outcome: step 149407: ended exited:2, where bd3b527f0a837af8 ended exited:0
failing run violates sdk: step 145713: always "balance-adds-up" failed: balance 380, expected 400
</span></code></pre></div></div>

<p>That took 40 seconds on my laptop<sup id="fnref:jvm"><a href="#fn:jvm" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> and we found that two deposits went missing.</p>

<p>The first line is the <code class="language-plaintext highlighter-rouge">sometimes</code> again: of the 17 runs check tried before it found the
failure, 11 had both tellers inside <code class="language-plaintext highlighter-rouge">deposit</code> at once.</p>

<h2 id="did-i-truly-fix-it">Did I truly fix it?</h2>

<p>The fix is the obvious one: hold a lock from the read to the store.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">others</span> <span class="o">=</span> <span class="n">depositing</span><span class="o">.</span><span class="na">getAndIncrement</span><span class="o">();</span>
<span class="nc">Rewind</span><span class="o">.</span><span class="na">sometimes</span><span class="o">(</span><span class="n">others</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">,</span> <span class="s">"deposits-concurrent"</span><span class="o">);</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">lock</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">seen</span> <span class="o">=</span> <span class="n">balance</span><span class="o">;</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"teller "</span> <span class="o">+</span> <span class="n">teller</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span>
                        <span class="n">seen</span> <span class="o">+</span> <span class="s">" + "</span> <span class="o">+</span> <span class="n">amount</span><span class="o">);</span>
    <span class="n">balance</span> <span class="o">=</span> <span class="n">seen</span> <span class="o">+</span> <span class="n">amount</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">depositing</span><span class="o">.</span><span class="na">decrementAndGet</span><span class="o">();</span>
</code></pre></div></div>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check .#bank-java
<span class="c">...
</span><span class="go">true in 42 of 65 runs that checked it: sometimes "deposits-concurrent"
every property held under all 65 schedules
</span></code></pre></div></div>

<p>Every schedule passes, but is that enough to make us feel warm and fuzzy?</p>

<p>The <code class="language-plaintext highlighter-rouge">sometimes</code> line is what gives me the warm embrace: in 42 of the 65 runs the tellers ran concurrently, but the lock prevented the bug. ✅<sup id="fnref:sync"><a href="#fn:sync" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<p>A condition that is only true when the bug happens can never be true once the bug is fixed. 💡</p>

<h2 id="junit">JUnit</h2>

<p>Java developers though love writing JUnit tests. Fear not my <a href="https://github.com/enterprisequalitycoding/fizzbuzzenterpriseedition">FizzBuzzEnterprise</a> colleagues, Rewind has you covered: the SDK has a JUnit 5 extension.</p>

<p>Put <code class="language-plaintext highlighter-rouge">@ExtendWith(RewindExtension.class)</code> on the test class and it marks each test’s start and result.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ExtendWith</span><span class="o">(</span><span class="nc">RewindExtension</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">BankTest</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
</code></pre></div></div>

<p>If you can’t touch the code, <code class="language-plaintext highlighter-rouge">junit=GLOB</code> reads the XML reports the runner wrote instead:
Maven’s surefire, Gradle, pytest’s <code class="language-plaintext highlighter-rouge">--junitxml</code>, whatever writes the format.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind check <span class="nt">--property</span> <span class="s1">'junit=TEST-*.xml'</span> github:fzakaria/rewindvm#bank-java
<span class="c">...
</span><span class="go">step 145417 decides it: a reschedule and a 10 µs stall there makes the run violate junit, sdk
failing run violates junit: step 149407: test BankTest.twoTellersDeposit() passed in the unperturbed run and failed here
failing run violates sdk: step 145713: always "balance-adds-up" failed: balance 380, expected 400
</span></code></pre></div></div>

<h2 id="visualizing-properties">Visualizing properties</h2>

<p>The Rewind app shows the timeline of a run, with the tests and the build log. The failing test is red, and the event card names the assertion that failed.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rewind open 643a298e 145713 <span class="nt">--compare</span> 61643f76
</code></pre></div></div>

<p><a href="/assets/images/rewind-junit-assertion.png"><img src="/assets/images/rewind-junit-assertion.png" alt="The Rewind app at step 145,713 of the failing bank-java run, compared with the passing run: the timeline zoomed to the tests, the build log ending in always &quot;balance-adds-up&quot; failed: balance 380, expected 400 in red, and the event card naming the failed assertion" /></a></p>

<p>The strip under the timeline has a span per test: green passed, red failed. Each include a helpeful tooltip with the test name and the steps it ran.</p>

<p><a href="/assets/images/rewind-junit-test-strip.png"><img src="/assets/images/rewind-junit-test-strip.png" alt="The timeline's test strip: a green span for the first test, a red one for the second, and a pointer on the red span showing Test BankTest.twoTellersDeposit failed, steps 145,291 to 145,986" /></a></p>

<p>All the other Rewind features can then be used to help isolate the cause: the source
panel, gdb on a fork at any step, the thread lanes and Check from here.</p>

<h2 id="easy-peasy-">Easy Peasy ?</h2>

<p>There is a SDK for many languages already included in the Rewind repository. They are tiny, and the only dependency is the Rewind VM itself. The SDK is a single file or two per language, and you can copy it into your project.<sup id="fnref:languages"><a href="#fn:languages" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>

<ul>
  <li><strong>C and C++</strong>: <code class="language-plaintext highlighter-rouge">rewind.h</code>, <code class="language-plaintext highlighter-rouge">REWIND_ALWAYS(cond, "id", "fmt", ...)</code></li>
  <li><strong>Rust</strong>: <code class="language-plaintext highlighter-rouge">rewind_sdk::always!(cond, "id", "fmt", ...)</code></li>
  <li><strong>Go</strong>: <code class="language-plaintext highlighter-rouge">rewind.Always(cond, "id", "fmt", ...)</code> and <code class="language-plaintext highlighter-rouge">rewind.Track(t)</code> for <code class="language-plaintext highlighter-rouge">go test</code></li>
  <li><strong>Python</strong>: <code class="language-plaintext highlighter-rouge">rewind_sdk.always(cond, "id")</code>, and a pytest plugin that loads itself</li>
  <li><strong>Java</strong>: <code class="language-plaintext highlighter-rouge">Rewind.always(cond, "id")</code> and the JUnit 5 extension</li>
  <li><strong>Shell</strong>: <code class="language-plaintext highlighter-rouge">rewind_always id "message" test "$x" -eq 3</code></li>
</ul>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>curl <span class="nt">-fsSL</span> https://rewindvm.dev/install | sh
<span class="gp">$</span><span class="w"> </span>rewind check github:fzakaria/rewindvm#bank-java
</code></pre></div></div>

<p>The <a href="https://rewindvm.dev/tutorials/properties.html">properties tutorial</a> goes through each
piece step by step, and the <a href="https://rewindvm.dev/tutorials/advanced.html">advanced tutorial</a>
has script properties and a file of properties to keep beside your tests for the more adventurous.</p>

<p>If you happen to find any interesting bugs with Rewind let me know. I am keeping track of a small <a href="https://rewindvm.dev/bugs.html">leaderboard</a> of the best ones. 🏆</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:example">
      <p>It is <a href="https://github.com/fzakaria/rewindvm/tree/main/examples/bank-java"><code class="language-plaintext highlighter-rouge">bank-java</code></a> in the Rewind repository, next to the C version. <a href="#fnref:example" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:perfdata">
      <p><code class="language-plaintext highlighter-rouge">-XX:-UsePerfData</code> turns it off. <a href="#fnref:perfdata" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:outside">
      <p>Outside Rewind the device does not exist and every call does nothing, so the calls can stay in your code. <a href="#fnref:outside" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:jvm">
      <p>The JVM is chatty: 148,479 steps for three tests, where the C equivalent takes about 4,500. <a href="#fnref:jvm" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:sync">
      <p>This counter must sit outside the lock. With <code class="language-plaintext highlighter-rouge">synchronized void deposit</code>, the second teller would wait before it reaches the counter. <a href="#fnref:sync" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:languages">
      <p>The <a href="https://github.com/fzakaria/rewindvm/tree/main/sdk">SDK’s README</a> has the format instrunctions if you want to write one for another language. <a href="#fnref:languages" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Exit 0 means your program exited 0. That’s it. That’s all it means. 🍞 I have been working on a deterministic VM called Rewind VM, where “a run” (i.e. test, command etc.) is a function of its inputs including the thread schedule. The determinism comes from the tool recording every step which lets you replay it exactly. This alone is a pretty good way to find flaky tests: run the test under Rewind, then rewind check runs it under many different thread schedules, with some slight latency added, and failures often crop up. 🐛]]></summary></entry></feed>