<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: arcker</title>
    <description>The latest articles on DEV Community by arcker (@arcker).</description>
    <link>https://dev.to/arcker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979063%2Fb47f840e-534a-44db-83bd-f4f50d654c30.png</url>
      <title>DEV Community: arcker</title>
      <link>https://dev.to/arcker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arcker"/>
    <language>en</language>
    <item>
      <title>cidx v2.1.0 — the most useful output of a release was its seven bug reports</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 19 Jul 2026 08:56:20 +0000</pubDate>
      <link>https://dev.to/arcker/cidx-v210-the-most-useful-output-of-a-release-was-its-seven-bug-reports-43lf</link>
      <guid>https://dev.to/arcker/cidx-v210-the-most-useful-output-of-a-release-was-its-seven-bug-reports-43lf</guid>
      <description>&lt;p&gt;&lt;strong&gt;cidx is a declarative CI/CD runner I build&lt;/strong&gt; (in Go) — you describe your pipeline once, it runs the same locally and in CI, exit-code parity guaranteed. v2.1.0 just shipped. And the most useful thing it produced wasn't the tag.&lt;/p&gt;

&lt;p&gt;The interesting part of a release is almost never the changelog. It's what &lt;em&gt;cutting&lt;/em&gt; the release teaches you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short part (the diff)
&lt;/h2&gt;

&lt;p&gt;11 commits since v2.0.0, after 59 quiet days. A &lt;code&gt;probatum&lt;/code&gt; test-phase preset, a &lt;code&gt;pr edit&lt;/code&gt; action (retitle a PR without dropping to the platform UI), and six fixes: prebuilt &lt;code&gt;cargo-audit&lt;/code&gt; (faster security phase), anonymous-pull fallback when a registry 401s on stale creds, pinning the bootstrapped cidx to the version that generated it (no more &lt;code&gt;@latest&lt;/code&gt; drift), &lt;code&gt;cpw&lt;/code&gt; waiting for the CI workflow to actually start, branch prefixes derived from commit type, and &lt;code&gt;check drift&lt;/code&gt; &lt;em&gt;resolving&lt;/em&gt; the workflow file instead of guessing &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;. Correct, useful, boring. On to the real story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cutting a release with your own tool
&lt;/h2&gt;

&lt;p&gt;cidx can cut a release: &lt;code&gt;cidx release create&lt;/code&gt;. So I used it to cut its own. And that's exactly where it gets interesting — a tool on a tidy demo never shows its rough edges; a tool on a &lt;strong&gt;real&lt;/strong&gt;, high-stakes task (tag it, push a version) shows them all.&lt;/p&gt;

&lt;p&gt;Seven, that day. Filed same-day, tag still warm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;#184&lt;/strong&gt; — &lt;code&gt;release create&lt;/code&gt; pushes the version-bump commit straight to &lt;code&gt;main&lt;/code&gt;… and gets rejected by branch protection. Recovery is a hand-rolled PR-then-cherry-pick-then-tag. The tool doesn't know the rules of the repo it runs on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#185&lt;/strong&gt; — &lt;code&gt;release preview&lt;/code&gt; computes the version from &lt;strong&gt;three sources&lt;/strong&gt; (VERSION, &lt;code&gt;.cz.toml&lt;/code&gt;, latest tag) with no reconciliation. First run: it suggested &lt;code&gt;v1.8.0&lt;/code&gt; when the latest tag was &lt;code&gt;v2.0.0&lt;/code&gt;. A single source of truth was missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#186&lt;/strong&gt; — &lt;code&gt;release tag prepare&lt;/code&gt; waits on an editor with no TTY detection. It hangs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#180&lt;/strong&gt; — &lt;code&gt;cpw&lt;/code&gt; reports "nothing to commit" for untracked files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#178&lt;/strong&gt; — cidx's own &lt;code&gt;cidx.toml&lt;/code&gt; now has &lt;em&gt;real&lt;/em&gt; drift — precisely because fix #177 finally lets &lt;code&gt;check drift&lt;/code&gt; see it: &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;release&lt;/code&gt; phases declared, no matching jobs in &lt;code&gt;ci.yml&lt;/code&gt;. The tool finding its own mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#175&lt;/strong&gt; — breaking-change detection reads &lt;code&gt;type!:&lt;/code&gt; from parsed commits, but the parser regex can't capture the &lt;code&gt;!&lt;/code&gt;. Latent bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#174&lt;/strong&gt; — ~20 CLI hints still point at the deprecated &lt;code&gt;cidx action …&lt;/code&gt; namespace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those show up reading the code. Each is a knot you only feel by &lt;em&gt;pulling on the rope for real&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop working, not the loop breaking
&lt;/h2&gt;

&lt;p&gt;cidx's &lt;code&gt;CLAUDE.md&lt;/code&gt; states a rule: &lt;em&gt;"we eat our own cooking; if a command has bad UX, that becomes the next priority."&lt;/em&gt; v2.1.0 is that sentence in action. The release ships, &lt;strong&gt;and&lt;/strong&gt; the release-tooling backlog grows the same day. That's not a release failure — it's the only honest way to know where the tool hurts: take it seriously on its own most sensitive task.&lt;/p&gt;

&lt;p&gt;And here's the nuance. Everyone says "we dogfood — we use our own tool." The honest version is different: &lt;strong&gt;you use it on the scary thing, and you write down, publicly and same-day, every place it bit.&lt;/strong&gt; The rough edges aren't swept under the rug waiting for a user to find them. They're the roadmap — dated, numbered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The v2.1.0 → v2.2.0 backlog is already those seven issues plus the #178 drift call. The next release will be quieter on features, heavier on release plumbing: TTY detection, one source of truth for "current version", protection-aware push routing, the deprecated-hint sweep. Small releases, tight loop — and each turn, the tool knows a little better where it hurts, because we dared to use it where it counts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;French version on &lt;a href="https://arcker.org/blog/2026-07-19-cidx-v2-1-0/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>The fixed point: a compiler written in its own language just reproduced itself, byte for byte</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 12 Jul 2026 09:08:49 +0000</pubDate>
      <link>https://dev.to/arcker/the-fixed-point-a-compiler-written-in-its-own-language-just-reproduced-itself-byte-for-byte-2bf0</link>
      <guid>https://dev.to/arcker/the-fixed-point-a-compiler-written-in-its-own-language-just-reproduced-itself-byte-for-byte-2bf0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building&lt;/strong&gt; — its compiler proves properties about your code (termination, sound types) and emits tiny x86-64 machine code, no runtime, no GC, no libc. This post stands on its own. The news: the compiler, written in Verbose itself, just reached the classic self-hosting &lt;strong&gt;fixed point&lt;/strong&gt; — it reproduced its entire self, byte for byte.&lt;/p&gt;

&lt;p&gt;Photocopy a photocopy of a photocopy and each generation degrades. Except there's a magic case where the copy is &lt;em&gt;perfect&lt;/em&gt; — identical to the original, forever. That's a &lt;strong&gt;fixed point&lt;/strong&gt;: applying the operation once more changes nothing. Every language whose compiler is written in itself eventually faces one question: &lt;em&gt;the version of me I just produced, when asked to produce me, does it produce exactly me?&lt;/em&gt; On 2026-07-11, Verbose answered yes, to the byte. And it's on &lt;code&gt;main&lt;/code&gt; now — not a branch — 18 commits closing the arc opened June 13.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three generations
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  gen0  ← the Verbose emitter, compiled by the Rust host compiler.
          (what a fresh checkout builds)
            │  feed it the 855 KB of Verbose source
            ▼
  gen1  ← the emitted ELF: the Verbose emitter, but compiled BY Verbose.
            │  feed it the SAME 855 KB source
            ▼
  gen2  ← the ELF gen1 emits in turn.

  The gate:   gen1  ==  gen2   (byte for byte)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gen0&lt;/code&gt; is the Rust-built bootstrap. &lt;code&gt;gen1&lt;/code&gt; is Verbose compiled by itself once. &lt;code&gt;gen2&lt;/code&gt; is Verbose-compiled-by-Verbose compiling itself again. Verdict: &lt;strong&gt;&lt;code&gt;gen1 == gen2 == 1,302,980 bytes&lt;/code&gt;, same sha256 &lt;code&gt;001481eb…&lt;/code&gt;.&lt;/strong&gt; Not "it works" — the &lt;em&gt;same binary, bit for bit, two generations running&lt;/em&gt;. Verified by an &lt;code&gt;#[ignore]&lt;/code&gt;d regression test (needs ~9 GB RAM, ~30 s).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why byte-identical, not just "it compiles"
&lt;/h2&gt;

&lt;p&gt;"It compiles" only asks for a binary that &lt;em&gt;runs&lt;/em&gt;. The fixed point asks more: that the compiler, reproducing itself, doesn't drift by a single byte. If it drifted — even one bit — &lt;code&gt;gen1&lt;/code&gt; and &lt;code&gt;gen0&lt;/code&gt; wouldn't be &lt;em&gt;the same compiler&lt;/em&gt;; one would emit something the other doesn't. Byte-identity proves the language stands on its own floor: from here, Verbose no longer needs Rust to rebuild itself identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What almost stopped it — and why the diagnosis is the real story
&lt;/h2&gt;

&lt;p&gt;A first attempt crashed. Hypothesis: a 32-bit integer overflow in the emitter's counters. Reasonable, given the input size. &lt;strong&gt;It was wrong&lt;/strong&gt; — and it's written up as wrong in the commit, not quietly dropped. &lt;code&gt;ptrace&lt;/code&gt; + &lt;code&gt;objdump&lt;/code&gt; at the actual faulting address gave the real cause: &lt;strong&gt;arena exhaustion&lt;/strong&gt;. The faulting instruction wrote a node 40 bytes &lt;em&gt;past&lt;/em&gt; the 1 GiB arena. 10.3M nodes × 104 bytes = exactly the limit, crossed by 40 bytes. Not a wild pointer — a clean overflow at the boundary.&lt;/p&gt;

&lt;p&gt;Why does &lt;code&gt;gen1&lt;/code&gt; overflow when &lt;code&gt;gen0&lt;/code&gt; holds, on the same input? Two different memory models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  gen0 (Rust backend)          gen1 (self-hosted emitter)
  stack-passes records         arena-stores EVERY record AND variant
  reuses the space             never reclaims
  ~1.5M nodes at peak          ~84M nodes, ~8.7 GB RSS at peak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gen0&lt;/code&gt; works on a whiteboard it wipes as it goes; &lt;code&gt;gen1&lt;/code&gt; writes in a notebook it never tears a page from. Fine for a factorial; it overflows compiling the &lt;em&gt;whole compiler&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix — and the fix that waits, on purpose
&lt;/h2&gt;

&lt;p&gt;PR #102's fix is small: the emitter's ELF prologue now &lt;code&gt;mmap&lt;/code&gt;s 16 GiB with &lt;code&gt;MAP_NORESERVE&lt;/code&gt;. Reserving is free — like reserving a whole parking lot but paying only for the spots you actually use; only the ~8.7 GB actually touched costs RAM, and small programs touch almost none.&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;real&lt;/strong&gt; memory fix (dedupe an O(sites × program) recompute, reclaim the arena between procedures) is documented and &lt;strong&gt;explicitly deferred&lt;/strong&gt;. It would melt the 8.7 GB peak toward &lt;code&gt;gen0&lt;/code&gt;'s footprint. It would &lt;strong&gt;not&lt;/strong&gt; change the byte-identical output.&lt;/p&gt;

&lt;p&gt;That decoupling is the point. A classic trap in self-hosted-compiler milestones is coupling the &lt;em&gt;correctness proof&lt;/em&gt; to a &lt;em&gt;specific optimization&lt;/em&gt;. Here they're separate: &lt;strong&gt;byte-identity is a semantic property of the emitter, and it holds today; the memory footprint is a resource property, and it improves later.&lt;/strong&gt; The proof doesn't depend on the perf. Same discipline as everywhere in these projects: name the limit instead of hiding it — the 8.7 GB peak, the O(sites × program) recompute, all written on the same commit that ships the fixed point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it means
&lt;/h2&gt;

&lt;p&gt;The full chain the project was built toward is now demonstrated &lt;em&gt;in itself&lt;/em&gt;: &lt;code&gt;parse → verify (four proofs: lints, sound types, purity, termination) → interpret → emit → self-compile byte-identical → reproduce its entire self&lt;/code&gt;. The self-hosted checker verifying its own proofs, the self-hosted emitter emitting its own ELF, and the byte-identity gate between generations — three claims holding at once. A perfect photocopy of itself, twice over. Verbose stands on its own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full version (in French) on &lt;a href="https://arcker.org/blog/2026-07-12-le-point-fixe/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>rust</category>
      <category>programminglanguages</category>
      <category>assembly</category>
    </item>
    <item>
      <title>lithair 1.0 — I'm here, and yet</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:00:48 +0000</pubDate>
      <link>https://dev.to/arcker/lithair-10-im-here-and-yet-3jb6</link>
      <guid>https://dev.to/arcker/lithair-10-im-here-and-yet-3jb6</guid>
      <description>&lt;p&gt;&lt;strong&gt;lithair is a memory-first Rust web framework I'm building&lt;/strong&gt; — one compiled binary serves the frontend, runs the cluster, and &lt;em&gt;is&lt;/em&gt; the database, event-sourced, no external services when the working set fits. This post stands on its own. Eight weeks after v0.2, it hit &lt;strong&gt;1.0&lt;/strong&gt;. And I want to be honest about how that actually feels, because it isn't a story about code.&lt;/p&gt;

&lt;p&gt;The tag is cut. &lt;code&gt;v1.0.0&lt;/code&gt;. I should be celebrating, and instead I'm looking at the screen thinking there's still stuff left. That it isn't quite finished — even though it's tested end-to-end, soaked in production under real traffic, and survived its own upgrade without losing a single record. Objectively: solid. Subjectively: I don't quite believe it yet.&lt;/p&gt;

&lt;p&gt;It took me a while to understand why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the distrust is actually rating
&lt;/h2&gt;

&lt;p&gt;It isn't rating lithair. The numbers are there: the measured cluster envelope, backup proven by &lt;em&gt;actually restoring&lt;/em&gt;, eight roadmap gates closed one by one. The distrust is rating something else — what it is to put something you care about out there under your name.&lt;/p&gt;

&lt;p&gt;There's the small voice: &lt;em&gt;"an AI wrote it anyway."&lt;/em&gt; And it's true an AI generated a lot of the lines. But a framework isn't its lines — it's its decisions. Choosing to publish the cluster's envelope instead of claiming infinite scale. Making the admin secure by default. Naming the limits instead of burying them. Deciding, after two years, what to build and — more importantly — what to refuse. The AI typed. The idea, the calls, the standard — those are mine. Typing isn't authorship.&lt;/p&gt;

&lt;p&gt;And there's the question of whether it gets seen. But I have to be precise here, because this is where I could misrepresent myself: I didn't build it to break through. I built it for myself — to propose my vision and see it hold. People showing up would please me; it was never the goal, and I'm not waiting on it. Traction is a lottery anyway — timing, luck, network. Pinning two years of work to a draw I don't control would be handing my peace to chance, when the real reward is already here: the vision exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually there
&lt;/h2&gt;

&lt;p&gt;So I strip the feeling and look at the thing.&lt;/p&gt;

&lt;p&gt;Two years ago, lithair was an idea. One stubborn idea: &lt;em&gt;what if a single binary could serve the site, run the cluster, and be the database?&lt;/em&gt; Not three services, not six layers, not an infra to operate. A compiled binary, state in memory when it fits, history in an event store, security close to the runtime.&lt;/p&gt;

&lt;p&gt;Today that idea compiles. It runs. It's tested. And it holds this very site on its own — three sites, one binary, secure admin, hot-reload. Website, cluster, database, in the same executable. The all-in-one isn't a line on a slide anymore; it's a process with a PID that replays its log on boot and serves pages. That's not nothing. That's the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest about what remains
&lt;/h2&gt;

&lt;p&gt;A 1.0 isn't a finish line. It's "the floor is solid enough to build on." Optimizations remain — compute throughput, true zero-downtime on a &lt;em&gt;binary&lt;/em&gt; upgrade (today it's a brief graceful restart, which I wrote in the docs instead of pretending otherwise). But the starting base is the right one: a stable API contract, on-disk format unchanged since 0.13, and what's there is measured and documented — limits included. That's what I always meant by "production-ready": not "it does everything," but "the envelope is measured and written down." The load plate, bolted to the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no one can take
&lt;/h2&gt;

&lt;p&gt;The AI codes at the start. Then the rest — the standards, the judgment, the ecosystem built around it, the two years. lithair is just one exhibit of that, next to my other projects. And the idea from two years ago — an all-in-one that holds — is here. Really here.&lt;/p&gt;

&lt;p&gt;Maybe I don't quite believe it yet. But it exists now. And that, no one can take from me.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>A compiler written in its own language just emitted a standalone Linux executable</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:47:45 +0000</pubDate>
      <link>https://dev.to/arcker/a-compiler-written-in-its-own-language-just-emitted-a-standalone-linux-executable-3lld</link>
      <guid>https://dev.to/arcker/a-compiler-written-in-its-own-language-just-emitted-a-standalone-linux-executable-3lld</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building.&lt;/strong&gt; Its compiler proves properties about your code — like termination — and emits tiny, readable x86-64 machine code: no runtime, no GC, no libc. This post stands on its own (you don't need the rest of the series). What it's about: a compiler &lt;em&gt;written in Verbose itself&lt;/em&gt; reached the point where a single rule emits a &lt;strong&gt;standalone Linux ELF&lt;/strong&gt; that runs and prints its answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Honesty first: this lives on a working branch — &lt;strong&gt;not merged, not tagged.&lt;/strong&gt; It's verified (green test suite, real in-memory execution, a measured benchmark), but it isn't in a released version, and it covers a &lt;em&gt;sub-language&lt;/em&gt; (scalar arithmetic), not all of Verbose. More on that at the end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Two halves of a compiler
&lt;/h2&gt;

&lt;p&gt;A compiler has a half that &lt;strong&gt;reads&lt;/strong&gt; (text → tokens → tree) and a half that &lt;strong&gt;writes&lt;/strong&gt; (tree → machine code). The reading half is the usual story. The new part is the writing half — and the thing that makes it interesting here is that the code generator &lt;strong&gt;is itself a Verbose rule&lt;/strong&gt;. The Rust compiler underneath (&lt;code&gt;verbosec&lt;/code&gt;) only &lt;em&gt;runs&lt;/em&gt; it; the machine code is emitted by rules written in the language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow factorial all the way down
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@proof termination decreasing : n
rule fact (n : number [0, 20]) -&amp;gt; number {
  if n == 0 then 1 else n * fact(n - 1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the guardrails that survive all the way to the bytes: &lt;code&gt;@proof termination decreasing : n&lt;/code&gt; (the compiler &lt;em&gt;requires&lt;/em&gt; proof the recursion ends) and &lt;code&gt;[0, 20]&lt;/code&gt; (the declared domain of &lt;code&gt;n&lt;/code&gt;). The generator walks this tree and emits, per node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  n == 0              cmp ; sete            → boolean in a register
  if … then … else …  jz over_then ; &amp;lt;then&amp;gt; ; jmp end ; over_then: &amp;lt;else&amp;gt; ; end:
  n * fact(n - 1)     &amp;lt;compute fact(n-1)&amp;gt; ; imul
  fact(n - 1)         call &amp;lt;offset&amp;gt;         → it calls itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jumps carry &lt;strong&gt;computed offsets&lt;/strong&gt;: you only know a jump's distance once both branches are emitted, so the generator runs twice — measure, then write — the way you number a book's pages only after laying it out.&lt;/p&gt;

&lt;p&gt;The milestone is that &lt;code&gt;call&lt;/code&gt;/&lt;code&gt;ret&lt;/code&gt;. When &lt;code&gt;fact&lt;/code&gt; calls itself, it's a stack of plates you build up, then unwind while multiplying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  build up                   unwind (ret)
  fact(3) n=3                3 × 2 = 6   ◄── final
   └ fact(2) n=2             2 × 1 = 2
      └ fact(1) n=1          1 × 1 = 1
         └ fact(0) n=0 ─► 1  (base case)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first time a rule written in Verbose emits machine code that &lt;strong&gt;references itself&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a byte blob to a real executable
&lt;/h2&gt;

&lt;p&gt;Bytes in a buffer don't run on their own — you have to wrap them so Linux will launch them. That wrapper is the &lt;strong&gt;ELF&lt;/strong&gt; format, and the rule now generates the whole thing, like a ready-to-ship parcel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ELF64 header         ← "this is a Linux executable"
  LOAD program header  ← "map this into memory, r+x"
  _start               ← call main ; rax → itoa ; sys_write ; sys_exit
  code for main, fact  ← the computation itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No sections, no symbols, no linked library. A few hundred bytes. The last brick added &lt;code&gt;itoa&lt;/code&gt; (int → decimal ASCII) &lt;strong&gt;in machine code&lt;/strong&gt; — without it the binary computed &lt;code&gt;120&lt;/code&gt; but couldn't show it. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./verbosec examples/factorial.verbose &lt;span class="nt"&gt;--run&lt;/span&gt; lower factorial 5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; a.out
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x a.out
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./a.out
&lt;span class="go"&gt;120
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standalone — no &lt;code&gt;verbosec&lt;/code&gt; needed to &lt;em&gt;run&lt;/em&gt; the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest benchmark
&lt;/h2&gt;

&lt;p&gt;The long-standing question — "what does a binary built this way actually buy versus C/Rust/Go?" — finally has a &lt;em&gt;measured&lt;/em&gt; answer (Ubuntu 24.04, gcc 13.3, rustc 1.90, go 1.25.4):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  trivial (print a constant, exit)
              size        wall(ms)  RSS(kB)  syscalls
  verbose        512       0.19      352        2
  gcc        706 584       0.24      664       17
  rustc    3 871 984       0.57    2 112       62
  go       2 254 335       1.28    2 132      228

  fib(40) = 102334155  (deep recursion — raw compute)
              size        wall(ms)  RSS(kB)  syscalls
  verbose        635      ~713       352        2
  gcc        706 584      ~135       664       17
  rustc    3 872 832      ~224     2 216       62
  go       2 254 624      ~380     2 232      406
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it both ways. &lt;strong&gt;Wins, by construction:&lt;/strong&gt; ~1100–1380× smaller than &lt;code&gt;gcc -static&lt;/code&gt;, ~6000× vs rustc; two syscalls vs 17–228; RSS down 2–6×. Because there's no runtime — nothing to initialize, no global allocator, no dynamic library. These are &lt;em&gt;audit-defensible&lt;/em&gt;: there's almost nothing to audit. &lt;strong&gt;Loss, by design:&lt;/strong&gt; on &lt;code&gt;fib(40)&lt;/code&gt;, Verbose is ~5× gcc's wall time. The emitted blob has seen &lt;em&gt;no&lt;/em&gt; optimizer — no register allocation, no inlining, no constant folding past the AST. That was a decision (a direct, line-by-line auditable emitter, not an LLVM backend), and the compute cost is the price of that legibility — published, not hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't there yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's a &lt;strong&gt;sub-language&lt;/strong&gt;: scalar arithmetic (&lt;code&gt;+ − × ÷ %&lt;/code&gt;, comparisons, &lt;code&gt;and/or&lt;/code&gt;, &lt;code&gt;if/else&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, recursive calls). No strings, no I/O, no services, no TLS.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;branch-only&lt;/strong&gt;, not merged, not tagged.&lt;/li&gt;
&lt;li&gt;No optimization — deliberately deferred to keep every byte auditable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't "Verbose beats gcc." It's "here's exactly what Verbose buys, and what it costs, measured." The &lt;em&gt;read&lt;/em&gt; half and the &lt;em&gt;write&lt;/em&gt; half of a self-hosting compiler just met one notch closer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full walk-through (in French) on &lt;a href="https://arcker.org/blog/2026-06-21-une-regle-qui-ecrit-un-executable/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>rust</category>
      <category>assembly</category>
      <category>programminglanguages</category>
    </item>
    <item>
      <title>Every elevator has a load plate. Tests are supposed to kill fear, not feed it.</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 14 Jun 2026 14:37:31 +0000</pubDate>
      <link>https://dev.to/arcker/every-elevator-has-a-load-plate-tests-are-supposed-to-kill-fear-not-feed-it-56mc</link>
      <guid>https://dev.to/arcker/every-elevator-has-a-load-plate-tests-are-supposed-to-kill-fear-not-feed-it-56mc</guid>
      <description>&lt;p&gt;Every elevator has a small metal plate near the doors: &lt;strong&gt;8 persons — 630 kg&lt;/strong&gt;. It doesn't mean the car snaps at the ninth person. It means an engineer loaded it, measured it, and signed an envelope: &lt;em&gt;this is what I guarantee, within these limits, with margin.&lt;/em&gt; The plate isn't a confession of weakness. It's the exact thing that lets you step in without a second thought.&lt;/p&gt;

&lt;p&gt;I build &lt;strong&gt;Lithair — a small, memory-first Rust web framework&lt;/strong&gt;. This post isn't about its API; it stands on its own. It's about a sentence I've come to believe — &lt;strong&gt;DevOps without the test isn't DevOps&lt;/strong&gt; — and the reason for it is probably not the one you expect. It's not about laziness. It's about fear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plate is permission to stop worrying
&lt;/h2&gt;

&lt;p&gt;Look around and the load-bearing things in the physical world all carry their envelope stamped on the outside. A bridge has a weight rating. A cable has an amperage. A climbing carabiner has a kN number etched into the spine. None of those numbers are bureaucracy. Each one is permission: &lt;em&gt;you don't have to be afraid of this, here's exactly how far it's been taken.&lt;/em&gt; The plate converts a private unknown into a public, re-checkable fact, and the reward for that conversion is that nobody has to feel the unknown anymore.&lt;/p&gt;

&lt;p&gt;That is what a test is for. Not a gate. Not a ritual. A way to stop being afraid of your own system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've actually seen
&lt;/h2&gt;

&lt;p&gt;I've spent years around enterprise software, and when real testing existed, it usually wore one of two shapes — and both, underneath, were shaped by fear.&lt;/p&gt;

&lt;p&gt;There were the &lt;strong&gt;dedicated performance teams&lt;/strong&gt;: a silo you handed the question to, where the schedule was measured in quarters, and where sometimes the software itself had to be bent to fit the benchmark rather than the other way around. Real rigor, but distant — the envelope lived in someone else's backlog.&lt;/p&gt;

&lt;p&gt;And there were the &lt;strong&gt;functional-test teams&lt;/strong&gt; who panicked the moment anyone touched anything. Change the color of a logo and you'd get a straight face and the words "non-regression testing." Not because they were unreasonable — because they had no fast, trustworthy way to know whether your one-line change broke something three modules away. So they did the only thing fear leaves you: they said &lt;em&gt;don't touch it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to be careful here, because it's easy to read this as contempt and it isn't. That fear is &lt;strong&gt;completely rational&lt;/strong&gt; when you can't verify. If I couldn't check, I'd guard the gate too. The tragedy isn't the fear. It's that the one thing built to dissolve it — automated tests, ideally backed by a dedicated tool — so often gets turned into another thing to be afraid of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests are the cure for that fear
&lt;/h2&gt;

&lt;p&gt;A test you actually trust does something quietly radical: it gives you back the right to change things. Change the logo color and the suite tells you, in seconds, that the checkout flow still works. Change the storage engine and the harness tells you the throughput still holds. The fear doesn't get managed or scheduled or escalated — it just &lt;em&gt;leaves&lt;/em&gt;, because the unknown it fed on is now a number on a screen.&lt;/p&gt;

&lt;p&gt;That's the whole point of the plate. You can step into the elevator because someone already took it to the edge and wrote down where the edge is. Automated tooling is how you stamp that plate cheaply enough to do it for everything, not just the one system important enough to rent a perf team. And &lt;strong&gt;DevOps — the actual discipline, not the job title — is made for exactly this&lt;/strong&gt;: turning "don't touch it" into "touch it freely, the tooling will tell you the truth."&lt;/p&gt;

&lt;h2&gt;
  
  
  So we built the bench
&lt;/h2&gt;

&lt;p&gt;Lithair's cluster had no performance team behind it and no QA gate in front of it. There was just the question — &lt;em&gt;what does it hold?&lt;/em&gt; — and nobody to hand it to. So the test became the work: we wrote our own stress harness and ran it until the numbers stopped being opinions.&lt;/p&gt;

&lt;p&gt;A three-node cluster. &lt;strong&gt;170,200 writes. Zero drops, zero panics, zero replication divergence&lt;/strong&gt; across all three nodes. Good numbers — but the one that actually matters isn't a success, it's a ceiling: &lt;strong&gt;~210–240 single-writes per second, per leader.&lt;/strong&gt; Past that wall, the latency you measure isn't instability — it's pure queueing against the ceiling. The mild decay over a long run (241 → 206 ops/s) traces to in-memory state growth, which is exactly what a memory-first design predicts — not a leak.&lt;/p&gt;

&lt;p&gt;We wrote all of it down. That's the plate. And the real payoff isn't the throughput number — it's that I can now change the cluster code and &lt;em&gt;not be afraid of it&lt;/em&gt;, because the harness will tell me the moment I leave the envelope.&lt;/p&gt;

&lt;h2&gt;
  
  
  A plate only kills fear if it's honest
&lt;/h2&gt;

&lt;p&gt;This is the part that's tempting to skip. A load rating that hides its caveats is worse than none, because it manufactures fresh fear later — the kind that shows up at 3 a.m. So the runbook names the ugly bits out loud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The leader election &lt;strong&gt;isn't Raft&lt;/strong&gt; — it's static, lowest live node ID wins. Always keep a node 0.&lt;/li&gt;
&lt;li&gt;There's a brief &lt;strong&gt;two-leaders window&lt;/strong&gt; when an old leader rejoins after a partition. Documented and non-blocking — committed split-brain is prevented elsewhere, by majority-ack on writes — but it's real, so it's on the page.&lt;/li&gt;
&lt;li&gt;A couple of endpoints are still stubs in this version. Named, not buried.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is comfortable to publish under your own name. All of it belongs on the plate. A caveat you wrote down is one nobody has to discover in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the test is the deliverable, not the feature
&lt;/h2&gt;

&lt;p&gt;The cluster code already worked before any of this started — replication, election, failover, all running. What it lacked wasn't capability. It lacked a plate, and you cannot stamp a plate you didn't measure.&lt;/p&gt;

&lt;p&gt;"Production-ready" without an envelope test is an adjective: you believe it or you don't, and belief is just fear wearing optimism. &lt;em&gt;With&lt;/em&gt; the test it becomes a falsifiable claim anyone can re-run — and a falsifiable claim is the only kind that lets a whole team relax. Strip the test out and what's left isn't speed or risk, it's the fear: the "don't touch it," the non-regression theater over a logo color, the silo nobody wants to disturb. DevOps was supposed to be the discipline that retires all of that. &lt;strong&gt;Without the test, you've kept the dashboards and the dread, and quietly thrown away the engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You can put a plate on almost anything
&lt;/h2&gt;

&lt;p&gt;The format generalizes to nearly every load-bearing thing you ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an API's requests/sec before tail latency cliffs,&lt;/li&gt;
&lt;li&gt;a queue's depth before backpressure kicks in,&lt;/li&gt;
&lt;li&gt;a batch job's row count before it OOMs,&lt;/li&gt;
&lt;li&gt;a cluster's writes/sec before it's just queueing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's always the same shape: &lt;em&gt;here's what I hold, here's why it moves, here's where you leave the tested region.&lt;/em&gt; And the reward is always the same too — not bragging rights, but the simple ability for you and the people around you to stop being afraid of your own system. Sometimes the entire tax is one afternoon with a stress harness and the nerve to write down what you actually find.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full numbers, the cluster runbook, and the v0.13.0 release that earned the plate are &lt;a href="https://arcker.org/blog/2026-06-14-lithair-cinq-piliers/" rel="noopener noreferrer"&gt;in the companion post on arcker.org&lt;/a&gt; (in French).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>testing</category>
      <category>performance</category>
      <category>rust</category>
    </item>
    <item>
      <title>A program is a tree — building a Verbose compiler in Verbose</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 14 Jun 2026 09:11:25 +0000</pubDate>
      <link>https://dev.to/arcker/a-program-is-a-tree-building-a-verbose-compiler-in-verbose-4927</link>
      <guid>https://dev.to/arcker/a-program-is-a-tree-building-a-verbose-compiler-in-verbose-4927</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building.&lt;/strong&gt; Its compiler proves properties about your code — like termination — and emits tiny, readable x86-64 machine code: no runtime, no GC, no libc. This post stands on its own (you don't need the rest of the series). What it's about: I'm now writing a Verbose compiler &lt;em&gt;in Verbose itself&lt;/em&gt;, and this is the foundation brick — how you represent a program as data so a compiler can work on it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(English version of an article from my French series, originally on &lt;a href="https://arcker.org/blog/2026-06-07-un-programme-cest-un-arbre/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After cryptography, we take on something more vertiginous: &lt;strong&gt;a Verbose compiler written in Verbose&lt;/strong&gt;. The language starting to describe itself.&lt;/p&gt;

&lt;p&gt;Let's be honest up front — it matters. This is not (yet) verbosec compiling the entirety of its own source. What exists today is a &lt;strong&gt;complete front end&lt;/strong&gt; — tokenizer, parser, analyses, interpreter, type checker — written &lt;em&gt;in Verbose&lt;/em&gt;, for a &lt;strong&gt;toy subset&lt;/strong&gt; of the language. The whole thing compiled by verbosec to native machine code. Not an interpreted demo: a ~60 KB ELF binary that reads your program and tells you what's wrong with it. That's &lt;code&gt;examples/vexprparse.verbose&lt;/code&gt;: 102 concepts, 219 rules.&lt;/p&gt;

&lt;p&gt;We'll walk through it brick by brick. This chapter lays the foundation without which nothing else exists: &lt;strong&gt;how to represent a program&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why write the compiler in Verbose?
&lt;/h2&gt;

&lt;p&gt;The question deserves an answer, because it isn't just an exercise — it touches Verbose's whole thesis.&lt;/p&gt;

&lt;p&gt;Today, the compiler (verbosec) is written in &lt;strong&gt;Rust&lt;/strong&gt;. And some of the logic — certain primitives — is Rust that emits x86-64 directly, &lt;em&gt;with no Verbose source&lt;/em&gt;. The concrete consequence: to audit a Verbose binary, to &lt;em&gt;really&lt;/em&gt; understand what it does, at some point you have to read Rust. And trust that Rust — and whoever, or whatever, wrote it.&lt;/p&gt;

&lt;p&gt;That's precisely what Verbose refuses. The whole series rests on four words: &lt;em&gt;you don't trust, you verify&lt;/em&gt;. You read the source, declared and proven. If the path from source to binary runs through unverifiable Rust, trust leaks out there.&lt;/p&gt;

&lt;p&gt;Writing the front end &lt;em&gt;in Verbose&lt;/em&gt; moves that logic into the language itself: the tokenizer, the parser, the analyses become a &lt;code&gt;.verbose&lt;/code&gt; file, verified under Verbose's proof regime, then compiled native. The auditor reads Verbose, not Rust. The remaining Rust shrinks to a small, stable, &lt;strong&gt;trusted-once&lt;/strong&gt; base (the verifier). Per-binary trust moves from Rust to the proven source.&lt;/p&gt;

&lt;p&gt;And it's the ultimate dogfooding: a compiler is the hardest thing to express. If Verbose can describe its own front end, under its own proof regime, then the language isn't a toy — it holds up on the most demanding task there is.&lt;/p&gt;




&lt;h2&gt;
  
  
  From text to a tree
&lt;/h2&gt;

&lt;p&gt;A compiler can do nothing with flat text. &lt;code&gt;x + y * 2&lt;/code&gt;, to a human, is a string of characters; to a compiler, it's a &lt;strong&gt;structure&lt;/strong&gt; — a tree, where the multiplication nests under the addition (operator precedence):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  The text  "x + y * 2"  is really a tree:

            ( + )
           /     \
         x      ( * )
               /     \
             y         2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything starts there. Before evaluating, type-checking, or catching an undefined variable — you first have to turn the text into that tree. That's the AST (&lt;em&gt;Abstract Syntax Tree&lt;/em&gt;). And to build it, you need a way to represent a tree &lt;strong&gt;as data&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A tree is a recursive sum type
&lt;/h2&gt;

&lt;p&gt;This is where the earlier chapters pay off. A tree is declared in Verbose as a &lt;strong&gt;sum type&lt;/strong&gt; — a type that can take several shapes — some of whose shapes &lt;strong&gt;reference themselves&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;concept Ast
  variants:
    AstNum  of (value : number)
    AstVar  of (start : number, len : number)
    AstBin  of (op : number, lhs : Ast, rhs : Ast)
    AstNeg  of (inner : Ast)
    AstIf   of (cond : Ast, thn : Ast, els : Ast)
    AstCall of (callee_start : number, callee_len : number, args : ArgList)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;AstBin&lt;/code&gt;: a binary operation holds an operator, &lt;strong&gt;a left subtree &lt;code&gt;Ast&lt;/code&gt;, and a right subtree &lt;code&gt;Ast&lt;/code&gt;&lt;/strong&gt;. The type contains itself. That's the recursion of a tree: an addition whose two sides are, themselves, expressions. &lt;code&gt;AstIf&lt;/code&gt; holds three (condition, &lt;em&gt;then&lt;/em&gt; branch, &lt;em&gt;else&lt;/em&gt; branch). &lt;code&gt;AstNum&lt;/code&gt; and &lt;code&gt;AstVar&lt;/code&gt; are &lt;strong&gt;leaves&lt;/strong&gt; — they hold no other &lt;code&gt;Ast&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our example then becomes, exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  AstBin( + ,
          AstVar(x),
          AstBin( * , AstVar(y), AstNum(2)) )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tree drawn above, written as a value. And &lt;code&gt;a.b.c&lt;/code&gt;? &lt;code&gt;AstField(AstField(AstVar(a), b), c)&lt;/code&gt; — the nesting follows the structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  No pointers: an index arena
&lt;/h2&gt;

&lt;p&gt;One problem remains. Verbose has no heap and no pointers — one of the reasons its binaries are so small and so verifiable. So how do you build a tree of arbitrary size?&lt;/p&gt;

&lt;p&gt;The answer: an &lt;strong&gt;arena&lt;/strong&gt;. All nodes live in a single bounded space, and a node points to its children by their &lt;strong&gt;index&lt;/strong&gt;, not by a pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  concept_group VExpr [max_depth: 4096, max_nodes: 65535]

  arena:  [0]  AstVar(x)
          [1]  AstVar(y)
          [2]  AstNum(2)
          [3]  AstBin( * , lhs=1, rhs=2)    ← references indices 1 and 2
          [4]  AstBin( + , lhs=0, rhs=3)    ← the root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tree is built bottom-up: leaves first, then the nodes that link them. &lt;code&gt;max_depth: 4096, max_nodes: 65535&lt;/code&gt; aren't decorative — they're the &lt;strong&gt;static bounds&lt;/strong&gt; the verifier needs to prove everything stays finite. No dynamic allocation, no possible overflow, and yet a tree of any shape.&lt;/p&gt;

&lt;p&gt;In the same group live the tokens, the environments, and the diagnostics — all variants of &lt;code&gt;VExpr&lt;/code&gt;, all linked by index. One arena for the whole front end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this brick first
&lt;/h2&gt;

&lt;p&gt;Because everything else plugs into it. The tokenizer will produce &lt;code&gt;Token&lt;/code&gt;s in this arena. The parser will consume them to build &lt;code&gt;Ast&lt;/code&gt;s. The analyses will walk the tree to find your mistakes. The interpreter will descend it to compute a result. Without a way to represent the tree — recursive, bounded, verifiable — there's no compiler at all.&lt;/p&gt;

&lt;p&gt;And it's the direct payoff of what we built before: the recursion of &lt;a href="https://arcker.org/blog/2026-05-25-from-idea-to-binary/" rel="noopener noreferrer"&gt;chapter 1&lt;/a&gt;, the termination proofs of &lt;a href="https://arcker.org/blog/2026-05-26-proving-termination/" rel="noopener noreferrer"&gt;chapter 3&lt;/a&gt;. An AST is &lt;em&gt;the&lt;/em&gt; recursive structure par excellence — and Verbose represents it under the same guarantees as everything else: bounded, pointerless, proven finite.&lt;/p&gt;

&lt;p&gt;The program has become data. The next chapter builds it from raw text: the tokenizer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arcker.org/blog/2026-06-07-un-programme-cest-un-arbre/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;, where the full series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>programminglanguages</category>
      <category>ast</category>
      <category>rust</category>
    </item>
    <item>
      <title>TLS 1.3 without a library — a real browser does the handshake against Verbose machine code</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:44:22 +0000</pubDate>
      <link>https://dev.to/arcker/tls-13-without-a-library-a-real-browser-does-the-handshake-against-verbose-machine-code-5c1f</link>
      <guid>https://dev.to/arcker/tls-13-without-a-library-a-real-browser-does-the-handshake-against-verbose-machine-code-5c1f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building&lt;/strong&gt; — its compiler proves properties about your code (like termination) and emits tiny, readable x86-64 machine code, with no runtime, no GC, no libc. This post stands on its own; you don't need any prior context. And it describes the wildest thing Verbose has done so far.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(English version of an article from my French series, originally on &lt;a href="https://arcker.org/blog/2026-06-04-tls-sans-bibliotheque/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When your browser shows a little padlock next to a URL, it has just held a cryptographic conversation with the server — the TLS &lt;em&gt;handshake&lt;/em&gt;. That conversation is usually handled by enormous C libraries: OpenSSL, BoringSSL, hundreds of thousands of lines nobody ever reads in full.&lt;/p&gt;

&lt;p&gt;This article is the &lt;strong&gt;capstone of the crypto arc&lt;/strong&gt; of the series. It shows the payoff: a real browser opens an HTTPS page served by a binary whose &lt;em&gt;every&lt;/em&gt; cryptographic transform — the key exchange, the identity signature, the bulk encryption, the hash — is &lt;strong&gt;machine code emitted by Verbose&lt;/strong&gt;. Not one line of OpenSSL. And the browser, the most demanding TLS client there is, can't tell the difference: it completes the handshake and renders the page.&lt;/p&gt;

&lt;p&gt;We build on &lt;a href="https://arcker.org/blog/2026-06-01-sha256-from-nothing/" rel="noopener noreferrer"&gt;SHA-256&lt;/a&gt; (chapter 1 of the arc) — it shows up everywhere in TLS. The details of each brick (AES, Ed25519) get their own chapters; here we pull up and watch the whole thing work.&lt;/p&gt;




&lt;h2&gt;
  
  
  A handshake, in three ideas
&lt;/h2&gt;

&lt;p&gt;Before any encrypted exchange, the browser and the server have to settle three things. That's all of TLS, one sentence each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agree on a secret&lt;/strong&gt; that no eavesdropper can guess, even after hearing the entire conversation (the key exchange).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove identity&lt;/strong&gt; — the server shows a certificate &lt;em&gt;and&lt;/em&gt; signs, so you know you're talking to the right party (the signature).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk encrypted&lt;/strong&gt; — once the shared secret exists, everything else is encrypted with a fast symmetric algorithm (the bulk encryption).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the wire, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  BROWSER                                 SERVER (Verbose binary)
      │                                          │
      │ ─── ClientHello ───────────────────────► │  "here are my algos + my key"
      │ ◄── ServerHello ─────────────────────── │  "here's mine"
      │            (encrypted from here on)       │
      │ ◄── Certificate + signature ──────────── │  "here's who I am, signed"
      │ ─── Finished ──────────────────────────► │
      │ ◄══ HTML page (AES-GCM encrypted) ══════ │  "Hello from Verbose TLS"
      │                                          │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of the three ideas needs a cryptographic ingredient. And that's where Verbose comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ingredients — and where the boundary runs
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters for Verbose's thesis. &lt;strong&gt;All&lt;/strong&gt; the cryptography is Verbose machine code. The host (in Python) only does plumbing: open the socket, frame the TLS messages, and draw the one random secret (&lt;code&gt;os.urandom&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ┌──────────────────────────────────────────────────────────┐
  │  HOST (Python)  : socket, TLS record framing,              │
  │                   os.urandom (the only secret input)       │
  ├──────────────────────────────────────────────────────────┤
  │  VERBOSE (x86-64 machine code) :                           │
  │     X25519          the key exchange                       │
  │     Ed25519 / P-256 the identity signature                 │
  │     AES-128-GCM     the bulk encryption                    │
  │     SHA-256 + HKDF  the hash and key derivation             │
  └──────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line of the Verbose block was validated &lt;strong&gt;byte-for-byte&lt;/strong&gt; against a reference before being assembled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AES-128-GCM&lt;/strong&gt; against the NIST GCM Test Case 2 vector;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X25519&lt;/strong&gt; against RFC 7748;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ed25519&lt;/strong&gt; against the three RFC 8032 §7.1 vectors;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HKDF&lt;/strong&gt; against RFC 5869;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256&lt;/strong&gt;, as we saw in chapter 1, against &lt;code&gt;sha256sum&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing is &lt;em&gt;taken on faith&lt;/em&gt; because "it worked once." Each brick is checked against the official spec, in isolation, before it's stacked into the next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The only loop in all of TLS: the X25519 ladder
&lt;/h2&gt;

&lt;p&gt;Almost all the cryptography here is &lt;em&gt;unrolled&lt;/em&gt;: each operation finishes in a fixed number of steps the verifier can follow statically. There's exactly &lt;strong&gt;one&lt;/strong&gt; real loop — the X25519 key exchange, which climbs a "ladder" (the &lt;em&gt;Montgomery ladder&lt;/em&gt;) over 255 rungs.&lt;/p&gt;

&lt;p&gt;In Verbose, a loop is written as recursion, and — like in &lt;a href="https://arcker.org/blog/2026-05-26-proving-termination/" rel="noopener noreferrer"&gt;chapter 3&lt;/a&gt; — it has to &lt;strong&gt;prove&lt;/strong&gt; it terminates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rule ladder
  logic:
    ...
    out = if s.i == 0 then &amp;lt;the ladder result&amp;gt;
          else ladder(LadderState { ..., i: s.i - 1, ... })
  proofs:
    termination:
      decreasing : i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The real state carries ~50 fields — the field elements over 10 limbs — elided here.) The key point: &lt;code&gt;decreasing : i&lt;/code&gt; is a &lt;em&gt;compile-time-verified promise&lt;/em&gt;. The rung &lt;code&gt;i&lt;/code&gt; strictly decreases on every call, so the compiler proves the ladder stops. The 255 iterations run at runtime; the guarantee that they finish is established before the binary even exists. No general loop construct was added to do TLS — the proof tools from chapter 3 are enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The catch: a real browser doesn't compromise
&lt;/h2&gt;

&lt;p&gt;The first server signed its identity with &lt;strong&gt;Ed25519&lt;/strong&gt;. Against &lt;code&gt;openssl s_client&lt;/code&gt; — the usual test client — everything passed: &lt;code&gt;Verify return code: 0 (ok)&lt;/code&gt;, signature &lt;code&gt;ed25519&lt;/code&gt;. We could have declared victory there.&lt;/p&gt;

&lt;p&gt;Except a real browser refused. Flat: &lt;code&gt;illegal_parameter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why? In its &lt;code&gt;ClientHello&lt;/code&gt;, the browser announces the signature schemes it accepts (the &lt;code&gt;signature_algorithms&lt;/code&gt; extension). And browsers offer &lt;code&gt;ecdsa_secp256r1_sha256&lt;/code&gt; (P-256), &lt;strong&gt;not&lt;/strong&gt; Ed25519. RFC 8446 requires the server to sign with an offered scheme. Ed25519 wasn't offered → immediate rejection.&lt;/p&gt;

&lt;p&gt;That's &lt;em&gt;exactly&lt;/em&gt; the value of a real browser as a target: it imposes constraints a test client lets slide. So we had to build the whole &lt;strong&gt;ECDSA P-256&lt;/strong&gt; stack — GF(p256) field arithmetic, point add/double, scalar multiplication, the modular inverse, and ECDSA-P256-SHA256 signing (RFC 6979 deterministic nonce, DER encoding, &lt;em&gt;low-s&lt;/em&gt;) — validated against the RFC 6979 §A.2.5 vectors and &lt;code&gt;openssl dgst -verify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With a P-256 certificate, the browser stops saying &lt;code&gt;illegal_parameter&lt;/code&gt;. It completes the handshake. It renders the page: &lt;strong&gt;"Hello from Verbose TLS"&lt;/strong&gt;. The only remaining warning is the expected self-signed-certificate one. Not a crypto failure. The page renders.&lt;/p&gt;




&lt;h2&gt;
  
  
  The detail that's fun: smaller, for free
&lt;/h2&gt;

&lt;p&gt;Two recursive rewrites, the same week, at zero compute cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p256_ninv&lt;/code&gt;: &lt;strong&gt;11.2 MB → 84 KB&lt;/strong&gt; of native code (131× smaller), 8/8 byte-for-byte vs the unrolled version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x25519_finish&lt;/code&gt;: &lt;strong&gt;1.3 MB → 42 KB&lt;/strong&gt; (31× smaller), 266 field multiplications identical to the unrolled version. openssl handshake re-validated after the cure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same algorithm, same exact output, zero CPU overhead. The recursive path introduced for self-hosting (Phase A) turned out to be the right tool for collapsing huge unrolled cryptographic chains into compact machine code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 is not a toy: it's a stack of cryptographic primitives, inside a state machine, inside a wire format, with a public client — your browser — that won't compromise on any of it. Making it work end-to-end is empirical proof that the language and its compiler can &lt;strong&gt;express, verify, and run&lt;/strong&gt; a real protocol, not a demo.&lt;/p&gt;

&lt;p&gt;And the method is the message. The cryptography here isn't credible because a tool produced it — it's credible because it's confronted, brick by brick, with the RFC vectors, then with OpenSSL, then with a browser that forgives nothing. You don't &lt;em&gt;trust&lt;/em&gt;, you &lt;em&gt;verify&lt;/em&gt;. That's the whole difference, and that's all of Verbose: a binary small enough to read, proofs declared in source, and output checked against reality at every step.&lt;/p&gt;

&lt;p&gt;The arc began with a 12 KB hash in chapter 1. It ends with a browser rendering a page encrypted by a binary you can read line by line. In between, each brick still needs telling in detail — AES, Ed25519, the Montgomery ladder. Those are the next chapters.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arcker.org/blog/2026-06-04-tls-sans-bibliotheque/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;, where the full series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>cryptography</category>
      <category>programming</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
