<?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>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>
