The CloudletNotes on compilers, systems programming, and functional programmingZola2026-03-12T00:00:00+00:00https://thecloudlet.github.io/atom.xmlEmacs Internal #03: Tagged Union, Tagged Pointer, and Poor Man's Inheritance2026-03-12T00:00:00+00:002026-03-12T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/project/emacs-03/<h2 id="recap">Recap</h2>
<p>From the <a href="https://thecloudlet.github.io/blog/project/emacs-01/">previous article</a>, we examined how GNU Emacs represents every Lisp value — integers, symbols, cons cells, strings, buffers — inside a single 64-bit slot called <code>Lisp_Object</code>. Because all heap objects are 8-byte aligned and the lowest 3 bits of any valid pointer are always zero, Emacs reclaims these "free" bits and uses them as a type tag.</p>
<p>The more fundamental question is: <strong>when a single variable must hold values of different types at runtime, how do we preserve enough information to use that data correctly?</strong></p>
<h2 id="01-back-to-the-basics-how-to-write-a-polymorphic-type">01. Back to the basics: how to write a polymorphic type</h2>
<p><strong>The static typing</strong></p>
<blockquote>
<p>Given memory and a struct type, how do we access the data?</p>
</blockquote>
<p>Data in memory is represented as bits. To operate on it, we define its shape. If we modify the value of <code>score</code>, we load the data from <code>base + 4 bytes</code>, write a <code>32-bit value</code>, and store it back.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-meta z-preprocessor z-include z-c"><span class="z-keyword z-control z-import z-include z-c">#include</span> <span class="z-string z-quoted z-other z-lt-gt z-include z-c"><span class="z-punctuation z-definition z-string z-begin z-c"><</span>stdint.h<span class="z-punctuation z-definition z-string z-end z-c">></span></span>
</span></span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">Person</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-support z-type z-stdint z-c">uint8_t</span> age<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> offset 0, size 1
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> [3 bytes padding] offset 1 (align next field to 4)
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-support z-type z-stdint z-c">uint32_t</span> score<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> offset 4, size 4
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-support z-type z-stdint z-c">uint64_t</span> id<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> offset 8, size 8
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">char</span> name<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span><span class="z-constant z-numeric z-integer z-decimal z-c">12</span><span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> offset 16, size 12
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> sizeof(Person) == 28, no trailing padding needed
</span></span></code></pre>
<p>The compiler remembers the shape of the data so the runtime does not have to.</p>
<p><strong>Dynamic typing</strong></p>
<blockquote>
<p>Given a memory address and a set of possible types, how do we access the data?</p>
</blockquote>
<p>One approach is to add a field at the beginning of a struct to indicate the active type.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">TaggedValue</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">int</span> type_id<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> which type is active?
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-union z-c"><span class="z-storage z-type z-c">union</span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c">
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> TypeA a<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: sizeof(TypeA)
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> TypeB b<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: sizeof(TypeB)
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> TypeC c<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: sizeof(TypeC)
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> union size = max(sizeof(TypeA), sizeof(TypeB), sizeof(TypeC))
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> </span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span> payload<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>This allows type checking before casting:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> predicate: is this a TypeA?
</span></span><span class="z-source z-c"><span class="z-storage z-type z-c">bool</span> <span class="z-meta z-function z-c"><span class="z-entity z-name z-function z-c">is_type_a</span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-storage z-type z-c">struct</span> TaggedValue<span class="z-keyword z-operator z-c">*</span> <span class="z-variable z-parameter z-c">v</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-meta z-function z-c"> </span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-flow z-return z-c">return</span> v<span class="z-punctuation z-accessor z-c">-></span>type_id <span class="z-keyword z-operator z-comparison z-c">==</span> TYPE_A<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> just an integer comparison
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> check cast: give me TypeA* or NULL
</span></span><span class="z-source z-c">TypeA<span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function z-c"><span class="z-entity z-name z-function z-c">as_type_a</span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-storage z-type z-c">struct</span> TaggedValue<span class="z-keyword z-operator z-c">*</span> <span class="z-variable z-parameter z-c">v</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-meta z-function z-c"> </span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-c">if</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>v<span class="z-punctuation z-accessor z-c">-></span>type_id <span class="z-keyword z-operator z-comparison z-c">==</span> TYPE_A<span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span>
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-flow z-return z-c">return</span> <span class="z-keyword z-operator z-c">&</span>v<span class="z-punctuation z-accessor z-c">-></span>payload<span class="z-punctuation z-accessor z-c">.</span><span class="z-variable z-other z-member z-c">a</span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> safe: we verified the tag first
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-punctuation z-section z-block z-end z-c">}</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-flow z-return z-c">return</span> <span class="z-constant z-language z-c">NULL</span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> wrong type
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span>
</span></code></pre>
<p>Functions can be dispatched according to this tag.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Each function pointer represents "what to do when you encounter this type".
</span></span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> A Visitor bundles all these handlers together into one struct.
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">Visitor</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-keyword z-operator z-c">*</span>visit_a<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>TypeA<span class="z-keyword z-operator z-c">*</span> a<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> called when type_id == TYPE_A
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-keyword z-operator z-c">*</span>visit_b<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>TypeB<span class="z-keyword z-operator z-c">*</span> b<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> called when type_id == TYPE_B
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-keyword z-operator z-c">*</span>visit_c<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>TypeC<span class="z-keyword z-operator z-c">*</span> c<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> called when type_id == TYPE_C
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> The dispatch function is the bridge between data and behavior.
</span></span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> It reads the type_id, then hands control to the correct handler.
</span></span><span class="z-source z-c"><span class="z-storage z-type z-c">void</span> <span class="z-meta z-function z-c"><span class="z-entity z-name z-function z-c">visit</span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-storage z-type z-c">struct</span> TaggedValue<span class="z-keyword z-operator z-c">*</span> <span class="z-variable z-parameter z-c">v</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-storage z-type z-c">struct</span> Visitor<span class="z-keyword z-operator z-c">*</span> <span class="z-variable z-parameter z-c">visitor</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-meta z-function z-c"> </span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-c">switch</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>v<span class="z-punctuation z-accessor z-c">-></span>type_id<span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span>
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-c">case</span> TYPE_A<span class="z-punctuation z-separator z-c">:</span> visitor<span class="z-punctuation z-accessor z-c">-></span><span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">visit_a</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-keyword z-operator z-c">&</span>v<span class="z-punctuation z-accessor z-c">-></span>payload<span class="z-punctuation z-accessor z-c">.</span><span class="z-variable z-other z-member z-c">a</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-keyword z-control z-flow z-break z-c">break</span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-c">case</span> TYPE_B<span class="z-punctuation z-separator z-c">:</span> visitor<span class="z-punctuation z-accessor z-c">-></span><span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">visit_b</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-keyword z-operator z-c">&</span>v<span class="z-punctuation z-accessor z-c">-></span>payload<span class="z-punctuation z-accessor z-c">.</span><span class="z-variable z-other z-member z-c">b</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-keyword z-control z-flow z-break z-c">break</span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-keyword z-control z-c">case</span> TYPE_C<span class="z-punctuation z-separator z-c">:</span> visitor<span class="z-punctuation z-accessor z-c">-></span><span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">visit_c</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-keyword z-operator z-c">&</span>v<span class="z-punctuation z-accessor z-c">-></span>payload<span class="z-punctuation z-accessor z-c">.</span><span class="z-variable z-other z-member z-c">c</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-keyword z-control z-flow z-break z-c">break</span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> if type_id is unknown, we silently do nothing —
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> in production code you would want an assertion here
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-meta z-block z-c"> <span class="z-punctuation z-section z-block z-end z-c">}</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-function z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span>
</span></code></pre>
<p>Now you have invented C++ <code>std::variant</code> and <code>std::visit</code> that were introduced in C++17, which utilize a "tagged union."</p>
<h2 id="02-tagged-union-unboxed-std-variant-and-std-visit">02. Tagged Union (Unboxed): <code>std::variant</code> and <code>std::visit</code></h2>
<p>People claim <code>std::variant</code> and <code>std::visit</code> provide a more "<strong>TYPE SAFE</strong>" way, but in fact they just provide some checks. It simply ensures that an invalid cast like <code>(TypeA*) type_b_object</code> is caught either at compile time (if the type is not in the variant at all) or at runtime (if the active type does not match), rather than silently producing undefined behavior as the hand-written version would.</p>
<p>There are two things that need attention here:</p>
<p>First, the problem with a tagged union is that the size of the <code>struct</code> is the size of the largest union element. So for the example below, even if most objects are <code>bool</code> or <code>int</code>, the overall size will be 64 bytes, which is very memory inefficient. Therefore, tagged union technique is mostly applied where different sizes of different types are similar, or temporary objects on stack that can be freed after this call.</p>
<p>PS. <code>std::variant</code> is originally designed to handle a closed set of known types in a type-safe manner — similar in spirit to Haskell's <code>Either</code>, where a value is one of several explicitly listed possibilities. The closest C++ analogue to Haskell's <code>Maybe</code> is actually <code>std::optional</code>.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">TaggedValue</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">int</span> type_id<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-union z-c"><span class="z-storage z-type z-c">union</span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c">
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">int</span> a<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: 4 bytes
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">bool</span> b<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: 1 byte
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">char</span> c<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span><span class="z-constant z-numeric z-integer z-decimal z-c">64</span><span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> size: 64 bytes
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> union size = 64
</span></span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> </span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span> payload<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>Second, this way of handling data and types is called a "<strong>tagged union</strong>", or "<strong>unboxed</strong>".</p>
<p>The naming of boxed and unboxed is a programming language (PL) term that looks very weird to C/C++ programmers. The "unboxed" way looks actually wrapped in a <code>struct</code> box. But the actual meaning in PL theory refers to <strong>memory indirection</strong>.</p>
<ul>
<li><strong>Boxed</strong>: The data lives on the heap (inside a "box"). You only hold a reference or pointer to it.</li>
<li><strong>Unboxed</strong>: The raw bits of the data are laid out flat right where they are declared (on the stack, or inline within an array). There are no pointers to follow. <code>std::variant</code> is unboxed because all the bytes required for the largest possible variant are allocated inline right there.</li>
</ul>
<p><img src="/images/boxed-vs-unboxed.png" alt="boxed-vs-unboxed" /></p>
<p>References on memory representation:</p>
<ul>
<li><a rel="noopener" target="_blank" href="https://simonmar.github.io/bib/papers/ptr-tagging.pdf">Simon Marlow - Faster Laziness Using Dynamic Pointer Tagging</a></li>
<li><a rel="noopener" target="_blank" href="https://www.janestreet.com/tech-talks/unboxed-types-for-ocaml/">Jane Street - OCaml unboxed types talk</a></li>
<li><a rel="noopener" target="_blank" href="https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/rts/storage/heap-objects">GHC Commentary</a></li>
</ul>
<h2 id="03-tagged-pointer-boxed">03. Tagged Pointer (Boxed)</h2>
<p>A tagged union allocates the maximum required size for every variant. If an Abstract Syntax Tree (AST) contains an integer node requiring 8 bytes and a function definition node requiring 256 bytes, an unboxed tagged union allocates 256 bytes for every integer node. This affects the overall memory footprint and cache usage.</p>
<p>An alternative approach keeps data on the heap (boxed) and uses the lowest 3 bits of the 8-byte aligned pointer as the type tag. This provides $2^3 = 8$ possible types without allocating additional inline bytes. This is called a <strong>tagged pointer</strong>.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">tagged_pointer</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span><span class="z-keyword z-operator z-c">*</span> pointer_and_tag<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span>
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> sizeof(tagged_pointer) == 8 bytes
</span></span></code></pre>
<blockquote>
<p>What if there are more than 8 types?</p>
</blockquote>
<p>One common solution is using <strong>fat pointer</strong>. Modern languages like Go (interfaces) and Rust (trait objects) use this approach extensively.</p>
<p>Stealing 3 bit is not enough, so, just add a 64-bit space to store the tag information.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">fat_pointer</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">int</span> tag<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Could be a type ID, a vtable pointer, or a size/length
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span><span class="z-keyword z-operator z-c">*</span> payload<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Pointer to the actual data on the heap
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span>
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> sizeof(fat_pointer) == 16 bytes (doubled)
</span></span></code></pre>
<blockquote>
<p>"Since both Go and Rust think fat pointers are great, why doesn't GNU Emacs just use fat pointers for <code>Lisp_Object</code>?"</p>
</blockquote>
<p>Comparing fat pointers to tagged pointers: A fat pointer is smaller than a tagged union, but it doubles the memory size compared to a 64-bit tagged pointer. This changes the memory footprint and the Garbage Collector (GC) scanning workload. Emacs was designed to keep the <code>Lisp_Object</code> within a single 64-bit word.</p>
<p>PS. It's a crime to waste memory in the 1980s — a typical workstation had around 256 KB of RAM, which is less than the size of a single modern emoji in a Unicode font file. Doubling every Lisp_Object from 8 to 16 bytes wasn't an engineering tradeoff. It was a confession.</p>
<h2 id="04-emacs-tagged-pointer-poor-man-s-inheritance">04. Emacs: Tagged Pointer + Poor Man's Inheritance</h2>
<p>Since GNU Emacs uses the lowest 3 bits for tags, it is strictly limited to 8 fundamental types. If you look at <code>enum Lisp_Type</code> in <code>src/lisp.h</code>, you'll see exactly that:</p>
<ol>
<li><code>Lisp_Symbol</code></li>
<li><code>Lisp_Int0</code></li>
<li><code>Lisp_Int1</code></li>
<li><code>Lisp_String</code></li>
<li><code>Lisp_Vectorlike</code></li>
<li><code>Lisp_Cons</code></li>
<li><code>Lisp_Float</code>
<em>(plus one unused type)</em></li>
</ol>
<pre data-lang="text" class="language-text z-code"><code class="language-text" data-lang="text"><span class="z-text z-plain">McCarthy's Lisp (1960) abstract math
</span><span class="z-text z-plain"> atom eq car cdr
</span><span class="z-text z-plain"> cons quote cond
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> │ Emacs engineers bridge:
</span><span class="z-text z-plain"> │ "statically typed C must represent
</span><span class="z-text z-plain"> │ dynamically typed Lisp"
</span><span class="z-text z-plain"> ▼
</span><span class="z-text z-plain"> Lisp_Object (src/lisp.h) C layer
</span><span class="z-text z-plain"> ┌──────────────────────┬────┐
</span><span class="z-text z-plain"> │ pointer or value │tag │ ← one machine word (64 bits)
</span><span class="z-text z-plain"> │ 61 bits │ 3b │
</span><span class="z-text z-plain"> └──────────────────────┴────┘
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> ├─ tag = Cons → struct Lisp_Cons
</span><span class="z-text z-plain"> ├─ tag = String → struct Lisp_String
</span><span class="z-text z-plain"> ├─ tag = Float → struct Lisp_Float
</span><span class="z-text z-plain"> ├─ tag = Int0/1 → EMACS_INT (immediate value, no pointer!)
</span><span class="z-text z-plain"> ├─ tag = Symbol → struct Lisp_Symbol
</span><span class="z-text z-plain"> └─ tag = Vectorlike
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> ▼
</span><span class="z-text z-plain"> union vectorlike_header (The Escape Hatch)
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> ├─ PVEC_BUFFER → struct buffer
</span><span class="z-text z-plain"> ├─ PVEC_WINDOW → struct window
</span><span class="z-text z-plain"> ├─ PVEC_HASH_TABLE → struct Lisp_Hash_Table
</span><span class="z-text z-plain"> ├─ PVEC_SYMBOL_WITH_POS → struct Lisp_Symbol_With_Pos
</span><span class="z-text z-plain"> └─ ... (over 30+ complex types!)
</span></code></pre>
<p>A basic Lisp interpreter might only need a few primitive types. But Emacs is a text editor designed for performance (?!), so it implements its core objects (like buffers and windows) directly in C. So, 8 types are not enough.</p>
<blockquote>
<p>How can we represent more types when the pointer tag is limited to 3 bits?</p>
</blockquote>
<p>To expand the type space, Emacs uses a C pattern sometimes called "<strong>Poor Man's Inheritance</strong>" or struct embedding. By placing a common header as the very first field of a struct, a pointer can be cast to the header type, checked for its sub-type, and then cast to the specific object type.</p>
<p>When <code>Lisp_Object</code> carries the <code>Lisp_Vectorlike</code> tag, it points to a struct starting with <code>union vectorlike_header</code>. Emacs reads this header to find a sub-tag indicating the specific type (<code>pvec_type</code>).</p>
<p>This is how <code>vectorlike_header</code> is embedded:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: lisp.h
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">Lisp_Vector</span></span></span><span class="z-meta z-struct z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> The generic "Base Data Structure"
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">union</span> vectorlike_header header<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> Lisp_Object contents<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span>FLEXIBLE_ARRAY_MEMBER<span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span> GCALIGNED_STRUCT<span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Derived type Example
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">Lisp_Symbol_With_Pos</span></span></span><span class="z-meta z-struct z-c">
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">union</span> vectorlike_header header<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> <--- The "Base Class" must be the first field!
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> Lisp_Object sym<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> A symbol <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> Lisp_Object pos<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> A fixnum <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span> GCALIGNED_STRUCT<span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>When Emacs identifies a <code>Lisp_Object</code>, it performs two checks:</p>
<ol>
<li><strong>Primary Tag Check</strong>: Check if the lowest 3 bits are <code>Lisp_Vectorlike</code> (<code>0b101</code>).</li>
<li><strong>Sub-Tag Check</strong>: Cast the pointer to <code>union vectorlike_header*</code>, read the <code>TYPE</code> field. If it equals <code>PVEC_SYMBOL_WITH_POS</code>, cast the pointer to <code>struct Lisp_Symbol_With_Pos*</code>.</li>
</ol>
<p>The definition of <code>union vectorlike_header</code> packs the subtype into its <code>size</code> field:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: src/lisp.h
</span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-storage z-type z-c">union</span> <span class="z-meta z-union z-c"><span class="z-entity z-name z-union z-c">vectorlike_header</span></span></span><span class="z-meta z-union z-c">
</span></span><span class="z-source z-c"><span class="z-meta z-union z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> The `size' header word, W bits wide, has one of two forms
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> discriminated by the second-highest bit (PSEUDOVECTOR_FLAG):
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> 1 1 W-2
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> +---+---+-------------------------------------+
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> | M | 0 | SIZE | vector
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> +---+---+-------------------------------------+
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> 1 1 W-32 6 12 12
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> +---+---+--------+------+----------+----------+
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> | M | 1 | unused | TYPE | RESTSIZE | LISPSIZE | pseudovector
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> +---+---+--------+------+----------+----------+
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> M (ARRAY_MARK_FLAG) holds the GC mark bit.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> SIZE is the length (number of slots) of a regular Lisp vector,
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> and the object layout is struct Lisp_Vector.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> TYPE is the pseudovector subtype (enum pvec_type).
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> LISPSIZE is the number of Lisp_Object fields at the beginning of the
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> object (after the header). These are always traced by the GC.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c">
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> RESTSIZE is the number of fields (in word_size units) following.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> These are not automatically traced by the GC.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> For PVEC_BOOL and statically allocated PVEC_SUBR, RESTSIZE is 0.
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> (The block size for PVEC_BOOL is computed from its own size
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> field, to avoid being restricted by the 12-bit RESTSIZE field.)
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-support z-type z-sys-types z-c">ptrdiff_t</span> size<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> </span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>And the following contains all the concrete <code>pvec_type</code> sub-types it can represent:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: src/lisp.h
</span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-storage z-type z-c">enum</span> <span class="z-meta z-enum z-c"><span class="z-entity z-name z-enum z-c">pvec_type</span></span></span><span class="z-meta z-enum z-c">
</span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_NORMAL_VECTOR<span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> Should be first, for sxhash_obj. <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_FREE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_BIGNUM<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_MARKER<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_OVERLAY<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_FINALIZER<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_SYMBOL_WITH_POS<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_MISC_PTR<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_USER_PTR<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_PROCESS<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_FRAME<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_WINDOW<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_BOOL_VECTOR<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_BUFFER<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_HASH_TABLE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_OBARRAY<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_TERMINAL<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_WINDOW_CONFIGURATION<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_SUBR<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_OTHER<span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> Should never be visible to Elisp code. <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_XWIDGET<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_XWIDGET_VIEW<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_THREAD<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_MUTEX<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_CONDVAR<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_MODULE_FUNCTION<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_NATIVE_COMP_UNIT<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_TS_PARSER<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_TS_NODE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_TS_COMPILED_QUERY<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_SQLITE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> These should be last, for internal_equal and sxhash_obj. <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_CLOSURE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_CHAR_TABLE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_SUB_CHAR_TABLE<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_RECORD<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_FONT<span class="z-punctuation z-separator z-c">,</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> PVEC_TAG_MAX <span class="z-keyword z-operator z-assignment z-c">=</span> PVEC_FONT <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> Keep this equal to the highest member. <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>This elegant combination of <strong>Tagged Pointers</strong> (for high-speed, core types and immediate integers without memory allocations) and <strong>Poor Man's Inheritance</strong> (for an extensible array of complex types) is how Emacs achieves dynamic typing in statically-typed C without sacrificing critical GC performance.</p>
<p><strong>Note on C/C++ Undefined Behavior:</strong></p>
<p>While Emacs relies heavily on GCC-specific behaviors to get away with manipulating pointer bits directly, doing this in standard modern C/C++ on raw pointers is a fast track to <strong>Undefined Behavior (UB)</strong>. It breaks compiler optimizations relying on <strong>Pointer Provenance</strong>.</p>
<p>To safely implement tagged pointers in C/C++, one must cast the pointer to <code>uintptr_t</code> (or <code>intptr_t</code>) before bitwise operations. The C++ committee is actually aware of this architectural need; there is an active proposal <a rel="noopener" target="_blank" href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3125r4.html">P3125R0</a> by Hana Dusíková aiming to add a standard library utility for pointer tagging that explicitly preserves provenance. (Thanks to HN users <em>tialaramex</em> and <em>trws</em> for pointing this out).</p>
<p>Unlike C++, <strong>Rust</strong> has recently stabilized its <strong>Strict Provenance API</strong> to tackle this exact problem. Instead of risking UB, Rust provides methods like <code>ptr::map_addr</code>, which allows developers to safely map a pointer to an integer, manipulate the tag bits, and map it back without confusing LLVM's aliasing model. It offers a standardized way to hide flags in pointers while playing nicely with the compiler's strict rules. (Thanks to HN user <em>shadowgovt</em>, <em>tialaramex</em>, <em>VorpalWay</em>)</p>
<h2 id="05-the-modern-reincarnation-llvm-s-custom-rtti">05. The Modern Reincarnation: LLVM's Custom RTTI</h2>
<p>The most fascinating part about reading Emacs's 1980s source code is discovering that these techniques are still highly applicable and relevant even in the modern C++ era. The combination of <strong>Tagged Pointer</strong> + <strong>Poor Man's Inheritance</strong> is no exception.</p>
<p>By looking at the source code of <strong>LLVM</strong>, engineers explicitly disable the C++ standard <strong>RTTI</strong> (<code>-fno-rtti</code>) and <code>dynamic_cast</code>. Instead, LLVM literally reinvents Emacs's "Poor Man's Inheritance" and Tagging system, but wraps it in modern C++ templates. It's called Custom RTTI.</p>
<blockquote>
<p>Why LLVM abandons standard RTTI?</p>
</blockquote>
<p>Standard C++ RTTI works by embedding a pointer to a <code>type_info</code> object inside every polymorphic class's vtable. A <code>dynamic_cast</code> then traverses a chain of these <code>type_info</code> objects at runtime, comparing strings or pointers until it finds a match or exhausts the hierarchy. For a compiler that performs millions of type checks per second while traversing an AST, this traversal cost is unacceptable.</p>
<p>Instead, LLVM defines a single integer field called <code>SubclassID</code> in its base class to identify concrete types:</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: llvm/include/llvm/IR/Value.h
</span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-storage z-type z-c++">class</span> </span><span class="z-meta z-class z-c++"><span class="z-entity z-name z-class z-c++">Value</span></span><span class="z-meta z-class z-c++"> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-storage z-modifier z-c++">private</span><span class="z-punctuation z-section z-class z-c++">:</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> A single integer tag — the same idea as Emacs's pvec_type enum.
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Private so subclasses cannot corrupt it accidentally.
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-modifier z-c++">const</span> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">char</span> SubclassID<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-storage z-modifier z-c++">public</span><span class="z-punctuation z-section z-class z-c++">:</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-meta z-method z-c++"><span class="z-entity z-name z-function z-c++">getValueID</span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-method z-c++"> <span class="z-storage z-modifier z-c++">const</span> </span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> SubclassID<span class="z-punctuation z-terminator z-c++">;</span> </span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> The base class accepts all Values — this is the identity check.
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-modifier z-c++">static</span> <span class="z-storage z-type z-c">bool</span> <span class="z-meta z-method z-c++"><span class="z-entity z-name z-function z-c++">classof</span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Value <span class="z-keyword z-operator z-c">*</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-method z-c++"> </span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-constant z-language z-c">true</span><span class="z-punctuation z-terminator z-c++">;</span> </span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> A derived class registers its own ID range and implements classof().
</span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-storage z-type z-c++">class</span> </span><span class="z-meta z-class z-c++"><span class="z-entity z-name z-class z-c++">Argument</span></span><span class="z-meta z-class z-c++"> <span class="z-punctuation z-separator z-c++">:</span> <span class="z-storage z-modifier z-c++">public</span> <span class="z-entity z-other z-inherited-class z-c++">Value</span> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-storage z-modifier z-c++">public</span><span class="z-punctuation z-section z-class z-c++">:</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> isa<Argument>(ptr) compiles down to this — a single integer comparison.
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> No vtable traversal. No string comparison. Just: is the tag == ArgumentVal?
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-modifier z-c++">static</span> <span class="z-storage z-type z-c">bool</span> <span class="z-meta z-method z-c++"><span class="z-entity z-name z-function z-c++">classof</span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Value <span class="z-keyword z-operator z-c">*</span><span class="z-variable z-parameter z-c++">V</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-method z-c++"> </span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> V<span class="z-punctuation z-accessor z-arrow z-c++">-></span><span class="z-meta z-method-call z-c++"><span class="z-variable z-function z-member z-c++">getValueID</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method-call z-c++"></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-comparison z-c">==</span> ArgumentVal<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"> </span></span><span class="z-meta z-method z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>Invoking <code>isa<Argument>(Val)</code> evaluates at compile time to <code>Argument::classof(Val)</code>, resulting in an intjeger comparison on <code>SubclassID</code>. -- <a rel="noopener" target="_blank" href="https://llvm.org/docs/ProgrammersManual.html#the-isa-cast-and-dyn-cast-templates">llvm docs</a></p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> How LLVM developers write type dispatch
</span></span><span class="z-source z-c++"><span class="z-keyword z-control z-c++">if</span> <span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">isa</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span>Argument<span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">Val</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span>
</span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"> Argument <span class="z-keyword z-operator z-c">*</span>Arg <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">cast</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span>Argument<span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">Val</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Do something with Arg...
</span></span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span>
</span></code></pre>
<p>Both Emacs and LLVM handle dynamic dispatch over a hierarchy of types by embedding tag information directly in the base structures and performing integer comparisons before casting.</p>
<blockquote>
<p><strong>Community Updates & Further Reading</strong></p>
<ul>
<li>
<p><strong>On CRTP & Static Polymorphism:</strong> Huge thanks to <a rel="noopener" target="_blank" href="https://nickdesaulniers.github.io/">Nick Desaulniers</a> for highlighting LLVM's elegant use of CRTP. For deeper dives into devirtualization, I highly recommend the <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern">Wikipedia CRTP page</a> and David Alvarez Rosa's excellent post on <a rel="noopener" target="_blank" href="https://david.alvarezrosa.com/posts/devirtualization-and-static-polymorphism/">Devirtualization and Static Polymorphism</a>.</p>
</li>
<li>
<p><strong>On LLVM Internals:</strong> While the <code>classof</code> pattern has been the backbone of LLVM's Custom RTTI for years, LLVM is continuously evolving its architecture. Recently, it introduced the <a rel="noopener" target="_blank" href="https://llvm.org/doxygen/structllvm_1_1CastInfo.html"><code>CastInfo</code> trait</a>, which decouples the casting mechanism from class definitions and relies more heavily on template specialization. <em>(Thanks to HN user mshockwave for this architectural update).</em></p>
</li>
</ul>
</blockquote>
<h2 id="06-other-tagged-pointer-usages">06. Other Tagged Pointer Usages</h2>
<p>The pattern of storing information in unused bits of pointers or headers is found in other system implementations:</p>
<ul>
<li><strong>Linux Kernel Red-Black Trees</strong>: Uses the lowest bits of parent pointers to store the node color.</li>
<li><strong>LuaJIT and V8 (NaN Boxing)</strong>: Uses the payload space of IEEE 754 "Not-a-Number" <code>double</code>s to encode pointers.</li>
<li><strong>PostgreSQL</strong>: Encodes transaction visibility metadata in the bit-fields of tuple headers.</li>
<li><strong>LLVM <code>PointerIntPair<></code></strong>: A C++ template utility for packing integers into pointer alignment padding.</li>
<li><strong>ARM64 Top Byte Ignore (TBI)</strong>: Hardware configuration that allows the top 8 bits of a 64-bit pointer to be used for tags (utilized in iOS/macOS).</li>
<li><a rel="noopener" target="_blank" href="https://simonmar.github.io/bib/papers/ptr-tagging.pdf">Faster Laziness Using Dynamic Pointer Tagging (Simon Marlow et al.)</a> (thanks to HN user <em>internet_points</em> for the reference)</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>This article outlines three ways memory is structured to handle dynamic typing:</p>
<ul>
<li><strong>Tagged Union (Unboxed)</strong>: Allocates inline memory based on the largest variant. (<code>std::variant</code>)</li>
<li><strong>Fat Pointer</strong>: Allocates additional bytes alongside the pointer to store type information. (Go interfaces, Rust traits)</li>
<li><strong>Tagged Pointer (Boxed)</strong>: Uses the alignment padding of pointers to store tags, relying on heap allocation for the data. (Emacs <code>Lisp_Object</code>, V8)</li>
</ul>
<p>Different memory layouts serve different requirements. Emacs and LLVM utilize Tagged Pointers and struct embedding to manage dynamic typing within their specific memory constraints.</p>
<h2 id="next-step">Next step</h2>
<p>Looking into the weird <code>Lisp_String</code> object... there is an interval tree in it.</p>
<hr />
<p>Emacs Internal Series:</p>
<ul>
<li>#01: <a href="https://thecloudlet.github.io/blog/project/emacs-01/">Emacs is a Lisp Runtime in C, Not an Editor</a></li>
<li>#02: <a href="https://thecloudlet.github.io/blog/project/emacs-02/">Data First — Deconstructing Lisp_Object in C</a></li>
<li>#03: Tagged Union, Tagged Pointer, and Poor Man's Inheritance</li>
</ul>
Emacs Internal #02: Data First — Deconstructing Lisp_Object in C2026-03-05T00:00:00+00:002026-03-05T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/project/emacs-02/<p>In the first part of this GNU Emacs series, I focused on the history and explains why there is a Lisp interpreter embedded inside a text editor. Before diving into this part, I recommend reading the previous post:</p>
<p><a href="https://thecloudlet.github.io/blog/project/emacs-01/">Emacs Internal #01: Emacs is a Lisp Runtime in C, Not an Editor</a></p>
<p>In this post, I want to look at GNU Emacs from a higher system-design perspective.</p>
<h2 id="the-mathematical-foundation-mccarthy-s-lisp">The Mathematical Foundation: McCarthy's Lisp</h2>
<p>Before diving into the source code, I left a short reference on Lisp here. Feel free to skip it if you are familiar with its background.</p>
<ul>
<li><a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Lisp_(programming_language)">Wiki - Lisp</a> (LISt Processing)</li>
<li><a rel="noopener" target="_blank" href="https://languagelog.ldc.upenn.edu/myl/llog/jmc.pdf">The Roots of Lisp</a> - Paul Graham</li>
<li><a rel="noopener" target="_blank" href="https://twobithistory.org/2018/10/14/lisp.html">How Lisp Became God's Own Programming Language</a> - Two-Bit history</li>
</ul>
<h2 id="first-principle-data-and-operations">First Principle: Data and Operations</h2>
<p>This is how I personally approach reading source code: I start from how general computation works.</p>
<blockquote>
<p>Given some <strong>data</strong>, and some <strong>operation</strong>, then we get a new piece of <strong>data</strong></p>
</blockquote>
<p>Starting with the very basic, <code>3 + 4 = 7</code>. The data is <code>3</code> and <code>4</code>. The operation is <code>+</code>.</p>
<p>If we pile up the abstractions of basic math operations with data abstractions:</p>
<ul>
<li><strong>Complex numbers</strong>:
$$
(a + bi)(c + di) = (ac - bd) + (ad + bc)i
$$</li>
<li><strong>Matrix multiplication</strong>:
$$
C_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj}
$$</li>
<li><strong>Convolution</strong>:
$$
(f * g)(t) = \int_{-\infty}^{\infty} f(\tau)g(t - \tau), d\tau
$$</li>
<li><strong>A step function</strong>:
$$
H(x) = \begin{cases} 1 & \text{if } x \ge 0 \ 0 & \text{if } x < 0 \end{cases}
$$</li>
</ul>
<p><strong>From mathematical computation to a Von Neumann machine</strong>, the computation can be lowered through IRs and eventually to assembly code.</p>
<pre data-lang="asm" class="language-asm z-code"><code class="language-asm" data-lang="asm"><span class="z-source z-assembly"><span class="z-entity z-name z-function z-assembly">o</span><span class="z-entity z-name z-function z-assembly">p</span><span class="z-entity z-name z-function z-assembly"> </span><span class="z-entity z-name z-function z-assembly">r</span><span class="z-entity z-name z-function z-assembly">d</span><span class="z-source z-assembly">,</span><span class="z-entity z-name z-function z-assembly"> </span><span class="z-entity z-name z-function z-assembly">r</span><span class="z-entity z-name z-function z-assembly">1</span><span class="z-source z-assembly">,</span><span class="z-entity z-name z-function z-assembly"> </span><span class="z-entity z-name z-function z-assembly">r</span><span class="z-entity z-name z-function z-assembly">2</span>
</span></code></pre>
<p>When I think about compilers here, I usually picture SSA form at this stage.</p>
<p>At this level, the model is brutally clean:</p>
<ul>
<li><strong>Data</strong> is a sequence of bits in the memory hierarchy, waiting to be fetched into a register.</li>
<li><strong>Operations</strong> are high-level semantics that the compiler lowers — pass by pass, IR by IR — until they become the native instruction set the silicon actually understands.</li>
</ul>
<p>This leads me to three things:</p>
<p>First, modern compilers work from this first principle. LLVM's main challenge is to merge, traverse, and select a sequence of instructions so that we have the least computation time (usually on a single core). MLIR aims to unify lowering across heterogeneous hardware targets, especially when the hardware supports domain-specific operations like convolution, matrix multiplication, and precision conversion. (MLIR is the next planned series to dive in.)</p>
<p>Second, the idea that <strong>code is data, and data is code</strong> keeps showing up for me. Data is just bits; instructions are also just bit patterns stored in memory. The instruction stream lives in the same memory hierarchy as the data it operates on, and the CPU treats some bits as "code" only because the program counter (PC) points to them.</p>
<blockquote>
<p>(Thanks for error correct by r/emacs community)</p>
<p>Lisp machines of the day were not a different architectural alternative to "von Neumann machine". They had RAM, disk, cpu, some peripherals attached to them. Basically a high-end workstations that just happened to have an OS written in Lisp, and bunch of user's program also implemented in Lisp, a lisp compiler, and so on. Differences in computational environment, and how one worked with them existed, but from the hardware point of view, Lisp machines, were also von Neumann architecture.</p>
<p>They do seem to had some stuff implemented in hardware that accelerated certain computations important for Lisp. But architecturally, this isn't different than say Intel shipping AES encryption in hardware and calling it a "crypto machine", or as we see nowadays, extensions on GPUs that benefit LLMs.</p>
<p>— <em>u/authurno1</em> on Reddit</p>
</blockquote>
<p>Third, when I read code, I tend to start from the data: in C/C++ terms, the <code>struct</code> or private members of a class. Data is often more self-descriptive than operations. Once I understand the data model, the operations become transformations over that model. This is a personal bias, but it matches how I think about functional programming (FP) and data-oriented programming (DOP). It also explains why OOP doesn’t click with me as easily: it starts from behavior and encapsulation, while I prefer to anchor my understanding in data first. From this lens I could talk about side effects, mutability, and other concepts, but that would take us too far.</p>
<p>Starting with the data...</p>
<h2 id="lisp-object-the-universal-c-type">Lisp_Object: The Universal C Type</h2>
<h3 id="tagged-pointer-layout">Tagged Pointer Layout</h3>
<p>Back to the GNU Emacs <a rel="noopener" target="_blank" href="https://github.com/emacs-mirror/emacs">source code</a>, the core data type used to represent Elisp values in C is called <code>Lisp_Object</code>, defined in <code>src/lisp.h</code>.</p>
<p>For simplicity, using a 64-bit system to explain.</p>
<p>Lisp_Object is a 64-bit machine word. For pointers, because heap allocations are 8-byte aligned, their lowest 3 bits are guaranteed to be <code>000</code>. Emacs simply embeds the 3-bit type tag directly into these "free" zero bits. For immediate integers (fixnums), the upper 62 bits hold the actual value.</p>
<pre class="z-code"><code><span class="z-text z-plain">64-bit Lisp_Object:
</span><span class="z-text z-plain">┌────────────────────────────────────────────────┬─────┐
</span><span class="z-text z-plain">│ pointer or value (61 bits) │ tag │
</span><span class="z-text z-plain">│ │ 3b │
</span><span class="z-text z-plain">└────────────────────────────────────────────────┴─────┘
</span></code></pre>
<p>Why the lowest 3 bits?</p>
<p>Because all heap-allocated objects are 8-byte aligned (due to <code>GCALIGNMENT</code>), their addresses always end in 000 in binary. These 3 bits are "free" — we can borrow them to store type information without losing any address precision.</p>
<p>The tag is a enum, named <code>Lisp_Type</code>. And the simplified source code is as below:</p>
<pre data-lang="C" class="language-C z-code"><code class="language-C" data-lang="C"><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-storage z-type z-c">enum</span> <span class="z-meta z-enum z-c"><span class="z-entity z-name z-enum z-c">Lisp_Type</span></span></span><span class="z-meta z-enum z-c">
</span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Symbol <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">0</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b000
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Type_Unused0 <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">1</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b001
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Int0 <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">2</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b010
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Int1 <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">6</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b110 <-- !
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_String <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">4</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b100
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Vectorlike <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">5</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b101
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Cons <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">3</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b010 <-- !
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> Lisp_Float <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-constant z-numeric z-integer z-decimal z-c">7</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> 0b111
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"> </span></span><span class="z-meta z-enum z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p>PS. This tagged pointer technique is actually a universal pattern across systems programming. It solves two problems: First, in dynamically typed contexts, the execution engine must know a value's type before operating on it. Second, placing this metadata in an extra struct field wastes memory and causes cache-misses from pointer chasing. To survive memory bus bottlenecks, engineers cram metadata directly into the unused bits of pointers. We'll discuss in the next post.</p>
<h3 id="stealing-one-more-bit">Stealing One More Bit</h3>
<p>Looking closely to the <code>Lisp_Int0</code> and <code>Lisp_Int1</code>, something looks weird...</p>
<pre class="z-code"><code><span class="z-text z-plain">Lisp_Int0 = 0b010
</span><span class="z-text z-plain">Lisp_Int1 = 0b110
</span><span class="z-text z-plain"> ^^
</span><span class="z-text z-plain">lowest 2 bits are the same!
</span></code></pre>
<p>This design actually doubled the value that can be represented by a <code>Lisp_Int</code></p>
<pre class="z-code"><code><span class="z-text z-plain">Normal 3-bit tag:
</span><span class="z-text z-plain">┌─────────────────────────────────────────────────────┬─────┐
</span><span class="z-text z-plain">│ value (61 bits) │ tag │
</span><span class="z-text z-plain">│ │ 3b │
</span><span class="z-text z-plain">└─────────────────────────────────────────────────────┴─────┘
</span><span class="z-text z-plain">Range: -2^60 to 2^60-1
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Fixnum with 2-bit tag:
</span><span class="z-text z-plain">┌───────────────────────────────────────────────────────┬───┐
</span><span class="z-text z-plain">│ value (62 bits) │tag│
</span><span class="z-text z-plain">│ │2b │
</span><span class="z-text z-plain">└───────────────────────────────────────────────────────┴───┘
</span><span class="z-text z-plain">Range: -2^61 to 2^61-1 (doubled!)
</span></code></pre>
<p>One important distinction: for a fixnum, the upper bits hold the integer value directly (an <em>immediate</em>). For all other types, those bits are a heap pointer to the underlying C struct.</p>
<h3 id="the-operation-conventions">The Operation Conventions</h3>
<p>The macros (or in debug mode is inline function) that work on <code>Lisp_Object</code> follow a naming convention:</p>
<ul>
<li><strong><code>X</code> prefix</strong> — <em>eXtract</em>: strip the tag bits and get the underlying value or pointer</li>
<li><strong><code>P</code> suffix</strong> — <em>Predicate</em>: check the type, returns bool</li>
<li><strong><code>CHECK_</code> prefix</strong> — <em>Assert</em>: like a predicate, but signals a Lisp error if the type is wrong</li>
</ul>
<p>For example, to check if an object is an integer and then read it:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: src/bignum.h
</span></span><span class="z-source z-c"><span class="z-keyword z-control z-c">if</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">FIXNUMP</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">obj</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> P: check type tag
</span></span><span class="z-source z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span>
</span></span><span class="z-source z-c"><span class="z-meta z-block z-c"> EMACS_INT n <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">XFIXNUM</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">obj</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> X: extract value
</span></span></span><span class="z-source z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span>
</span></code></pre>
<p>internally, <code>XSTRING</code>, <code>XCONS</code>, <code>XFIXNUM</code> and all other X macros work by masking off the tag bits using XUNTAG, then casting to the appropriate C struct pointer.</p>
<p>For <code>XFIXNUM</code> the mask is 2 bits, so</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: src/lisp.h — XFIXNUM_RAW
</span></span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-keyword z-control z-flow z-return z-c">return</span> <span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">XLI</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">a</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span> <span class="z-keyword z-operator z-arithmetic z-c">>></span> INTTYPEBITS<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> INTTYPEBITS = 2 for fixnums
</span></span></code></pre>
<p>PS. By performing a right shift (<code>>></code>) on a signed integer, it forces the compiler to emit an arithmetic shift instruction. The hardware preserves the sign bit.</p>
<p>and for other types using <code>XUNTAG</code>:</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Source: src/lisp.h — XUNTAG (for pointer types: XCONS, XSTRING, XSYMBOL, ...)
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"><span class="z-keyword z-control z-import z-define z-c">#define</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-entity z-name z-function z-preprocessor z-c">XUNTAG</span></span><span class="z-meta z-preprocessor z-macro z-parameters z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-variable z-parameter z-c">a</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-variable z-parameter z-c">type</span><span class="z-punctuation z-separator z-c">,</span> <span class="z-variable z-parameter z-c">ctype</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-punctuation z-separator z-continuation z-c">\</span>
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>ctype <span class="z-keyword z-operator z-c">*</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-support z-type z-stdint z-c">uintptr_t</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">XLP</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">a</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span> <span class="z-keyword z-operator z-arithmetic z-c">-</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span><span class="z-support z-type z-stdint z-c">uintptr_t</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">LISP_WORD_TAG</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">type</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> e.g. XCONS expands to:
</span></span><span class="z-source z-c"><span class="z-keyword z-control z-flow z-return z-c">return</span> <span class="z-meta z-function-call z-c"><span class="z-variable z-function z-c">XUNTAG</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c">a<span class="z-punctuation z-separator z-c">,</span> Lisp_Cons<span class="z-punctuation z-separator z-c">,</span> <span class="z-storage z-type z-c">struct</span> Lisp_Cons</span></span><span class="z-meta z-function-call z-c"><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> ^^^^^^ subtract the tag word from the raw pointer address
</span></span></code></pre>
<p>Why clear the lower 3 bit tag using subtraction (<code>-</code>) instead of a bitwise AND (<code>& ~0x7</code>)?</p>
<p>On architectures like x86, memory addressing supports <code>Base - Offset</code>. A C compiler can fold the tag clearing and the subsequent struct access (like <code>.car</code> or <code>.cdr</code>) into a single instruction, saving a CPU register.</p>
<blockquote>
<p>Historical Update: The Evolution of LSB Tagging</p>
<p>I originally joked that this optimization was because Emacs was built by the "same maniacs who built GCC." While true that early GNU hackers were intimately familiar with compilers, the specific evolution of this 3-bit LSB (Least Significant Bit) tagging scheme has a more precise history.</p>
<p>According to the paper <a rel="noopener" target="_blank" href="https://dl.acm.org/doi/epdf/10.1145/3386324">Evolution of Emacs Lisp</a>, early Emacs used 7-bit MSB (Most Significant Bit) tags. The modern LSB tagging scheme, which elegantly exploits the 8-byte alignment of heap allocations, was actually pioneered and implemented by Stefan Monnier during the development of Emacs 22 in 2007.</p>
<p>(Huge thanks to Reddit user <em>GuDzpoz</em> for the rigorous fact-checking and for pointing me to this paper!)</p>
</blockquote>
<h3 id="the-big-picture">The Big Picture</h3>
<pre class="z-code"><code><span class="z-text z-plain">McCarthy's Lisp (1960) abstract math
</span><span class="z-text z-plain"> atom eq car cdr
</span><span class="z-text z-plain"> cons quote cond
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> │ Emacs engineers bridge:
</span><span class="z-text z-plain"> │ "statically typed C must represent
</span><span class="z-text z-plain"> │ dynamically typed Lisp"
</span><span class="z-text z-plain"> ▼
</span><span class="z-text z-plain"> Lisp_Object (src/lisp.h) C layer
</span><span class="z-text z-plain"> ┌──────────────────────┬────┐
</span><span class="z-text z-plain"> │ pointer or value │tag │ ← one machine word
</span><span class="z-text z-plain"> │ 61 bits │ 3b │
</span><span class="z-text z-plain"> └──────────────────────┴────┘
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> ├─ tag = Cons → CONSP() → XCONS() → struct Lisp_Cons
</span><span class="z-text z-plain"> ├─ tag = String → STRINGP() → XSTRING() → struct Lisp_String
</span><span class="z-text z-plain"> ├─ tag = Int0/1 → FIXNUMP() → XFIXNUM() → EMACS_INT (immediate)
</span><span class="z-text z-plain"> └─ tag = Symbol → SYMBOLP() → XSYMBOL() → struct Lisp_Symbol
</span><span class="z-text z-plain"> machine bits
</span></code></pre>
<p>With the data representation in place, we can now map McCarthy's original 7 axioms directly onto these C macros.</p>
<h2 id="mapping-mccarthy-s-7-axioms-to-c">Mapping McCarthy's 7 Axioms to C</h2>
<p>If McCarthy's 7 axioms are the soul of Lisp, the Emacs source is its physical body — but that body is not confined to a single file. The axioms split across three files depending on whether they are about <em>data representation</em>, <em>memory</em>, or <em>control flow</em>:</p>
<table><thead><tr><th>Axiom</th><th>Meaning</th><th>C struct / function</th><th>File</th></tr></thead><tbody>
<tr><td><code>atom</code></td><td>is it NOT a pair?</td><td><code>!CONSP(obj)</code> (e.g., <code>EMACS_INT</code>, <code>struct Lisp_String</code>, etc.)</td><td><code>lisp.h</code></td></tr>
<tr><td><code>eq</code></td><td>are two refs identical?</td><td><code>Lisp_Object</code> (raw 64-bit word compare)</td><td><code>lisp.h</code></td></tr>
<tr><td><code>car</code></td><td>first element of pair</td><td><code>struct Lisp_Cons</code> - <code>.car</code> field</td><td><code>lisp.h</code></td></tr>
<tr><td><code>cdr</code></td><td>rest of pair</td><td><code>struct Lisp_Cons</code> - <code>.cdr</code> field</td><td><code>lisp.h</code></td></tr>
<tr><td><code>cons</code></td><td>construct a new pair</td><td><code>struct Lisp_Cons</code> allocated by <code>Fcons()</code></td><td><code>alloc.c</code></td></tr>
<tr><td><code>quote</code></td><td>return without evaluating</td><td><code>Fquote()</code> — special form</td><td><code>eval.c</code></td></tr>
<tr><td><code>cond</code></td><td>branch on predicate</td><td><code>Fcond()</code> — special form</td><td><code>eval.c</code></td></tr>
</tbody></table>
<p>Notice the split: the first four axioms — <code>atom</code>, <code>eq</code>, <code>car</code>, <code>cdr</code> — are pure <em>data</em> operations, living entirely in <code>lisp.h</code>. <code>cons</code> crosses into memory management. Only <code>quote</code> and <code>cond</code> require the <em>evaluator</em> — they are the boundary where data becomes behavior.</p>
<p>PS. Other important files written in C.</p>
<ul>
<li><code>lread.c</code> — tokenizing and reading Lisp source into <code>Lisp_Object</code> trees</li>
<li><code>eval.c</code> — evaluating those trees</li>
<li><code>alloc.c</code> — allocating and garbage-collecting Lisp objects</li>
<li><code>xdisp.c</code> — redisplay engine</li>
</ul>
<blockquote>
<p>Emacs C sources, use a lot of Lisp idioms abstracted as preprocessor macros, masking C language as Lisp look alike. Observe that, when you use them, you are not writing Lisp, you are writing pure C that just happens to look like Lisp. Those preprocessor macros exist for use in C core only, they are not visible to Elisp, and they happen to be macros for practical reasons of C programming: to always get inlined, in both release and debug builds. Alternative would be of course to implement them as inlined functions and I think they have start to replace some of those preprocessor macros with inlined versions. I am not really watching the mailing list and commited patches, so don't take me for the word.</p>
<p>If you want connection to the Lisp implementation, I think you should look into Fatom, Feq, Fcar and Fcdr in data.c, which are those "McCarthian operators" we are using in Elisp.</p>
<p>Another remark about McCarthian Lisp, since you touch on it, is that Lisp is a theory of computation that also happens to be practical tool. As a computation theory, it stands at the same level as as lambda calculus and turing machine; a mathematical construct, that happens to be runnable, so to say.</p>
<p>In McCarthy's papers it varies how many are needed, depending on which paper you read. Regardless, the idea is that those "axioms" is all you need to build computations on. A closed universe. A mathematical theory. Those axioms are like Euclidean axioms, something you can build other mathematical constructs, it is just that here we are talking about computing.</p>
<p>However, only in theory, i.e. in McCarthy's papers! :). in practical terms, I don't think there is any practical Lisp, more than toy examples, based on only those first 5, or 9 or 7 or how many forms McCarthy thought at some point in time are "basics". Emacs does not have statements on which are "elementary forms". C core implements ~1700 of "primitive" forms. Graham came up with 17 for Arc. Common Lisp has 25. Now, I have never used Arc, but I am sure that none of Common Lisp implementations in Common Lisp are implemented directly on top of only those 25 so called "special operators", even though, in theory that might have been the idea. Of course I don't know for sure, I discovered Lisp after the Lisp, and am just learning this myself, but to be practical you have to talk to the outside world, and outside world is often a bit more complex than what those 25 forms cover.</p>
<p><em>u/authurno1</em> on Reddit</p>
</blockquote>
<h2 id="next-step">Next step</h2>
<p>The tagged pointer trick Emacs uses handles 8 fundamental types perfectly, but how does the editor fit the remaining dozens of complex types (like Buffers and Windows) within the exact same 3-bit restriction?</p>
<p>In the next post, we will look at how GNU Emacs expands this layout using a technique called "Poor Man's Inheritance", compare it to Tagged Unions (like C++ <code>std::variant</code>), and see how LLVM reinvents this exact memory philosophy in the modern era.</p>
<hr />
<p>Emacs Internal Series:</p>
<ul>
<li>#01: <a href="https://thecloudlet.github.io/blog/project/emacs-01/">Emacs is a Lisp Runtime in C, Not an Editor</a></li>
<li>#02: Data First — Deconstructing Lisp_Object in C</li>
<li>#03: <a href="https://thecloudlet.github.io/blog/project/emacs-03/">Tagged Union, Tagged Pointer, and Poor Man's Inheritance</a></li>
</ul>
Emacs Internal #01: is a Lisp Runtime in C, Not an Editor2026-02-26T00:00:00+00:002026-02-26T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/project/emacs-01/<p>I tried to move to an LLM-friendly platform like VSCode or Cursor, but I kept returning to GNU Emacs. After reading other users' stories, I realized this is a common pattern. Very few tools survive 40 years and still feel hard to leave.</p>
<p>I read parts of the source code and discovered that Emacs is not just a code editor. There is <strong>design philosophy</strong>, <strong>system software trade-offs</strong>, and code that still feels like a treasure today. I want to record some personal discoveries that might be worth sharing.</p>
<p>Before we dive into the implementation, here is the "why" and the history I looked up.</p>
<h2 id="the-church-why-people-cannot-leave-gnu-emacs">The Church: why people cannot leave GNU Emacs</h2>
<p>Starting with the long-standing joke:</p>
<blockquote>
<p>Emacs, "a great operating system, lacking only a decent editor"
-- <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Editor_war">Editor War</a></p>
</blockquote>
<p>Joshua Blias's <a rel="noopener" target="_blank" href="https://www.youtube.com/watch?v=sBc7toJaCxw">Returning to the Church (of Emacs)</a> is a true story about switching to Neovim and coming back to GNU Emacs.</p>
<p>Here are common reasons why, in this modern world, people still use GNU Emacs, from a <a rel="noopener" target="_blank" href="https://www.reddit.com/r/emacs/comments/1brnmds/why_use_emacs/">Reddit post</a>.</p>
<ul>
<li>"<em>Because I love being a pariah at the office.</em>"</li>
<li>"<em>I learn one editor, once, and use it for my whole career.</em>"</li>
<li>"<em>It fits like a tailored suit.</em>" -- customizable experience, and you can mostly customize "EVERYTHING".</li>
<li>Tools that cannot be replaced: OrgMode, Magit</li>
<li>Tsoding - <a rel="noopener" target="_blank" href="https://www.youtube.com/watch?v=DMbrNhx2zWQ">The Annoying Usefulness of Emacs</a></li>
<li>The romantic Free Software Foundation spirit and unwillingness to be controlled by big tech</li>
</ul>
<p>So here is my first big question:</p>
<h2 id="the-history-why-emacs-embeds-an-elisp-interpreter-in-c">The history: Why Emacs Embeds an Elisp Interpreter in C</h2>
<p>In the 1970s, hackers at the MIT AI Lab used a text editor called <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/TECO_(text_editor)">TECO</a>. Unlike modern editors with a cursor, users had to input a sequence of password-like strings to cast the magic that edits text (<a rel="noopener" target="_blank" href="https://github.com/blakemcbride/TECOC/blob/master/doc/teco-manual.txt">TECO manual</a>). To reduce the pain, people started writing "macros" to speed up the process.</p>
<p><img src="https://www.copters.com/pictures/teco_layout.gif" alt="TECO layout" /></p>
<p>As macros grew larger and more complicated, they needed variables, <code>if-else</code> control flow, and loops. At that point, <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Richard_Stallman">Richard Stallman</a> and <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Guy_L._Steele_Jr.">Guy Steele</a> (the creator of Scheme) made a decision: "If the macro is complicated enough to act like a programming language, why not give it a real <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Turing_completeness">Turing-complete</a> programming language?"</p>
<p>This is the birth of Emacs (Editor MACroS). An interpreter made the editor itself programmable, so users could extend and evolve it live without recompiling or waiting for upstream changes. In the earliest Emacs, the "interpreter" was just TECO's macro language. Later, GNU Emacs adopted <strong>Emacs Lisp</strong>. Lisp was a natural choice because its syntax is simple, its macros are powerful, and the interpreter is small and flexible, which makes live customization easy.</p>
<p>Later, Lisp machines were a commercial failure, and <strong>C on von Neumann architecture</strong> dominated the industry. When Richard Stallman and the <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Free_Software_Foundation">Free Software Foundation</a> wanted a free Emacs on Unix, there was no Lisp environment there. So he wrote a <strong>Lisp virtual machine and interpreter core in C</strong>, effectively reviving the spirit of Lisp machines in a Unix ecosystem, because it was the path of least resistance to a complete Unix toolchain.</p>
<p>This helps explain why GNU Emacs' source code looks the way it does, and why jokes like "a great operating system" evolved.</p>
<hr />
<h2 id="things-learned-from-the-story">Things learned from the Story</h2>
<h3 id="gnu-emacs-source-code-directly">GNU Emacs source code directly</h3>
<p>After understanding more about Emacs' history, the code and directory layout feel more reasonable. In this series, we'll discuss how C implements the Lisp interpreter, memory allocation, dynamic binding, and more.</p>
<h3 id="worse-is-better-human-nature">Worse is better: human nature</h3>
<p>As to why C became dominant and not Lisp, a classic articulation of this "less elegant but more successful" outcome is Richard P. Gabriel's The Rise of <a rel="noopener" target="_blank" href="https://www.dreamsongs.com/RiseOfWorseIsBetter.html">Worse is Better</a>. Sometimes the real world works this way too...</p>
<h3 id="greenspun-s-tenth-rule">Greenspun's Tenth Rule</h3>
<blockquote>
<p>Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
-- <a rel="noopener" target="_blank" href="https://philip.greenspun.com/research/">Philip Greenspun</a></p>
</blockquote>
<p>In more direct words: "Don't fool yourself: it starts as a simple config, but it won't stop there." Humans have unlimited desire, and long-lived software eventually evolves into a DSL and needs an embedded virtual machine.</p>
<p>Vimscript is a perfect victim of this Greenspun's Rule. On the other side, Richard Stallman's vision already foresaw this curse.</p>
<p>So Vim eventually needed a fork: Neovim using a <strong>Lua runtime</strong> (still an interpreter inside). They chose Lua because <strong>LuaJIT</strong> is a modern, fast runtime. Now Lua turns Neovim into a "Lua virtual machine text editor platform." Richard Stallman would probably laugh and say, "we did this 40 years ago."</p>
<p>Although the idea looks similar, Neovim often feels faster than Emacs in practice because it leans on newer runtimes and techniques (e.g., LuaJIT, async jobs, and RPC). The following are some ways Neovim outperforms Emacs.</p>
<table><thead><tr><th style="text-align: left"></th><th style="text-align: left">Emacs Lisp</th><th style="text-align: left">Neovim</th></tr></thead><tbody>
<tr><td style="text-align: left"><strong>architecture</strong></td><td style="text-align: left">Monolithic C core + embedded Elisp interpreter</td><td style="text-align: left">C core + embedded LuaJIT + Msgpack RPC host/guest plugins</td></tr>
<tr><td style="text-align: left"><strong>concurrency</strong></td><td style="text-align: left">single-threaded main loop</td><td style="text-align: left">event loop; async jobs and RPC-based plugins</td></tr>
<tr><td style="text-align: left"><strong>union tagging</strong></td><td style="text-align: left">tagged pointers + cons cells/immediates</td><td style="text-align: left"><a rel="noopener" target="_blank" href="https://medium.com/@kannanvijayan/exboxing-bridging-the-divide-between-tag-boxing-and-nan-boxing-07e39840e0ca">NaN-tagging</a> in LuaJIT</td></tr>
</tbody></table>
<p>For modern editors like VSCode, an interpreter is still inside. VSCode is essentially a web browser for code, built on Electron (Chromium + V8). That means a <strong>VM is part of the editor's core</strong>. From a language-design viewpoint it may look less elegant (JavaScript is slow), but in practice it feels fast because of JIT, SIMD, and async IPC that keeps the UI responsive. Either way, the pattern repeats: a VM sits at the core of the editor.</p>
<p><strong>Appendix (PS): Other Greenspun's Tenth Rule victims</strong></p>
<ul>
<li>Bash scripting</li>
<li>LaTeX</li>
<li>CMake</li>
<li>Printer/PDF scripting languages</li>
<li>eBPF in the Linux kernel</li>
<li>Tcl (Tool Command Language) in the EDA industry</li>
<li>LLVM TableGen (<code>.td</code>)</li>
</ul>
<p>This is also why my cache simulator <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Stratum">Stratum</a> uses Racket to create a DSL for cache configuration.</p>
<h2 id="next-step">Next step:</h2>
<p>A. How to build a tiny Emacs Lisp interpreter in C with only seven elements</p>
<ol>
<li>quote</li>
<li>atom</li>
<li>eq</li>
<li>car</li>
<li>cdr</li>
<li>cons</li>
<li>cond</li>
</ol>
<p>B. How <code>Lisp_Object</code> works?</p>
<hr />
<p>Emacs Internal Series:</p>
<ul>
<li>#01: Emacs is a Lisp Runtime in C, Not an Editor</li>
<li>#02: <a href="https://thecloudlet.github.io/blog/project/emacs-02/">Data First — Deconstructing Lisp_Object in C</a></li>
<li>#03: <a href="https://thecloudlet.github.io/blog/project/emacs-03/">Tagged Union, Tagged Pointer, and Poor Man's Inheritance</a></li>
</ul>
Wishful Thinking from SICP, Practically2026-02-04T00:00:00+00:002026-02-04T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/reading/wishful-thinking/<p>This is <strong>not</strong> a pure technical blog post. It is an attempt to connect engineering principles to real life.</p>
<p>I've been trying to understand my anxiety and self-doubt through engineering principles. <a rel="noopener" target="_blank" href="https://web.mit.edu/6.001/6.037/sicp.pdf">Structure and Interpretation of Computer Programs (SICP)</a> wishful thinking gave me a framework to think about this. But I'm learning that understanding a pattern intellectually doesn't mean I've solved it emotionally. This post is about what I'm learning—not what I've figured out.</p>
<h2 id="what-is-wishful-thinking">What is Wishful Thinking?</h2>
<p>In Chapter 2 of SICP, the authors introduce a powerful strategy for building abstractions: wishful thinking. The idea is simple but profound: <strong>write code as if the pieces you need already exist.</strong> Define the interface you wish you had, then trust that the implementation details can be filled in later.</p>
<p>When explaining this concept, the authors write:</p>
<blockquote>
<p>We are using here a powerful strategy of synthesis: wishful thinking.
We haven’t yet said how a rational number is represented, or how the
procedures <code>numer</code>, <code>denom</code>, and <code>make-rat</code> should be implemented. Even
so, if we did have these three procedures, we could then <code>add</code>, <code>subtract</code>,
<code>multiply</code>, <code>divide</code>, and test <code>equality</code> ...</p>
</blockquote>
<p>To make this concrete, here's the same concept in C++ without OOP. Notice how we <strong>use functions that don't exist yet</strong>, and we haven't defined how the <strong>Rational</strong> data type is represented—that's wishful thinking:</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">numer</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">r</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">denom</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">r</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">Rational <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">make_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-variable z-parameter z-c++">numerator</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-type z-c">int</span> <span class="z-variable z-parameter z-c++">denominator</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Now we can write high-level operations using our "wished" interface
</span></span><span class="z-source z-c++">Rational <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">add_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">x</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">y</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">make_rat</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-arithmetic z-c">+</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-separator z-c++">,</span>
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++">Rational <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">sub_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">x</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">y</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">make_rat</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-arithmetic z-c">-</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-separator z-c++">,</span>
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++">Rational <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">mul_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">x</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">y</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">make_rat</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-separator z-c++">,</span>
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++">Rational <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">div_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">x</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">y</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">make_rat</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-separator z-c++">,</span>
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++"><span class="z-storage z-type z-c">bool</span> <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">equal_rat</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">x</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-modifier z-c++">const</span> Rational<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">y</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-comparison z-c">==</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">numer</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">y</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span> <span class="z-keyword z-operator z-c">*</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">denom</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">x</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></code></pre>
<p>Now, the rest of the missing pieces tend to emerge naturally, almost effortlessly.</p>
<p>This way of thinking gave me a way to stop reacting blindly to every signal, and instead design my next step deliberately. In that sense, wishful thinking wasn’t just a programming technique for me—it became a survival tool.</p>
<hr />
<h2 id="how-i-apply-wishful-thinking-to-my-life">How I Apply Wishful Thinking to My Life</h2>
<p>How do I actually use <em>wishful thinking</em> in my life?</p>
<p>I follow these steps:</p>
<ol>
<li>
<p><strong>Define the outcome</strong></p>
<p>Specify the mental state or concrete achievement you are aiming for.</p>
</li>
<li>
<p><strong>Identify the procedures</strong></p>
<p>Define the procedures that would contribute to achieving that outcome, without worrying about their implementation yet.</p>
</li>
<li>
<p><strong>Lazy evaluation</strong></p>
<p>Defer thinking and decision‑making until action is actually required.
Avoid premature optimization, over‑planning, or the urge to make everything perfect upfront.</p>
</li>
</ol>
<p>For example, I define my perfect day like this:</p>
<pre data-lang="racket" class="language-racket z-code"><code class="language-racket" data-lang="racket"><span class="z-source z-racket"><span class="z-meta z-function z-source z-racket">(<span class="z-keyword z-source z-racket">define</span> (<span class="z-entity z-name z-function">a-great-day</span></span>)
</span><span class="z-source z-racket"> <span class="z-meta z-keywords z-source z-racket">(<span class="z-keyword z-source z-racket">let</span> </span>([breakfast (with-hot-coffee)]
</span><span class="z-source z-racket"> [commute-to-work (listen-to-good-music)]
</span><span class="z-source z-racket"> [work (perform-meaningful-contribution)]
</span><span class="z-source z-racket"> [family (initiate-a-warm-hug)]
</span><span class="z-source z-racket"> [growth (learn-<span class="z-constant z-numeric z-integer z-source z-racket">1</span>-simple-thing-today)])))
</span></code></pre>
<p>Or my ideal life:</p>
<pre data-lang="racket" class="language-racket z-code"><code class="language-racket" data-lang="racket"><span class="z-source z-racket"><span class="z-meta z-function z-source z-racket">(<span class="z-keyword z-source z-racket">define</span> (<span class="z-entity z-name z-function">my-ideal-life</span></span>)
</span><span class="z-source z-racket"> <span class="z-meta z-keywords z-source z-racket">(<span class="z-keyword z-source z-racket">let</span> </span>([mind (inner-peace)]
</span><span class="z-source z-racket"> [body (healthy-body)]
</span><span class="z-source z-racket"> [family (cherish-everyday)]
</span><span class="z-source z-racket"> [interaction (spread-warmth-and-kindness)])))
</span></code></pre>
<p>The following are my own reminders:</p>
<h3 id="01-define-the-goal">01. Define the Goal</h3>
<p>A gentle reminder from Naval Ravikant: “Desire is a contract you make with yourself to be unhappy until you get what you want.” That quote forces me to define my “good day” and “ideal life” with extreme restraint.</p>
<p>I often catch myself chasing things that society rewards—status, recognition, external validation. But pursuing those metrics usually leads to anxiety and dissatisfaction, which is the opposite of what I actually want. The irony is obvious: I was optimizing for the wrong objective function.</p>
<blockquote>
<p>Clarity is not motivation; it is a filter.</p>
</blockquote>
<p>When the end state is not clearly defined, every option feels equally important. Everything competes for attention, and motion feels like progress—but nothing converges.</p>
<p>Once the end state is clear, most options fall away naturally. What looked like luck was just knowing where I was heading.</p>
<p>Effortless doesn't mean easy. It means the system knows where it's going.</p>
<h3 id="02-find-the-procedures">02. Find the Procedures</h3>
<blockquote>
<p>Always work top-down, not bottom-up. Think recursive.</p>
</blockquote>
<p>I've been struggling at work for three years. I ship solid technical work—optimizations, stable regressions—but I struggle with communication and office dynamics. When my senior colleague consistently rejects my code reviews, I get defensive. I speak too directly. I miss social cues. The mismatch is real: this environment needs both technical skills and political awareness. I have the former, I'm learning the latter is not my strength. This hurts to admit.</p>
<p>I'm trying to distinguish between useful feedback and noise. But I notice I sometimes filter out painful truths by labeling them 'not aligned with growth.' When someone points out I'm too aggressive in code reviews, I want to dismiss it. But maybe that's exactly the feedback I need. I'm still learning where the line is between protecting myself from toxic criticism and avoiding uncomfortable growth.</p>
<blockquote>
<p>Not all feedback is input; some of it is noise.</p>
</blockquote>
<h3 id="03-lazy-evaluation">03. Lazy Evaluation</h3>
<p>This is the most important part.</p>
<ol>
<li>
<p><strong>Before execution, don’t panic.</strong></p>
<p>Avoid catastrophizing the future. Trust that when execution starts, the next required step will naturally become clearer. Just like a lazy evaluator, clarity is produced only when demanded.</p>
</li>
<li>
<p><strong>Execute lazily. Live at runtime.</strong></p>
<p>This idea maps surprisingly well to Zen thinking. Zen does not ask you to stop thinking—it asks you to stop precomputing.</p>
<p>When I execute a task, I try to stay fully inside the current stack frame. I am only here, only now. In that sense, I am a single-threaded system.</p>
<p>Humans are not compilers running with <code>-O3</code>. We don't need to speculate every branch, unroll every loop, or pipeline every future scenario. Multitasking looks powerful, but for human brains it often results in cache stalls, context switching, and mental thrashing.</p>
</li>
<li>
<p><strong>Meaning emerges during execution, not before it.</strong></p>
<p>Zen has a quiet reminder:</p>
<blockquote>
<p>The path appears where the feet land.</p>
</blockquote>
<p>In engineering terms, many insights are late-bound. They cannot be derived at design time—they emerge only when the system runs. Wishful thinking lets me defer unnecessary decisions. Zen teaches me to stay present enough to recognize the right one when it finally arrives.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>I found wishful thinking when I was looking for answers. I was struggling—with self-doubt, with not knowing my worth, with trying to prove something I couldn't name. Systematic thinking felt like solid ground in uncertain terrain.</p>
<p>But here's what I'm learning: I use frameworks to feel in control when I'm actually scared. When life feels chaotic, I reach for structure. When I'm hurt, I intellectualize. This isn't wrong—it's how my mind works. But it has limits. Some wounds don't need better architecture. They need time, and rest, and the courage to admit I don't have it figured out.</p>
<p>SICP taught me wishful thinking as engineering practice. Life is teaching me it's also about wishing for peace while not knowing how to get there—and learning that's okay. Some days I remember this. Most days I forget and try to optimize my way out of uncertainty. That's where I am: somewhere between understanding and living, between the wish and the trust.</p>
<p>I'm still learning.</p>
<hr />
<h2 id="other-reference">Other Reference</h2>
<ul>
<li><a rel="noopener" target="_blank" href="https://sookocheff.com/post/fp/evaluating-lambda-expressions/">Applicative-Order vs Normal-Order Evaluation</a></li>
<li><a rel="noopener" target="_blank" href="https://wiki.haskell.org/Thunk">Thunk (Haskell)</a></li>
<li><a rel="noopener" target="_blank" href="https://www.youtube.com/watch?v=G6YZSyoShBE">The Lazy Method</a>, by Josh Brindley.</li>
</ul>
Stratum: Architecting a Configurable Cache Simulator with C++ and Racket2026-01-29T00:00:00+00:002026-01-29T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/project/stratum/<h2 id="the-interview-that-changed-everything">The Interview That Changed Everything</h2>
<p>During a recent interview, the conversation started well. The interviewer asked about my open-source contribution to <a rel="noopener" target="_blank" href="https://github.com/sysprog21/rv32emu">rv32emu</a>—specifically, how I achieved 52% faster lookups and 35% faster insertions in the red-black tree implementation (<a rel="noopener" target="_blank" href="https://github.com/sysprog21/rv32emu/commit/434c46660f67c78d9a4f587e05d2d59ec2102dc0">commit 434c466</a>).</p>
<p>My brain immediately started searching for the answer from <em>memory</em>—both the biological kind and the DRAM kind. Since I'd explained this optimization before, the answer was already cached:</p>
<p><strong>The optimization:</strong> Eliminate the parent pointer from each node, shrinking node size by 20%. Fewer bytes per node means better cache density.</p>
<p><strong>The technique:</strong> Instead of storing parent pointers in every node, maintain a path array on the stack during tree traversal. The path array stays hot in L1 cache, while the old approach scattered parent pointers across the heap, causing cache misses at every step.</p>
<pre data-lang="c" class="language-c z-code"><code class="language-c" data-lang="c"><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Old: 3 pointers per node
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">map_node</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span> <span class="z-keyword z-operator z-c">*</span>key<span class="z-punctuation z-separator z-c">,</span> <span class="z-keyword z-operator z-c">*</span>data<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">long</span> parent_color<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">map_node</span></span></span><span class="z-meta z-struct z-c"> *left, *right</span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> New: 2 pointers per node
</span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">map_node</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">void</span> <span class="z-keyword z-operator z-c">*</span>key<span class="z-punctuation z-separator z-c">,</span> <span class="z-keyword z-operator z-c">*</span>data<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">map_node</span></span></span><span class="z-meta z-struct z-c"> *left, *right_red</span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Path tracking on stack (stays in L1)
</span></span><span class="z-source z-c">rb_path_entry_t path<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span>RB_MAX_DEPTH<span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c"><span class="z-keyword z-control z-c">for</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>pathp <span class="z-keyword z-operator z-assignment z-c">=</span> path<span class="z-punctuation z-terminator z-c">;</span> pathp<span class="z-punctuation z-accessor z-c">-></span>node<span class="z-punctuation z-terminator z-c">;</span> pathp<span class="z-keyword z-operator z-arithmetic z-c">++</span><span class="z-punctuation z-section z-group z-end z-c">)</span></span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span>
</span></span><span class="z-source z-c"><span class="z-meta z-block z-c"> pathp<span class="z-punctuation z-accessor z-c">-></span>cmp <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>rb<span class="z-punctuation z-accessor z-c">-></span>comparator<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>node<span class="z-punctuation z-accessor z-c">-></span>key<span class="z-punctuation z-separator z-c">,</span> pathp<span class="z-punctuation z-accessor z-c">-></span>node<span class="z-punctuation z-accessor z-c">-></span>key<span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></span><span class="z-source z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span>
</span></code></pre>
<p>Simple, clean, and—I thought—demonstrated solid understanding of cache behavior.</p>
<p>If there were a camera rolling, I probably had a pretty confident smile on my face. Then the interviewer asked the next question:</p>
<blockquote>
<p><strong>"What is the main cause of cache-miss latency?"</strong></p>
</blockquote>
<p>Well, I replied. (Also from my cache.)</p>
<p>"If we can't find data in L1, we fall back to L2. And since L2 is usually bigger than L1, finding an address in L2 is slower."</p>
<p>Seemed reasonable, right?</p>
<blockquote>
<p><strong>"If L2 is the same size as L1, where does the latency difference come from?"</strong></p>
</blockquote>
<p>"Physical distance," I replied. "The wire length from CPU to L1 versus L2."</p>
<p>The interviewer paused, then continued:</p>
<blockquote>
<p><strong>"What if you ran your benchmark on an ARM big.LITTLE SoC instead of x86?"</strong> > <strong>"What if the cache associativity changed?"</strong> > <strong>"What if the replacement policy was different?"</strong></p>
</blockquote>
<p>My smile faded. If this were a cartoon, there'd be question marks floating above my head.</p>
<p>That's when I realized: I could optimize <em>for</em> cache behavior, but I didn't actually understand <em>how</em> caches work.</p>
<p>My rb-tree optimization succeeded on one machine with one cache configuration. I had no idea if it would work anywhere else—or why.</p>
<hr />
<h2 id="back-to-the-basics-study-cache-in-a-software-engineer-s-perspective">Back to the basics: Study Cache in a Software Engineer's Perspective</h2>
<p>After that interview, I needed to understand caches from first principles. I started with CS:APP Chapter 6 (<a rel="noopener" target="_blank" href="https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/books/CSAPP_2016.pdf">book</a>, <a rel="noopener" target="_blank" href="https://www.youtube.com/watch?v=vusQa4pfTFU">lectures</a>)—the canonical resource for cache architecture.</p>
<h3 id="the-fundamentals-that-matter">The Fundamentals That Matter</h3>
<p><strong>Cache Structure: It's Just a 2D Array</strong></p>
<p>A cache is organized as S sets × E ways. Each cache line contains three fields:</p>
<pre class="z-code"><code><span class="z-text z-plain"> 1 valid bit t tag bits B = 2^b bytes
</span><span class="z-text z-plain"> per line per line per cache block
</span><span class="z-text z-plain"> +-------+------------------+---+---+-----+-----+
</span><span class="z-text z-plain"> Set 0: | Valid | Tag | 0 | 1 | ... | B-1 | <- Way 0
</span><span class="z-text z-plain"> +-------+------------------+---+---+-----+-----+
</span><span class="z-text z-plain"> Set 1: | Valid | Tag | 0 | 1 | ... | B-1 | <- Way 1
</span><span class="z-text z-plain"> +-------+------------------+---+---+-----+-----+
</span><span class="z-text z-plain"> ...
</span><span class="z-text z-plain"> +-------+------------------+---+---+-----+-----+
</span><span class="z-text z-plain">Set S-1: | Valid | Tag | 0 | 1 | ... | B-1 |
</span><span class="z-text z-plain"> +-------+------------------+---+---+-----+-----+
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Cache size: C = S × E × B data bytes
</span></code></pre>
<p><strong>Example:</strong> 8-way set-associative, 256 sets, 64-byte blocks
-> 256 × 8 × 64 = 128 KB cache</p>
<p><strong>Associativity:</strong> The Trade-off I Missed</p>
<p>This is what the interviewer was asking about. Associativity (E-way) determines how many cache lines in a set can hold data:</p>
<ol>
<li>
<p><strong>Direct-mapped (E=1)</strong>: Each address maps to exactly ONE location</p>
<ul>
<li>Fastest (no way selection)</li>
<li>Most conflict misses</li>
</ul>
</li>
<li>
<p><strong>N-way set-associative (E=N)</strong>: Each address can go into N locations within a set</p>
<ul>
<li>Hardware checks all N tags in parallel</li>
<li>Requires N-input multiplexer -> higher latency</li>
</ul>
</li>
<li>
<p><strong>Fully-associative</strong>: Any address can go anywhere</p>
<ul>
<li>No conflict misses</li>
<li>Slowest (compare against ALL cache lines)</li>
</ul>
</li>
</ol>
<p><strong>Now the interviewer's question made sense:</strong></p>
<p>Same size, different latency -> different associativity -> different tag comparison circuitry.</p>
<p>L2 isn't slower because it's "bigger"—it's slower because it's 16-way instead of 8-way.</p>
<h3 id="the-recursive-pattern">The Recursive Pattern</h3>
<p><strong>Cache Read Process:</strong></p>
<ol>
<li><strong>Select the set</strong> using set_index</li>
<li><strong>Check all ways</strong> in the set (parallel tag comparison)</li>
<li><strong>Handle the result:</strong>
<ul>
<li><strong>Cache hit</strong>: Return data immediately</li>
<li><strong>Cache miss</strong>:
<ol>
<li><strong>Fetch from next level</strong> (L2, L3, or DRAM)</li>
<li><strong>Store in current cache</strong> (allocate a line, evict if needed)</li>
<li><strong>Return data</strong> to CPU</li>
</ol>
</li>
</ul>
</li>
</ol>
<p>Wait. Step 3 is interesting.</p>
<p>When L1 misses, it asks L2. When L2 misses, it asks L3. When L3 misses, it asks DRAM. Each level follows the same pattern: check locally, delegate on miss.</p>
<p>This is a <strong>recursive process</strong>—the same pattern SICP Chapter 1.2 describes as "deferred operations that build up." Each cache level is a function that either returns data or calls the next level.</p>
<pre data-lang="scheme" class="language-scheme z-code"><code class="language-scheme" data-lang="scheme"><span class="z-text z-plain">; Cache lookup as recursive process (SICP perspective)
</span><span class="z-text z-plain">; This is pseudocode to illustrate the concept, not actual Stratum code
</span><span class="z-text z-plain">(define (cache-lookup addr level)
</span><span class="z-text z-plain"> (let ((result (probe-cache level addr)))
</span><span class="z-text z-plain"> (if (hit? result)
</span><span class="z-text z-plain"> (extract-data result)
</span><span class="z-text z-plain"> (let ((data (cache-lookup addr (next-level level))))
</span><span class="z-text z-plain"> (cache-fill level addr data) ; Write fetched data back to current level
</span><span class="z-text z-plain"> data))))
</span></code></pre>
<p>That's when I decided: <strong>implement cache hierarchy as recursive types in C++.</strong></p>
<p>Instead of a traditional OOP design with virtual methods and polymorphism, use template metaprogramming to bind the hierarchy at compile time—just like hardware does at synthesis time.</p>
<hr />
<p><strong>Further reading:</strong></p>
<ul>
<li><a rel="noopener" target="_blank" href="https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/books/CSAPP_2016.pdf">CS:APP Chapter 6</a> - Cache fundamentals</li>
<li><a rel="noopener" target="_blank" href="https://web.mit.edu/6.001/6.037/sicp.pdf">SICP Chapter 1.2</a> - Recursive processes</li>
<li><a rel="noopener" target="_blank" href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">What Every Programmer Should Know About Memory</a> - Section 3.1</li>
</ul>
<hr />
<h2 id="building-stratum-digging-through-memory-layers">Building Stratum: Digging Through Memory Layers</h2>
<p>Here's how I think about cache hierarchy from a compiler backend perspective:</p>
<p><img src="/images/stratum-svg.svg" alt="Stratum Memory Hierarchy" /></p>
<p>The name "Stratum" comes from this mental model: memory hierarchy as <strong>geological layers</strong>.</p>
<p><strong>Above ground:</strong> CPU and registers—visible, fast, directly accessible.</p>
<p><strong>Below ground:</strong> Cache stratums—hidden, progressively deeper and slower. When the compiler generates a load instruction (<code>lw</code> in RISC-V), it's essentially saying "dig down through the strata until you find this data."</p>
<ul>
<li>L1 cache miss? Dig deeper to L2.</li>
<li>L2 cache miss? Dig deeper to L3.</li>
<li>L3 cache miss? Excavate all the way down to DRAM.</li>
</ul>
<blockquote>
<p>From the grounds down.</p>
</blockquote>
<p><em>(Apologies to Canadian aviation students Not up--we're compiler people, we dig)</em></p>
<p>From a compiler backend perspective, every load instruction digs <em>down</em> through memory layers to find data. Which stratum is your data in? L1? L2? DRAM? The depth determines the latency.</p>
<p>That's why this project is named "Stratum"—because cache hierarchy is literally about drilling through geological layers of memory.</p>
<h3 id="design-principles">Design Principles</h3>
<p>This geological metaphor shaped Stratum's architecture:</p>
<p><strong>1. Discrete layers</strong>: Each cache level is independent
<strong>2. Miss delegation</strong>: On miss, ask the next layer down
<strong>3. Fixed topology</strong>: Like rock strata, layers don't rearrange at runtime</p>
<p>This maps to the recursive pattern from SICP Chapter 1.2: each level is a function that either returns data or recursively calls the next level.</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-keyword z-control z-c++">using</span> <span class="z-entity z-name z-type z-using z-c++">MemType</span> <span class="z-keyword z-operator z-assignment z-c">=</span> MainMemory<span class="z-punctuation z-section z-generic z-begin z-c++"><</span><span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>MainMemory<span class="z-punctuation z-definition z-string z-end z-c">"</span></span><span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-keyword z-control z-c++">using</span> <span class="z-entity z-name z-type z-using z-c++">L2Type</span> <span class="z-keyword z-operator z-assignment z-c">=</span> Cache<span class="z-punctuation z-section z-generic z-begin z-c++"><</span><span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>L2<span class="z-punctuation z-definition z-string z-end z-c">"</span></span><span class="z-punctuation z-separator z-c++">,</span> MemType<span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">512</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">8</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">64</span><span class="z-punctuation z-separator z-c++">,</span> LRUPolicy<span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">10</span><span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-keyword z-control z-c++">using</span> <span class="z-entity z-name z-type z-using z-c++">L1Type</span> <span class="z-keyword z-operator z-assignment z-c">=</span> Cache<span class="z-punctuation z-section z-generic z-begin z-c++"><</span><span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>L1<span class="z-punctuation z-definition z-string z-end z-c">"</span></span><span class="z-punctuation z-separator z-c++">,</span> L2Type<span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">64</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">8</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">64</span><span class="z-punctuation z-separator z-c++">,</span> LRUPolicy<span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-numeric z-integer z-decimal z-c++">4</span><span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> ^ ^ ^ ^ ^ ^ ^
</span></span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Name NextLayer Sets Ways BlockSize Policy HitLatency
</span></span></code></pre>
<p>Each cache level is a <strong>type</strong> that statically binds to the next layer. The entire hierarchy resolves at compile time—just like how hardware interconnects are fixed when you synthesize a Verilog/VHDL design into gates.</p>
<p><strong>Why this design?</strong></p>
<p>Traditional OOP would use virtual methods and runtime dispatch. But cache topology is fixed at hardware synthesis—there's no runtime decision about "which next level to query."</p>
<p>Think of it like <strong>wiring in Verilog</strong> (<code>wire</code> keyword), <strong>signal connections in VHDL</strong>, or <strong>module connections in Chisel</strong> (<code>:=</code> and <code><></code> operators): L1's miss port is hardwired to L2's input port. You don't change this at runtime; it's baked into the silicon.</p>
<p>Template metaprogramming captures this constraint: compile-time binding eliminates virtual dispatch overhead, mirroring how HDL synthesis produces fixed interconnects.</p>
<p><strong>Disadvantages:</strong></p>
<ol>
<li>
<p><strong>Template error messages are cryptic</strong>
Template instantiation errors are... educational. You will become very familiar with <code>std::enable_if</code> and SFINAE, whether you want to or not.</p>
</li>
<li>
<p><strong>Recompilation required for every configuration</strong>
This is fine for one-off experiments but painful for systematic exploration. (Just like changing Verilog parameters requires resynthesis. This is why I added the Racket code generator.)</p>
</li>
</ol>
<p><strong>Advantages:</strong></p>
<ol>
<li>
<p><strong>Significantly faster than OOP design</strong>
No vtable overhead, no runtime dispatch—like the difference between software function calls vs. hardwired logic gates.</p>
</li>
<li>
<p><strong>Very simple configuration</strong>
Just a few parameters to specify cache size, associativity, block size, etc.</p>
</li>
</ol>
<h3 id="automating-configuration-with-racket">Automating Configuration with Racket</h3>
<p>The C++ template approach was fast, but had a painful limitation:
<strong>Every configuration change requires full recompilation.</strong></p>
<p>Want to compare 2-level vs 3-level cache? Recompile. <br />
Want to test LRU vs FIFO? Recompile. <br />
Want to sweep associativity from 4-way to 16-way? Recompile.</p>
<p>For a single experiment, this is tolerable. For systematic exploration across dozens of configurations, it's a productivity killer.</p>
<p><strong>Solution: Generate the C++ code programmatically.</strong></p>
<p>Instead of manually editing templates, define configurations as S-expressions and generate all variants at one:</p>
<pre data-lang="racket" class="language-racket z-code"><code class="language-racket" data-lang="racket"><span class="z-source z-racket"><span class="z-comment z-line z-documentation z-source z-racket">;; Compare 3-level vs 2-level hierarchy
</span></span><span class="z-source z-racket"><span class="z-meta z-variable z-source z-racket">(<span class="z-keyword z-source z-racket">define</span> <span class="z-entity z-name z-variable z-source z-racket">experiments</span>
</span></span><span class="z-source z-racket"> (case_001
</span><span class="z-source z-racket"> <span class="z-comment z-line z-documentation z-source z-racket">;; name sets way latency policy nextLevel
</span></span><span class="z-source z-racket"> (L1 <span class="z-constant z-numeric z-integer z-source z-racket">64</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">4</span> LRUPolicy L2)
</span><span class="z-source z-racket"> (L2 <span class="z-constant z-numeric z-integer z-source z-racket">512</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">64</span> LRUPolicy L3)
</span><span class="z-source z-racket"> (L3 <span class="z-constant z-numeric z-integer z-source z-racket">8192</span> <span class="z-constant z-numeric z-integer z-source z-racket">16</span> <span class="z-constant z-numeric z-integer z-source z-racket">64</span> LRUPolicy MainMemory))
</span><span class="z-source z-racket">
</span><span class="z-source z-racket"> (case_002
</span><span class="z-source z-racket"> <span class="z-comment z-line z-documentation z-source z-racket">;; name sets way latency policy nextLevel
</span></span><span class="z-source z-racket"> (L1 <span class="z-constant z-numeric z-integer z-source z-racket">64</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">4</span> LRUPolicy L2)
</span><span class="z-source z-racket"> (L2 <span class="z-constant z-numeric z-integer z-source z-racket">512</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">64</span> LRUPolicy MainMemory)))
</span></code></pre>
<p>Run <code>racket config.rkt</code> to generate <code>case_001.cpp</code> and <code>case_002.cpp</code>.</p>
<p>Compile once, run both experiments.</p>
<p>Then build using cmake, we can have all different reports of executable ready to compare.</p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">racket</span></span><span class="z-meta z-function-call z-arguments z-shell"> scripts/config.rkt <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Regenerate C++ code</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cmake</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>build</span> build <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Build executable</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Run experiments</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./build/bin/case_001</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-keyword z-operator z-assignment z-redirection z-shell">></span> results_3level.txt</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./build/bin/case_002</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-keyword z-operator z-assignment z-redirection z-shell">></span> results_2level.txt</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">diff</span></span><span class="z-meta z-function-call z-arguments z-shell"> results_3level.txt results_2level.txt</span>
</span></code></pre>
<p>Output:</p>
<pre class="z-code"><code><span class="z-text z-plain">=========================================================
</span><span class="z-text z-plain">Running Simulation: Sequential (/path/to/sequential.txt)
</span><span class="z-text z-plain">=========================================================
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">=== Simulation Results (Aggregated) ===
</span><span class="z-text z-plain">Level Hits Misses Avg Latency (cyc)
</span><span class="z-text z-plain">L1 4375 625 4
</span><span class="z-text z-plain">L2 0 625 0
</span><span class="z-text z-plain">L3 0 625 0
</span><span class="z-text z-plain">MainMemory 625 0 232
</span></code></pre>
<p><strong>Why Racket for Code Generation?</strong></p>
<p><strong>TL;DR: I didn't want to write a parser.</strong></p>
<p>In Racket, the configuration file IS the program:</p>
<pre data-lang="racket" class="language-racket z-code"><code class="language-racket" data-lang="racket"><span class="z-source z-racket"><span class="z-comment z-line z-documentation z-source z-racket">;; This is valid Racket code AND valid configuration data:
</span></span><span class="z-source z-racket"><span class="z-meta z-variable z-source z-racket">(<span class="z-keyword z-source z-racket">define</span> <span class="z-entity z-name z-variable z-source z-racket">experiments</span>
</span></span><span class="z-source z-racket"> '((case_001
</span><span class="z-source z-racket"> (L1 <span class="z-constant z-numeric z-integer z-source z-racket">64</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">4</span> LRUPolicy L2)
</span><span class="z-source z-racket"> (L2 <span class="z-constant z-numeric z-integer z-source z-racket">512</span> <span class="z-constant z-numeric z-integer z-source z-racket">8</span> <span class="z-constant z-numeric z-integer z-source z-racket">64</span> LRUPolicy L3))))
</span></code></pre>
<p>No JSON. No YAML. No <code>configparser</code>. Just <code>read</code> the file and you have nested lists ready to process.</p>
<p>In Python, you'd need to pick a format (JSON? YAML? TOML?) and write parsing logic:</p>
<pre data-lang="python" class="language-python z-code"><code class="language-python" data-lang="python"><span class="z-source z-python"><span class="z-meta z-statement z-import z-python"><span class="z-keyword z-control z-import z-python">import</span> <span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">json</span></span></span>
</span><span class="z-source z-python"><span class="z-meta z-statement z-with z-python"><span class="z-keyword z-control z-flow z-with z-python">with</span> <span class="z-meta z-function-call z-python"><span class="z-meta z-qualified-name z-python"><span class="z-variable z-function z-python"><span class="z-support z-function z-builtin z-python">open</span></span></span></span><span class="z-meta z-function-call z-arguments z-python"><span class="z-punctuation z-section z-arguments z-begin z-python">(</span><span class="z-meta z-string z-python"><span class="z-string z-quoted z-double z-python"><span class="z-punctuation z-definition z-string z-begin z-python">"</span></span></span><span class="z-meta z-string z-python"><span class="z-string z-quoted z-double z-python">config.json<span class="z-punctuation z-definition z-string z-end z-python">"</span></span></span><span class="z-punctuation z-section z-arguments z-end z-python">)</span></span> <span class="z-meta z-statement z-with z-python"><span class="z-keyword z-control z-flow z-with z-as z-python">as</span></span></span><span class="z-meta z-statement z-with z-python"> <span class="z-meta z-generic-name z-python">f</span><span class="z-punctuation z-section z-block z-with z-python">:</span></span>
</span><span class="z-source z-python"> <span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">experiments</span></span> <span class="z-keyword z-operator z-assignment z-python">=</span> <span class="z-meta z-function-call z-python"><span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">json</span></span><span class="z-meta z-qualified-name z-python"><span class="z-punctuation z-accessor z-dot z-python">.</span></span><span class="z-meta z-qualified-name z-python"><span class="z-variable z-function z-python"><span class="z-meta z-generic-name z-python">load</span></span></span></span><span class="z-meta z-function-call z-arguments z-python"><span class="z-punctuation z-section z-arguments z-begin z-python">(</span><span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">f</span></span><span class="z-punctuation z-section z-arguments z-end z-python">)</span></span> <span class="z-comment z-line z-number-sign z-python"><span class="z-punctuation z-definition z-comment z-python">#</span> Now deal with dicts/lists
</span></span><span class="z-source z-python"> <span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">name</span></span> <span class="z-keyword z-operator z-assignment z-python">=</span> <span class="z-meta z-item-access z-python"><span class="z-meta z-qualified-name z-python"><span class="z-meta z-generic-name z-python">config</span></span></span><span class="z-meta z-item-access z-python"><span class="z-punctuation z-section z-brackets z-begin z-python">[</span></span><span class="z-meta z-item-access z-arguments z-python"><span class="z-meta z-string z-python"><span class="z-string z-quoted z-double z-python"><span class="z-punctuation z-definition z-string z-begin z-python">"</span></span></span><span class="z-meta z-string z-python"><span class="z-string z-quoted z-double z-python">name<span class="z-punctuation z-definition z-string z-end z-python">"</span></span></span></span><span class="z-meta z-item-access z-python"><span class="z-punctuation z-section z-brackets z-end z-python">]</span></span> <span class="z-comment z-line z-number-sign z-python"><span class="z-punctuation z-definition z-comment z-python">#</span> String keys everywhere
</span></span></code></pre>
<p><strong>The cost:</strong> ~100 lines of Racket code vs ~150-200 lines of Python (after adding <code>json.load</code>, error handling, and dict unpacking).</p>
<p><strong>The benefit:</strong> For someone who already knows Lisp, Racket is faster to write and harder to break (no missing commas in JSON, no YAML indentation errors).</p>
<p>If you don't already know Racket, <strong>use Python</strong>. The productivity gain only exists if you're fluent in Lisp.</p>
<hr />
<h2 id="building-stratum-answering-the-questions-i-couldn-t">Building Stratum: Answering the Questions I Couldn't</h2>
<p>After studying CS:APP and building <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Stratum">Stratum</a>, I can now properly answer those interview questions. More importantly, I understand <em>why</em> my rb-tree optimization worked--and when it might not.</p>
<h3 id="question-1-what-causes-cache-miss-latency">Question 1: What Causes Cache-Miss Latency?</h3>
<p>My original answer was wrong. I said "L2 is bigger so slower."</p>
<p><strong>The main reason:</strong> L2/L3 are physically larger—longer wires and interconnects stretch the access pipeline. Size and wire delay dominate once you leave L1.</p>
<p><strong>But there's more to it.</strong> Latency also comes from <strong>tag comparison</strong> and <strong>way selection</strong>.</p>
<p>When you access L2, hardware must:</p>
<ol>
<li>Compare the address tag against <em>all ways</em> in the set (parallel)</li>
<li>Select the matching way using a multiplexer</li>
</ol>
<p>Higher associativity (more ways) = more parallel comparisons + bigger multiplexer = longer critical path.</p>
<p><strong>Why L2 is slower than L1:</strong></p>
<ul>
<li>L1: 4-8 ways (faster tag compare, shorter wires)</li>
<li>L2: 16+ ways (more comparisons, longer wires)</li>
<li>Physical distance dominates, but associativity adds overhead</li>
</ul>
<p><strong>Real hardware nuance:</strong> Modern CPUs mitigate associativity costs with banking and pipelining. In practice, size/wire delay and staging dominate L2/L3 latency; associativity is an important but secondary knob.</p>
<h3 id="question-2-same-size-different-latency-why">Question 2: Same Size, Different Latency—Why?</h3>
<p><strong>The real answer:</strong> Different associativity.</p>
<p>Same capacity (<code>C = S * E * B</code>), different organizations:</p>
<ul>
<li>Cache A: 512 sets × 1 way (direct-mapped)</li>
<li>Cache B: 64 sets × 8 ways (8-way associative)</li>
</ul>
<p>Cache B is slower because:</p>
<ul>
<li>8 parallel tag comparisons (vs 1 in Cache A)</li>
<li>8-input multiplexer (vs direct wire in Cache A)</li>
</ul>
<p><strong>Trade-off:</strong></p>
<ul>
<li>Direct-mapped: Fast but conflict misses</li>
<li>8-way: Slower but fewer conflicts</li>
</ul>
<p><strong>Real hardware nuance:</strong> Modern CPUs narrow this gap using parallel tag+data access, way prediction, and banking. But higher associativity still tends to add latency while reducing conflict misses.</p>
<h3 id="question-3-what-if-associativity-policy-changes">Question 3: What If Associativity/Policy Changes?</h3>
<p><strong>The hard truth:</strong> My rb-tree optimization might not work everywhere.</p>
<p>On ARM big.LITTLE:</p>
<ul>
<li>Little cores: Smaller L1, lower associativity
-> More conflict misses -> My path array advantage shrinks</li>
<li>Big cores: Larger L1, higher associativity
-> My optimization still wins</li>
</ul>
<p><strong>Real hardware complexity:</strong> Cache-sensitive optimizations are microarchitecture-dependent. Beyond capacity and associativity, replacement policy, prefetchers, line size, VIPT/TLB behavior, and LLC organization can flip results.</p>
<p><strong>What I learned from Stratum:</strong>
You can't just benchmark on one machine and claim victory. Cache-sensitive code needs profiling across architectures. That's why I added Valgrind trace support—capture real workload patterns, then test against different cache configurations in Stratum.</p>
<p><strong>Best practice:</strong> Always validate on multiple cores (ARM big.LITTLE, x86, RISC-V) with hardware counters and trace-driven simulation.</p>
<hr />
<h2 id="honest-evaluation-of-stratum">Honest Evaluation of Stratum</h2>
<p><strong>What Stratum Does Well:</strong></p>
<ol>
<li>
<p><strong>Pre-Silicon Validation</strong>: Test cache configurations before committing to RTL design</p>
<ul>
<li>Example: Compare 2-level vs 3-level hierarchy trade-offs</li>
<li>Evidence-based decision making for SoC development</li>
</ul>
</li>
<li>
<p><strong>Research and Education</strong>:</p>
<ul>
<li>Compare replacement policies (LRU vs FIFO vs Random) with real traces</li>
<li>Teach cache concepts with observable, measurable behavior</li>
<li>Sensitivity analysis for associativity and block size</li>
</ul>
</li>
<li>
<p><strong>Workload Analysis</strong>: Profile real applications via Valgrind traces</p>
<ul>
<li>Identify cache-unfriendly access patterns in production code</li>
<li>Validate optimizations (like my rb-tree path array approach)</li>
</ul>
</li>
</ol>
<p><strong>What Stratum Doesn't Do:</strong></p>
<ul>
<li><strong>Mesh/NoC topologies</strong>: Requires runtime routing, breaks compile-time binding</li>
<li><strong>Non-inclusive/NUCA caches</strong>: Complex invalidation protocols not modeled</li>
<li><strong>Prefetchers</strong>: Only demand-driven accesses supported</li>
<li><strong>Multicore coherence</strong>: No MESI/MOESI protocol simulation</li>
<li><strong>Power modeling</strong>: No energy consumption tracking</li>
</ul>
<p><strong>Why These Limitations Are Acceptable:</strong></p>
<p>Stratum optimizes for <strong>clarity</strong> and <strong>rapid experimentation</strong>, not production-grade simulation. For detailed performance modeling, use gem5 or ZSim. For hardware verification, use RTL simulators.</p>
<p><strong>Design Pattern Applicability:</strong></p>
<p>This template-based hierarchy pattern isn't limited to CPU caches. The same approach applies to other memory hierarchies:</p>
<ul>
<li><strong>NPU/GPU memory</strong>: L1 texture cache -> L2 cache -> HBM (High Bandwidth Memory) -> Host DRAM</li>
<li><strong>Distributed systems</strong>: Redis cache -> Local DB -> Remote DB -> Cold storage</li>
<li><strong>CDN architecture</strong>: Edge cache -> Regional cache -> Origin server</li>
<li><strong>DMA transfers</strong>: On-chip buffer -> L2 -> Main memory -> Peripheral device</li>
</ul>
<p><strong>The key insight</strong>: Any hierarchical lookup with fixed topology at "compile time" (or deployment time) can use this zero-overhead template approach. The abstraction of "check current level, on miss delegate to next level" is universal.</p>
<hr />
<h2 id="try-it-yourself">Try It Yourself</h2>
<p><strong>Quick Start (5 minutes):</strong></p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">git</span></span><span class="z-meta z-function-call z-arguments z-shell"> clone https://github.com/TheCloudlet/Stratum.git</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-cd z-shell">cd</span></span><span class="z-meta z-function-call z-arguments z-shell"> Stratum</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cmake</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>B</span> build<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>DCMAKE_BUILD_TYPE</span><span class="z-keyword z-operator z-assignment z-option z-shell">=</span>Release</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cmake</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>build</span> build</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./build/bin/stratum <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Run default experiments</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></span></span></code></pre>
<p><strong>Dependencies:</strong> C++20 compiler (GCC 10+/Clang 11+/MSVC 2019+), CMake 3.20+</p>
<p><strong>Want to test your own traces?</strong></p>
<p>Visit <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Stratum">Stratum README</a> for instructions on:</p>
<ul>
<li>Generating Valgrind memory traces from your programs</li>
<li>Creating custom cache configurations</li>
<li>Using the Racket code generator (optional)</li>
</ul>
<p><strong>License:</strong> MIT - Fork it, modify it, break it, learn from it.</p>
<hr />
<h2 id="conclusion-from-interview-failure-to-first-principles">Conclusion: From Interview Failure to First Principles</h2>
<p>That interview question—"What causes cache-miss latency?"—exposed a gap between <strong>optimizing for cache behavior</strong> and <strong>understanding how caches actually work</strong>. I could write cache-friendly code by intuition, but I couldn't explain why it worked or predict when it wouldn't.</p>
<p>Building Stratum closed that gap.</p>
<p><strong>What this project taught me:</strong></p>
<ol>
<li>
<p><strong>Cache behavior is about access patterns, not just capacity</strong></p>
<ul>
<li>My rb-tree optimization worked because the path array had <strong>sequential access</strong> (L1-friendly)</li>
<li>The old parent-pointer approach had <strong>random heap access</strong> (L1-hostile)</li>
<li>Conflict misses matter more than capacity misses (associativity isn't just a spec number)</li>
</ul>
</li>
<li>
<p><strong>Template metaprogramming can mirror hardware constraints</strong></p>
<ul>
<li>Zero-cost abstraction: cache hierarchy as types, not virtual dispatch</li>
<li>Compile-time binding mirrors hardware reality (topology is fixed at synthesis)</li>
<li>Design constraints become compiler guarantees</li>
</ul>
</li>
<li>
<p><strong>Building tools teaches concepts better than reading alone</strong></p>
<ul>
<li>Implementing LRU forced me to understand why timestamp arrays beat linked lists</li>
<li>Exposed why associativity directly impacts latency</li>
<li>Debugging Valgrind traces revealed access patterns I'd never noticed in profilers</li>
</ul>
</li>
</ol>
<p><strong>What I still don't understand:</strong></p>
<ul>
<li>How replacement policies map to hardware counters (e.g., tree-PLRU in Intel)</li>
<li>Why real L1 caches use physical vs virtual indexing (TLB interactions)</li>
<li>How MESI/MOESI states work in multi-core scenarios (cache coherence protocols)</li>
</ul>
<p><strong>Next steps:</strong>
Reading "A Primer on Memory Consistency and Cache Coherence" and implementing a MOESI simulator to close these gaps.</p>
<hr />
<p><strong>Connect with me:</strong></p>
<ul>
<li>GitHub: <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Stratum">TheCloudlet/Stratum</a></li>
<li>Questions/feedback: Open an issue or PR</li>
<li>Interested in collaborating? Let's talk.</li>
</ul>
About: Yi-Ping Pan (Cloudlet)2026-01-01T00:00:00+00:002026-01-01T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/about/<p>Yi-Ping Pan is a software engineer with a deep interest in compilers, systems programming, and functional programming. He has worked on projects spanning compiler infrastructure (LLVM, MLIR), low-level systems in C++, and exploring the elegance of Haskell and functional paradigms.</p>
<p>He has contributed to open-source tooling and enjoys thinking about the intersection of language design and runtime performance — from how programs are parsed and optimized, to how they execute on real hardware.</p>
<p>Outside of engineering, he writes, tinkers with Emacs configurations, and occasionally gets lost in a good book.</p>
<p>He can be reached at <a href="mailto:[email protected]">[email protected]</a>, or found on <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet">GitHub</a> and <a rel="noopener" target="_blank" href="https://linkedin.com/in/yipingpan">LinkedIn</a>.</p>
Introducing Coogle: Bringing Haskell's Hoogle to C++2025-12-02T00:00:00+00:002025-12-02T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/project/coogle/<h2 id="why-i-started-this-project">Why I Started This Project?</h2>
<p>The story started in 2024, when I decided to review basic algorithms and data structures. I started a repository called "<a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/LeetcodeHero">From Zero to Leetcode Hero</a>." My intention was to learn and solve LeetCode problems using C++ and Haskell together.</p>
<p>Playing with both languages was fun. I discovered that tail recursion and iteration share almost the same control flow. The main difference lies in how the compiler (like LLVM) handles the stack frame prologue and epilogue, but that is a story for another time.</p>
<p>While exploring Haskell, I found an incredibly useful tool called <a rel="noopener" target="_blank" href="https://hoogle.haskell.org/">Hoogle</a>, which allows you to search for functions by their type signatures.</p>
<p>For example, if I want a function that takes a list of integers and returns a single integer (like a total or a count), I can search for <code>[Int] -> Int</code>. Hoogle will suggest relevant functions like <code>sum</code>, <code>product</code>, or <code>length</code>.</p>
<pre data-lang="Haskell" class="language-Haskell z-code"><code class="language-Haskell" data-lang="Haskell"><span class="z-source z-haskell"><span class="z-meta z-function z-type-declaration z-haskell"><span class="z-entity z-name z-function z-haskell">add</span> <span class="z-keyword z-other z-double-colon z-haskell">::</span> <span class="z-storage z-type z-haskell">Int</span> <span class="z-keyword z-other z-arrow z-haskell">-></span> <span class="z-storage z-type z-haskell">Int</span> <span class="z-keyword z-other z-arrow z-haskell">-></span> <span class="z-storage z-type z-haskell">Int</span>
</span></span><span class="z-source z-haskell"><span class="z-meta z-function z-type-declaration z-haskell"></span>add <span class="z-keyword z-operator z-haskell">=</span> undefined
</span></code></pre>
<ul>
<li><code>add</code> is the function name.</li>
<li><code>::</code> means "has type".</li>
<li><code>Int -> Int -> Int</code> means it takes two <code>Int</code> arguments and returns an <code>Int</code>.</li>
<li><code>add = undefined</code> means the implementation is still pending. We don't care about the body yet; the signature tells the whole story.</li>
</ul>
<p>The last type in the chain is the return type. So, a signature like <code>Int -> Char</code> means it takes an integer and returns a character.</p>
<p>This strict enforcement ensures that Hoogle can accurately match tools to your needs, and guarantees no "surprise actions" (side effects) modify values outside the function scope.</p>
<p>Honestly, when I first encountered Haskell types, I was completely confused by the syntax. It took me a while to fully translate my mindset, but then signature searching just clicked.</p>
<p><strong>Why is searching by type signature useful?</strong></p>
<p>It allows me to ignore all the operations and irrelevant details, and focus on how data flows through functions.</p>
<p>From a compiler engineer's perspective, looking at code this way is a superpower. It forces us to separate the <strong>Plumbing</strong> (how we move data) from the <strong>Logic</strong> (what we do with data). This separation is the essence of Data Abstraction taught in foundational texts like SICP: you focus on the behavior of functions at the highest level (the interface) without concerning yourself with their messy implementation details.</p>
<p>From my daily work experience, for example, when I need to handle getting a real name from an AST node, I often find myself searching for functions that convert <code>ASTNode</code> to <code>std::string</code>. This is especially true when dealing with unfamiliar third-party libraries or legacy codebases.</p>
<p>So I am looking for the function signature like:</p>
<pre data-lang="Haskell" class="language-Haskell z-code"><code class="language-Haskell" data-lang="Haskell"><span class="z-source z-haskell"><span class="z-meta z-function z-type-declaration z-haskell"><span class="z-entity z-name z-function z-haskell">foo</span> <span class="z-keyword z-other z-double-colon z-haskell">::</span> <span class="z-storage z-type z-haskell">ASTNode</span> <span class="z-keyword z-other z-arrow z-haskell">-></span> <span class="z-storage z-type z-haskell">String</span>
</span></span><span class="z-source z-haskell"><span class="z-meta z-function z-type-declaration z-haskell"></span>foo <span class="z-keyword z-operator z-haskell">=</span> undefined
</span></code></pre>
<p>or in C++ notation:</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++">std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">foo</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> ASTNode<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">node</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<blockquote>
<p>What if I could use this approach in my everyday VHDL compiler development? Searching for the input class and the output class would be incredibly useful.</p>
</blockquote>
<p>However, I couldn't find any similar tool for C/C++. That's why I created <strong>Coogle</strong> — a high-performance C++ command-line tool for searching C/C++ functions based on their type signatures, inspired by Hoogle from the Haskell ecosystem.</p>
<h2 id="the-semantic-gap-why-text-matching-fails-c-code">The Semantic Gap: Why Text Matching Fails C++ Code</h2>
<blockquote>
<p>Why not just use <code>grep</code> or <code>rg</code> to search for function names or patterns in header files?</p>
</blockquote>
<p>That is the first question everyone asks. The answer is simple: C++ is not a regular language, and source code is not just text—it is structure.</p>
<p>Consider this scenario: You want a function that takes an <code>ASTNode</code> and returns a <code>std::string</code>.</p>
<ul>
<li>
<p><strong>Regex approach</strong>: Writing <code>std::string\s+\w+\(\s*const\s+ASTNode&\s*\w+\s*\)</code></p>
</li>
<li>
<p><strong>The problem</strong>: This will miss many valid matches:</p>
<pre data-lang="c++" class="language-c++ z-code"><code class="language-c++" data-lang="c++"><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> The const is trailing
</span></span><span class="z-source z-c++">std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">getName</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++">ASTNode <span class="z-storage z-modifier z-c++">const</span><span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">node</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> There is a line break
</span></span><span class="z-source z-c++">std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string
</span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">getName</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-modifier z-c++">const</span> ASTNode<span class="z-keyword z-operator z-c">&</span> <span class="z-variable z-parameter z-c++">node</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
</li>
</ul>
<p>Sure, you might claim that you can write more complex regex to cover these cases. But do you really want to write that crazy regex, or would you rather use something like this?</p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Standard search</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./coogle</span></span><span class="z-meta z-function-call z-arguments z-shell"> . <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>std::string(ASTNode const&)<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> It understands references and whitespace automatically</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./coogle</span></span><span class="z-meta z-function-call z-arguments z-shell"> . <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>std::string ( ASTNode & )<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Wildcards supported</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./coogle</span></span><span class="z-meta z-function-call z-arguments z-shell"> . <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>std::string (*)<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<blockquote>
<p>"What if I wrap the regex in a Python script?"</p>
</blockquote>
<p>Fair point! However, here's the thing: writing a Python script to manage complex regexes is basically building your own fragile parser. Sure, it's more convenient than raw regex, but it doesn't actually solve the correctness problem.</p>
<p>Even with a script wrapping text-matching logic, you'll still run into two fundamental C++ features that trip up regex—but Coogle handles them naturally:</p>
<ol>
<li>
<p><strong>Type Aliases (The Semantic Gap)</strong> — Regex sees text; Coogle sees types.</p>
<ul>
<li>Code: <code>using NodeID = uint64_t;</code></li>
<li>Search: You want a function returning <code>uint64_t</code>.</li>
<li>Regex/Python: Will miss <code>NodeID get_id()</code> because <code>"NodeID" != "uint64_t"</code>.</li>
<li>Coogle: Understands the alias and finds the match.</li>
</ul>
</li>
<li>
<p><strong>Template Nesting (The "Greedy" Match Problem)</strong> — Regex struggles with balanced brackets and recursive structures.</p>
<ul>
<li>Code: <code>std::map<std::string, std::vector<int>></code></li>
<li>Regex: Writing a regex to correctly match nested <code><...></code> without accidentally matching the closing <code>></code> of a different template is a nightmare (and theoretically impossible for standard regex engines).</li>
<li>Coogle: Parses the AST structure correctly.</li>
</ul>
</li>
</ol>
<p>Therefore, if you want correctness and reliability, building a proper parser is the only way—and that is exactly what Coogle does.</p>
<h2 id="building-coogle">Building Coogle</h2>
<p>I'd like to thank Professor Ching-Han Chen (陳慶瀚) at National Central University, Taiwan, for teaching me the MIAT methodology. One of the most valuable lessons I learned was this: <strong>the best way to start any project is to clearly define your inputs and outputs first.</strong></p>
<p>So that's exactly where I began with Coogle.</p>
<h3 id="defining-the-interface">Defining the Interface</h3>
<p>The tool needed to be simple and intuitive. I envisioned a command-line interface that takes two arguments:</p>
<ol>
<li><strong>File or directory path</strong> — where to search</li>
<li><strong>Type signature</strong> — what to find</li>
</ol>
<p>The output should tell me exactly where the matching functions are:</p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Input: Search for a function that takes an int and returns an int</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./coogle</span></span><span class="z-meta z-function-call z-arguments z-shell"> . <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>int(int)<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Output: Show the file, line number, and function name</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">foo.cpp:42:addOne</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">bar.cpp:108:increment</span></span>
</span></code></pre>
<p>Simple enough. With the interface defined, I could now think about the internal architecture.</p>
<p>At the highest level, Coogle is just a function that transforms inputs into outputs:</p>
<pre class="z-code"><code><span class="z-text z-plain">┌──────────────────────────────┐
</span><span class="z-text z-plain">│ INPUT │
</span><span class="z-text z-plain">│ • C/C++ File Path │
</span><span class="z-text z-plain">│ • Search Signature String │
</span><span class="z-text z-plain">└──────────────┬───────────────┘
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> ┌───────────────┐
</span><span class="z-text z-plain"> │ Coogle │
</span><span class="z-text z-plain"> └───────┬───────┘
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain">┌──────────────┴───────────────┐
</span><span class="z-text z-plain">│ OUTPUT │
</span><span class="z-text z-plain">│ • file:line:functionName │
</span><span class="z-text z-plain">│ • file:line:functionName │
</span><span class="z-text z-plain">│ • ... │
</span><span class="z-text z-plain">└──────────────────────────────┘
</span></code></pre>
<p>I sketched out how data would flow through Coogle. Following the functional programming principle that <strong>everything is a function</strong>, I broke down the entire system into a pipeline where each stage has clearly defined inputs and outputs:</p>
<pre class="z-code"><code><span class="z-text z-plain">══════════════════════════════════════════════════════════
</span><span class="z-text z-plain"> Coogle Processing Pipeline
</span><span class="z-text z-plain">══════════════════════════════════════════════════════════
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">[C/C++ File Path] [Search Signature String]
</span><span class="z-text z-plain"> | |
</span><span class="z-text z-plain"> v v
</span><span class="z-text z-plain"> findSourceFiles() parseFunctionSignature()
</span><span class="z-text z-plain">(recursively discover) (custom parser)
</span><span class="z-text z-plain"> | |
</span><span class="z-text z-plain"> | Output: List<FilePath> | Output: Signature
</span><span class="z-text z-plain"> v v
</span><span class="z-text z-plain"> | |
</span><span class="z-text z-plain"> +-----------> foreach file <--------+
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> clang_parseTranslationUnit()
</span><span class="z-text z-plain"> (via libclang)
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> | Output: CXTranslationUnit (AST)
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> visitor()
</span><span class="z-text z-plain"> (CXCursor, Signature*) -> CXChildVisitResult
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> | Extract function declarations
</span><span class="z-text z-plain"> | Build actual signature
</span><span class="z-text z-plain"> isSigntaureMatch()
</span><span class="z-text z-plain"> | Output: List<Match>
</span><span class="z-text z-plain"> | where Match = {file, line, name, sig}
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> fmt::print()
</span><span class="z-text z-plain"> (List<Match>) -> IO
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> [Screen]
</span></code></pre>
<p>This looked simple enough, and I thought this would be a one-week project. I was completely wrong. I jumped into several potholes and had to crawl my way out. Let me share the key challenges I faced.</p>
<h2 id="pothole-1-understanding-libclang">Pothole 1: Understanding <code>libclang</code></h2>
<p>To be honest, I don't feel comfortable using tools or concepts that I don't understand well. My background is in Communication Engineering (Electrical Engineering), where we were trained in highly detailed mathematical modeling and derivation from first principles. We learned to question "black box" solutions and rigorously verify foundational theories before applying any abstraction—from Karnaugh maps to Gray codes, from probability theory to QPSK modulation.</p>
<p>My first "panic moment" came during COVID-19, when I volunteered to help my company build a pandemic survey web form. I chose the Django framework, but felt completely blind while implementing the website. It was like baking without understanding the chemistry—tweaking the amount of salt, yeast, and dough resting time, with no idea what was actually happening under the hood. Nevertheless, it eventually worked.</p>
<p>When working with <code>libclang</code>, I felt the same way. I knew I only needed to focus on the visitor function, but the syntax looked so unfamiliar—this didn't look like the familiar C/C++ code I was used to.</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++">CXChildVisitResult <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">visitor</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++">CXCursor <span class="z-variable z-parameter z-c++">Cursor</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-meta z-brackets z-c++"><span class="z-punctuation z-section z-brackets z-begin z-c++">[</span><span class="z-meta z-brackets z-c++"><span class="z-punctuation z-section z-brackets z-begin z-c++">[</span>maybe_unused<span class="z-punctuation z-section z-brackets z-end z-c++">]</span></span><span class="z-punctuation z-section z-brackets z-end z-c++">]</span></span> CXCursor <span class="z-variable z-parameter z-c++">Parent</span><span class="z-punctuation z-separator z-c++">,</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"> CXClientData <span class="z-variable z-parameter z-c++">ClientData</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-meta z-function z-c++"> </span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-type z-c">auto</span> <span class="z-keyword z-operator z-c">*</span>Ctx <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-keyword z-operator z-word z-cast z-c++">static_cast</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span>VisitorContext <span class="z-keyword z-operator z-c">*</span><span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span>ClientData<span class="z-punctuation z-section z-group z-end z-c++">)</span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> CXCursorKind Kind <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getCursorKind</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">Cursor</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-c++">if</span> <span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span>Kind <span class="z-keyword z-operator z-comparison z-c">==</span> CXCursor_FunctionDecl <span class="z-keyword z-operator z-arithmetic z-c">||</span> Kind <span class="z-keyword z-operator z-comparison z-c">==</span> CXCursor_CXXMethod<span class="z-punctuation z-section z-group z-end z-c++">)</span></span> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Build actual signature from libclang
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> coogle<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>SignatureStorage ActualStorage<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Get return type
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> CXType RetType <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getCursorResultType</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">Cursor</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-support z-function z-C99 z-c">assert</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">RetType<span class="z-punctuation z-accessor z-dot z-c++">.</span><span class="z-variable z-other z-readwrite z-member z-c++">kind</span> <span class="z-keyword z-operator z-comparison z-c">!=</span> CXType_Invalid <span class="z-keyword z-operator z-arithmetic z-c">&&</span>
</span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"> <span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>Invalid return type obtained from libclang<span class="z-punctuation z-definition z-string z-end z-c">"</span></span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> CXString RetSpelling <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getTypeSpelling</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">RetType</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view RetTypeSV <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getCString</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">RetSpelling</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view RetTypeInterned <span class="z-keyword z-operator z-assignment z-c">=</span> ActualStorage<span class="z-punctuation z-accessor z-dot z-c++">.</span><span class="z-meta z-method-call z-c++"><span class="z-variable z-function z-member z-c++">internString</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++">RetTypeSV</span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view RetTypeNorm <span class="z-keyword z-operator z-assignment z-c">=</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-function-call z-c++">coogle<span class="z-punctuation z-accessor z-double-colon z-c++">::</span><span class="z-variable z-function z-c++">normalizeType</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">ActualStorage<span class="z-punctuation z-accessor z-dot z-c++">.</span><span class="z-meta z-method-call z-c++"><span class="z-variable z-function z-member z-c++">arena</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method-call z-c++"></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-separator z-c++">,</span> RetTypeInterned</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_disposeString</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">RetSpelling</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span>...
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-block z-c++"> <span class="z-punctuation z-section z-block z-end z-c++">}</span></span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-function z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></code></pre>
<p>After some research, I realized that Clang might not be so different from the RTL compiler I'm used to. At minimum, I needed to get a grasp of the high-level code structure and how data flows—that would comfort me a bit.</p>
<p><strong>The full story of demystifying <code>libclang</code>—including the visitor pattern, AST traversal strategies, and how different languages handle compilation—deserves its own dedicated post.</strong> For now, let's treat this as a black box with a well-defined interface: input a file, output an AST.</p>
<p>Stay tuned for: <em>"Inside libclang: From Visitor Pattern to AST Mastery"</em></p>
<h2 id="pothole-2-the-std-string-and-template-matching-issue">Pothole 2: The <code>std::string</code> and Template Matching Issue</h2>
<p>At the beginning, I thought <code>std::string</code> was a real type. (Yes, this is how naive I was—despite working with professional C++ programmers and being tortured by <code>std::string</code> issues regularly, I didn't know this fundamental fact). So when I expected to find <code>std::string</code> but actually got <code>std::basic_string<...></code>, I was very confused.</p>
<p>I documented my learning journey in detail here:
<a href="https://thecloudlet.github.io/blog/project/coogle/">Back to Basics: From C char to string_view (Notes from building Coogle)</a></p>
<p><strong>The fix: Canonicalization (The Search for Truth)</strong></p>
<p>To solve this, I couldn't just store the function signature as it appears in the source code. I had to store the Canonical Type.</p>
<p>Clang provides <code>GetCanonicalType()</code>. This strips away all the "sugar"—typedefs, type aliases, and redundant qualifiers. This process mirrors the Lisp philosophy of Uniformity (Homogeneity). Just as Lisp treats code as data (S-expressions), canonicalization treats disparate C++ type aliases as a single, uniform data structure. By stripping away the syntactic sugar, we reveal the underlying mathematical truth of the function's type.</p>
<h2 id="pothole-3-the-translation-unit-trap-the-flood-of-headers">Pothole 3: The Translation Unit Trap (The Flood of Headers)</h2>
<p>This was the funniest bug.</p>
<p>When I searched for a simple signature like <code>void(void*)</code> inside <code>main.cpp</code>, I expected to find my own utility functions. Instead, Coogle vomited thousands of results: internal functions from <code>libc++</code>, <code>std::vector</code> helpers, and obscure symbols from the system SDK.</p>
<p><strong>The Root Cause:</strong> I forgot how C++ compilers actually work. <code>libclang</code> operates on a <strong>Translation Unit (TU)</strong>. When reading <code>#include <iostream></code>, the preprocessor copy-pastes the entire content of <code>iostream</code> (and everything it includes) into the file. So, to the parser, those <code>std::</code> functions are just as much "part of the file" as <code>main()</code>.</p>
<p><strong>The Fix: Defense in Depth (Double Filtering)</strong></p>
<p>To address this, I realized I needed a rigorous filtering strategy to separate "my code" from "library code." I implemented a two-layer filter:</p>
<p><strong>Layer 1: The Safety Net (System Header Check)</strong></p>
<p>First, I ask Clang if the cursor is explicitly inside a system header. This handles cases where file paths might be ambiguous but the compiler knows it's a library.</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++">CXSourceLocation location <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getCursorLocation</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">cursor</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-keyword z-control z-c++">if</span> <span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_Location_isInSystemHeader</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">location</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span>
</span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> CXChildVisit_Continue<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Layer 1: Drop system headers immediately
</span></span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span>
</span></code></pre>
<p><strong>Layer 2: The Strict Scope (File Provenance Check)</strong></p>
<p>Second, even if it's not a system header, I don't want to see results from other user headers included in the TU unless I explicitly asked for them. I verify that the cursor physically resides in the target file.</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++">CXFile File<span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getSpellingLocation</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">location<span class="z-punctuation z-separator z-c++">,</span> <span class="z-keyword z-operator z-c">&</span>File<span class="z-punctuation z-separator z-c++">,</span> <span class="z-keyword z-operator z-c">&</span>Line<span class="z-punctuation z-separator z-c++">,</span> <span class="z-keyword z-operator z-c">&</span>Column<span class="z-punctuation z-separator z-c++">,</span> <span class="z-constant z-language z-c++">nullptr</span></span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">CXString FileName <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getFileName</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">File</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-storage z-modifier z-c++">const</span> <span class="z-storage z-type z-c">char</span> <span class="z-keyword z-operator z-c++">*</span>FileNameStr <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-meta z-function-call z-c++"><span class="z-variable z-function z-c++">clang_getCString</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++">FileName</span></span><span class="z-meta z-function-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++">
</span><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Layer 2: Only show results from the file we're explicitly parsing
</span></span><span class="z-source z-c++"><span class="z-keyword z-control z-c++">if</span> <span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span><span class="z-keyword z-operator z-arithmetic z-c">!</span>FileNameStr <span class="z-keyword z-operator z-arithmetic z-c">||</span> Ctx<span class="z-punctuation z-accessor z-arrow z-c++">-></span><span class="z-variable z-other z-readwrite z-member z-c++">CurrentFile</span> <span class="z-keyword z-operator z-comparison z-c">!=</span> FileNameStr<span class="z-punctuation z-section z-group z-end z-c++">)</span></span> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span>
</span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"> <span class="z-keyword z-control z-flow z-return z-c++">return</span> CXChildVisit_Continue<span class="z-punctuation z-terminator z-c++">;</span>
</span></span><span class="z-source z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span>
</span></code></pre>
<p>By combining <strong>System Header Filtering</strong> (Blacklist) with <strong>Explicit File Matching</strong> (Whitelist), I achieved zero noise. Coogle now respects the user's intent: "Search this file, and only this file."</p>
<h2 id="pothole-4-performance-issue-when-dealing-with-large-codebases">Pothole 4: Performance Issue When Dealing With Large Codebases</h2>
<p>Running Coogle on a small "Hello World" project was instant. But when I unleashed it on a massive codebase like LLVM itself? It choked. It got stuck for over 40 minutes, eating up CPU cycles like there was no tomorrow.</p>
<p>Profiling revealed two things:</p>
<ol>
<li>Too many things are being parsed</li>
<li><code>std::string</code> memory allocating issue</li>
</ol>
<h3 id="issue-1-the-lazy-parser-strategy">Issue 1: The "Lazy Parser" Strategy</h3>
<p>By default, Clang behaves like a compiler—it wants to build a perfect, complete AST. It resolves every <code>#include</code>, parses every template inside <code><vector></code>, and validates every function body.</p>
<p><strong>The Epiphany: Coogle is a Search Engine, not a Compiler.</strong></p>
<p>I don't need to generate binary code; I just need to read the <strong>Signatures</strong>. I realized I could aggressively turn off Clang's "heavy lifting" features to trade correctness for speed.</p>
<p>I decided to strip down the parsing process to the bare minimum by injecting specific compiler flags and options.</p>
<p><strong>1. Cutting the Cord to System Headers</strong></p>
<p>Since I already implemented the "File Filtering" logic in Pothole 3, parsing system headers was now purely wasted time.</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++">ArgsVec<span class="z-punctuation z-accessor z-dot z-c++">.</span><span class="z-meta z-method-call z-c++"><span class="z-variable z-function z-member z-c++">push_back</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>-nostdinc<span class="z-punctuation z-definition z-string z-end z-c">"</span></span></span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Stop searching standard system directories
</span></span><span class="z-source z-c++">ArgsVec<span class="z-punctuation z-accessor z-dot z-c++">.</span><span class="z-meta z-method-call z-c++"><span class="z-variable z-function z-member z-c++">push_back</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-string z-quoted z-double z-c"><span class="z-punctuation z-definition z-string z-begin z-c">"</span>-nostdinc++<span class="z-punctuation z-definition z-string z-end z-c">"</span></span></span></span><span class="z-meta z-method-call z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Stop searching standard C++ directories
</span></span></code></pre>
<p><strong>The Logic:</strong> This tells Clang: "Ignore the standard library."</p>
<p><strong>The Gain:</strong> We skip parsing thousands of lines from <code><iostream></code>, <code><vector></code>, and <code><string></code>. The parser no longer wastes time building ASTs for the entire STL ecosystem.</p>
<p><strong>2. Skipping the Implementation Details</strong></p>
<p>I don't care how a function is implemented; I only care what it takes and returns.</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++"><span class="z-storage z-type z-c">unsigned</span> Options <span class="z-keyword z-operator z-assignment z-c">=</span> CXTranslationUnit_SkipFunctionBodies <span class="z-keyword z-operator z-arithmetic z-c">|</span> <span class="z-keyword z-operator z-variadic z-c">...</span>
</span></code></pre>
<p><strong>The Logic:</strong> <code>CXTranslationUnit_SkipFunctionBodies</code>.</p>
<p><strong>The Gain:</strong> <code>libclang</code> completely ignores the code inside <code>func { ... }</code>. This is huge. For a 1000-line function, we now parse only the first line.</p>
<p><strong>3. Tolerating Imperfection</strong></p>
<p>Since I cut off the system headers, the code is technically "broken" (types like <code>std::string</code> are now undefined symbols to the parser).</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++"><span class="z-storage z-type z-c">unsigned</span> Options <span class="z-keyword z-operator z-assignment z-c">=</span> <span class="z-keyword z-operator z-variadic z-c">...</span> <span class="z-keyword z-operator z-arithmetic z-c">|</span> CXTranslationUnit_Incomplete<span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p><strong>The Logic:</strong> <code>CXTranslationUnit_Incomplete</code>.</p>
<p><strong>The Gain:</strong> This tells Clang: "It's okay if you find missing symbols or headers. Don't error out; just give me what you have." This makes the parser resilient to my aggressive optimization strategy.</p>
<h3 id="issue-2-string-allocation-overhead-memory-pool-optimization">Issue 2: String Allocation Overhead (Memory Pool Optimization)</h3>
<p>While fixing the parser flags solved the CPU bottleneck, I noticed the memory usage was still alarmingly high. This is where my previous deep dive into <code>std::string</code> and SSO (from Pothole 2) came back to save me.</p>
<p>The old <code>Signature</code> structure I used looked like this:</p>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-storage z-type z-c++">struct</span> </span><span class="z-meta z-struct z-c++"><span class="z-entity z-name z-struct z-c++">Signature</span></span><span class="z-meta z-struct z-c++"> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string RetType<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Original return type
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string RetTypeNorm<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Normalized return type
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>vector<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string<span class="z-punctuation z-section z-generic z-end z-c++">></span> ArgType<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Original argument types
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>vector<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string<span class="z-punctuation z-section z-generic z-end z-c++">></span> ArgTypeNorm<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Normalized argument types
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>For every matched signature, I needed at least 4 heap allocations (<code>void(void)</code>), which is obviously very expensive.</p>
<p>To solve this, I implemented a String Interning mechanism backed by a linear memory arena:</p>
<ul>
<li><strong>Storage</strong>: A central <code>std::vector<char></code> (or similar deque) acts as a persistent string pool.</li>
<li><strong>Reference</strong>: Instead of holding <code>std::string</code> (which owns memory), my AST nodes now hold <code>std::string_view</code> (which borrows memory).</li>
<li><strong>Deduplication</strong>: Before storing a type name, I check if it exists in the pool. If yes, I return a view to the existing data.</li>
</ul>
<pre data-lang="C++" class="language-C++ z-code"><code class="language-C++" data-lang="C++"><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-storage z-type z-c++">struct</span> </span><span class="z-meta z-struct z-c++"><span class="z-entity z-name z-struct z-c++">Signature</span></span><span class="z-meta z-struct z-c++"> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view RetType<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Original return type
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view RetTypeNorm<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Normalized return type
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> span<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view<span class="z-punctuation z-section z-generic z-end z-c++">></span> ArgTypes<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Original argument types
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> span<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string_view<span class="z-punctuation z-section z-generic z-end z-c++">></span> ArgTypesNorm<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Normalized argument types
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span> <span class="z-meta z-attribute z-c++"><span class="z-storage z-modifier z-c++">__attribute__</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">((</span></span></span><span class="z-meta z-attribute z-c++"><span class="z-meta z-group z-c++">packed</span><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-end z-c++">))</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>By using <code>string_view</code> pointing to a stable memory arena, we are effectively enforcing Immutability. Once a string is interned, it never changes. This immutability eliminates the need for defensive copying and complex ownership management, much like how functional languages handle data structures.</p>
<p>Initially, I considered caching all <code>int</code> strings in a hash table to avoid duplicates, but I found that searching the table with string comparisons and checking whether the string is preallocated was actually slower than directly writing to the string pool.</p>
<p><strong>The Result</strong>: This shifted the architecture from "Object-Oriented Ownership" (everyone owns their strings) to "Data-Oriented Sharing". Memory footprint dropped significantly, and more importantly, cache locality improved because related string data was now packed tightly in the arena rather than scattered across the heap.</p>
<p>In the end, I successfully reduced the whole AST parsing time for LLVM from being completely stuck (40+ minutes) down to just 6 minutes. While still not blazing fast, it's at least workable. If we want it even faster, we could integrate <code>compile_commands.json</code> or customize JSON file dumps, so we parse once and can check different signatures in split seconds.</p>
<h2 id="conclusion-the-takeaway">Conclusion (The Takeaway)</h2>
<p>Building Coogle wasn't just about making a search tool—it was a journey of demystifying compilers and embracing the power of <strong>Plumbing vs. Logic</strong> separation.</p>
<p>I started with fear: fear of <code>libclang</code>, fear of the "black box," fear of diving into unfamiliar territory. But by applying the principles I learned from both SICP and practical engineering—<strong>Data Abstraction</strong> (separating interface from implementation), <strong>Canonicalization</strong> (normalizing types to their single source of truth), and <strong>Lazy Evaluation</strong> (only parsing what we need)—I managed to tame the beast.</p>
<p>The four potholes I encountered taught me valuable lessons:</p>
<ol>
<li><strong>Understanding <code>libclang</code></strong> — Sometimes you don't need to understand everything; treating components as black boxes with clear interfaces is okay.</li>
<li><strong>Template Matching</strong> — <code>std::string</code> isn't what I thought it was, and understanding the underlying type system is crucial.</li>
<li><strong>Translation Unit Filtering</strong> — Defense in depth with layered filtering (system headers + file provenance) achieves zero noise.</li>
<li><strong>Performance Optimization</strong> — Aggressive optimization strategies (lazy parsing + memory pools) can reduce 40+ minutes to 6 minutes.</li>
</ol>
<p>Now, Coogle serves as my daily driver for navigating complex C++ codebases. It's not perfect, but it's built on a solid understanding of how C++ compilers actually work under the hood.</p>
<p><strong>What's Next?</strong></p>
<ul>
<li>Integrate <code>compile_commands.json</code> for persistent AST caching</li>
<li>Consider building a language server protocol (LSP) extension</li>
</ul>
<p>If you're interested in trying Coogle, check out the repository: <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Coogle">github.com/TheCloudlet/Coogle</a></p>
<h2 id="appendix-architecture-diagram">Appendix: Architecture Diagram</h2>
<pre class="z-code"><code><span class="z-text z-plain">+---------------------+ +----------------------+
</span><span class="z-text z-plain">| C/C++ Source | | User Query |
</span><span class="z-text z-plain">| (File / Project) | | "int(int)" |
</span><span class="z-text z-plain">+----------+----------+ +-----------+----------+
</span><span class="z-text z-plain"> | |
</span><span class="z-text z-plain"> v v
</span><span class="z-text z-plain">+----------+------------------------------+----------+
</span><span class="z-text z-plain">| Coogle Frontend |
</span><span class="z-text z-plain">| |
</span><span class="z-text z-plain">| [ libclang Parser ] <---- (Translation Unit) |
</span><span class="z-text z-plain">| | |
</span><span class="z-text z-plain">| v |
</span><span class="z-text z-plain">| (Clang AST) |
</span><span class="z-text z-plain">| | |
</span><span class="z-text z-plain">| v |
</span><span class="z-text z-plain">| < RecursiveASTVisitor > --+ |
</span><span class="z-text z-plain">| | | |
</span><span class="z-text z-plain">| | (Visit) | (Filter) |
</span><span class="z-text z-plain">| v v |
</span><span class="z-text z-plain">| [ FunctionDecl ] [ System Header Check ] |
</span><span class="z-text z-plain">+-----------+----------------------------------------+
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain">+-----------+----------------------------------------+
</span><span class="z-text z-plain">| Coogle Backend |
</span><span class="z-text z-plain">| |
</span><span class="z-text z-plain">| 1. Type Extraction (Get Raw Signature) |
</span><span class="z-text z-plain">| "NodeID get(int)" |
</span><span class="z-text z-plain">| | |
</span><span class="z-text z-plain">| v |
</span><span class="z-text z-plain">| 2. Canonicalization & Matching |
</span><span class="z-text z-plain">| | |
</span><span class="z-text z-plain">| v |
</span><span class="z-text z-plain">| 3. String Interning (Memory Pool) |
</span><span class="z-text z-plain">| Store unique signature via string_view |
</span><span class="z-text z-plain">+-----------+----------------------------------------+
</span><span class="z-text z-plain"> |
</span><span class="z-text z-plain"> v
</span><span class="z-text z-plain"> +-------+-------+
</span><span class="z-text z-plain"> | Output |
</span><span class="z-text z-plain"> +---------------+
</span></code></pre>
Back to Basics: From C char to string_view (Notes from building Coogle)2025-11-20T00:00:00+00:002025-11-20T00:00:00+00:00
Yi-Ping Pan (Cloudlet)
https://thecloudlet.github.io/blog/cpp/cpp-string/<h2 id="background">Background</h2>
<p>When I was implementing <a rel="noopener" target="_blank" href="https://github.com/TheCloudlet/Coogle">Coogle</a>, I discovered something bizarre about C++ strings. That is why I wrote this note to summarize what I learned about C++ strings.</p>
<p>Coogle is a search engine that I implemented in C++. It is designed to quickly and efficiently find function signatures in large codebases—especially useful for 30-year-old C++ codebases that were migrated from C and mixed with C++11/14/17/20 features like my lovely everyday job.</p>
<p>Since I like Haskell and pure functions, I tried to pay tribute to Hoogle, a Haskell function search engine. The idea is to be able to search for function signatures in C/C++ and get back a list of matching functions.</p>
<p>First step, support simple types like: <code>int</code>, <code>char</code>. Okay that is not too hard.</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> This is my test file
</span></span><span class="z-source z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++"><span class="z-entity z-name z-function z-c++">add</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-variable z-parameter z-c++">a</span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-type z-c">int</span> <span class="z-variable z-parameter z-c++">b</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span><span class="z-source z-c++"><span class="z-storage z-type z-c">int</span> <span class="z-meta z-function z-c++"><span class="z-meta z-toc-list z-full-identifier z-c++">std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span><span class="z-entity z-name z-function z-c++">string</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++"><span class="z-punctuation z-section z-group z-begin z-c++">(</span></span></span><span class="z-meta z-function z-parameters z-c++"><span class="z-meta z-group z-c++">std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>string <span class="z-variable z-parameter z-c++">s</span><span class="z-punctuation z-section z-group z-end z-c++">)</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>I expect when I search "int(int, int)", Coogle should be able to find this signature quickly. So, implemented that, and it worked fine.</p>
<p>But, when I try to search "std::string(std::string)" to find function signatures that take and return <code>std::string</code>, I found that Coogle could not find any results, even though there are many such functions in my test codebase.</p>
<p>This is really a WTF moment for me.</p>
<p>I couldn't understand why and got stuck for a while, then I printed out the full AST tree using libclang. That's when I saw something confusing: <code>std::basic_string<char, std::char_traits<char>, std::allocator<char>></code> showed up instead of <code>std::string</code>.</p>
<p>What the heck is a trait and allocator? I don't understand C++ strings at all. I thought it was supposed to be just <code>std::string</code>!</p>
<p>Well... So I dug deeper and read more about C++ strings.</p>
<p>My initial thinking was to ask LLMs about C++ strings and designs. But since I work on legacy C and C++ code, and lots of my algorithms are implemented in C (for milking out best performance), I decided to first understand C strings and character types deeply.</p>
<p>As Jserv always says in <a rel="noopener" target="_blank" href="https://hackmd.io/@sysprog/c-programming">C Programming series</a>:</p>
<blockquote>
<p>"Be honest with yourself. You don't know C." <br />
— Jserv Huang</p>
</blockquote>
<p>So, before we tackle std::string, let's be honest with ourselves and look at the chaos of char. Everything below is based on my research using different resources, including language standards, articles.</p>
<p>That may not be 100% accurate, but I tried my best to summarize what I learned. If you find any mistakes, please kindly point them out to me.</p>
<hr />
<h2 id="part-1-c99-chars-and-strings">PART 1: C99 chars and strings</h2>
<p>Starting with my conclusion.</p>
<blockquote>
<p>In C, char doesn't really mean 'character'. It just means the smallest addressable unit of memory (a byte). ASCII or other encodings just happen to fit into this unit. <br />
— The Cloudlet</p>
</blockquote>
<p>Or maybe through the lens of VHDL we can see more clearly:</p>
<pre class="z-code"><code><span class="z-text z-plain">library IEEE;
</span><span class="z-text z-plain">use IEEE.std_logic_1164.all;
</span><span class="z-text z-plain">use IEEE.numeric_std.all;
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">package C_Types is
</span><span class="z-text z-plain"> -- Defined in <limits.h>
</span><span class="z-text z-plain"> -- Most likely 8, but could be 16 or 32 on DSPs
</span><span class="z-text z-plain"> constant CHAR_BIT : integer := 8;
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> -- "unsigned char" -> 0 to 255
</span><span class="z-text z-plain"> subtype c_unsigned_char is unsigned(CHAR_BIT-1 downto 0);
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> -- "signed char" -> -128 to 127
</span><span class="z-text z-plain"> subtype c_signed_char is signed(CHAR_BIT-1 downto 0);
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> -- Compiler defines "__CHAR_UNSIGNED__" on platforms like ARM.
</span><span class="z-text z-plain"> -- Set by Arch (x86=false, ARM=true)
</span><span class="z-text z-plain"> constant CHAR_IS_UNSIGNED : boolean := ???;
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> alias c_char is
</span><span class="z-text z-plain"> case CHAR_IS_UNSIGNED generate
</span><span class="z-text z-plain"> when false => c_signed_char; -- x86 (Default)
</span><span class="z-text z-plain"> when true => c_unsigned_char; -- ARM
</span><span class="z-text z-plain"> end generate;
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">end package C_Types;
</span></code></pre>
<p>VHDL is chosen here specifically because of its strictness. It forces us to see the distinction between "Raw Bits" (std_logic_vector), "Unsigned Math" (unsigned), and "Signed Math" (signed).</p>
<h3 id="quick-c99-char-summary">Quick C99 Char Summary</h3>
<p>Please refer to ISO C99 standard §6.2.5 for more details.</p>
<ol>
<li>
<p>The "Three Types" Rule: char, signed char, and unsigned char are three distinct types. The plain char is a wild card—it behaves like signed on x86 but unsigned on ARM.</p>
</li>
<li>
<p>Usage Rule:</p>
<ul>
<li>Use <code>char</code> ONLY for text strings.</li>
<li>Use <code>signed char</code> for small value calculation</li>
<li>Use <code>unsigned char</code> (or uint8_t) for binary data.</li>
</ul>
</li>
</ol>
<h3 id="c-strings-philosophy-recap">C Strings philosophy recap</h3>
<p>This is CS101 stuff, I don't think we need to talk too much deeply about it. Just doing a quick recap and why C strings are designed this way.</p>
<p><strong>The Core Mechanism</strong></p>
<ul>
<li><code>\0</code> is the null terminator.</li>
<li>Operations like <code>strlen</code>, <code>strcpy</code>, <code>strcmp</code> are linear scan <code>O(n)</code>.</li>
</ul>
<pre class="z-code"><code><span class="z-text z-plain">┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
</span><span class="z-text z-plain">│ H │ e │ l │ l │ o │ , │ │ W │ o │ r │ l │ d │ ! │ \0 │
</span><span class="z-text z-plain">├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
</span><span class="z-text z-plain">│ 72 │ 101 │ 108 │ 108 │ 111 │ 44 │ 32 │ 87 │ 111 │ 114 │ 108 │ 100 │ 33 │ 0 │
</span><span class="z-text z-plain">├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
</span><span class="z-text z-plain">│0x48 │0x65 │0x6C │0x6C │0x6F │0x2C │0x20 │0x57 │0x6F │0x72 │0x6C │0x64 │0x21 │0x00 │
</span><span class="z-text z-plain">└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
</span><span class="z-text z-plain"> [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> ^ ^
</span><span class="z-text z-plain"> | |
</span><span class="z-text z-plain"> str (pointer to first character) Null terminator ('\0')
</span></code></pre>
<p><strong>The Why of Null-Terminated Strings</strong></p>
<p>I have searched around for why C strings are designed this way. What I can find is at 1970, there are 2 typical ways to represent strings in memory:</p>
<ol>
<li>Pascal style: Length-prefixed strings (1 byte length + data)</li>
<li>C style: Null-terminated strings (data + '\0')</li>
</ol>
<p>But I haven’t found a definitive answer to why C chose null-terminated strings over length-prefixed strings.
Instead, I stumbled upon some interesting articles pointing out that C strings are, frankly, a pain. Check out Joel Spolsky’s classic post, <a rel="noopener" target="_blank" href="https://www.joelonsoftware.com/2001/12/11/back-to-basics/">Back to Basics</a>, where he describes the performance problems with strcat and how some developers resorted to “F***ed-Up Strings” (Pascal-style strings in C) as a workaround for the shortcomings of null-terminated strings.</p>
<p>People have different implementations for string handling in C, just like the Linux kernel has its qstr implementation under <code>include/linux/dcache.h</code> when dealing with filesystem paths. Google's <a rel="noopener" target="_blank" href="https://github.com/abseil">Abseil</a> library also introduced <code>StringPiece</code> (which later evolved into string_view in C++17). The commonly accepted approach across these implementations is to keep a length field along with the char pointer to avoid repeated strlen calls.</p>
<p>Therefore, C++'s <code>std::string</code> is not the only solution to C string problems—but understanding its design can be helpful for compiler, library, and systems programmers.</p>
<p><strong>Update</strong>: Community Insights (2025-11-26)</p>
<p>For the nul-terminated string design, some kind comments on Reddit pointed out that:</p>
<blockquote>
<p>Because in ye olden days a one byte length wasn't large enough to represent long strings, but using more bytes for the length field was inefficient for short strings. And I guess no one wanted the complexity of a variable length encoding for the length prefix. And so we live with the pain of null terminated strings today. <br />
— u/Kered13</p>
</blockquote>
<p>Also, thanks to u/vip17 for pointing out these excellent resources for diving deeper into the history:</p>
<blockquote>
<p>There are lots of places that explain the history of C's null-terminated strings: \</p>
<ul>
<li><a rel="noopener" target="_blank" href="https://retrocomputing.stackexchange.com/questions/24855/null-terminated-strings-on-the-pdp-7">Null-terminated strings on the PDP-7 (retrocomputing.stackexchange)</a> \</li>
<li><a rel="noopener" target="_blank" href="https://www.reddit.com/r/cpp_questions/comments/qlzho1/why_did_c_and_linux_api_use_nullterminated_strings/">Why did C and Linux API use null-terminated strings? (reddit r/cpp_questions)</a> \</li>
<li><a rel="noopener" target="_blank" href="https://stackoverflow.com/questions/2221304/why-do-strings-in-c-need-to-be-null-terminated">Why do strings in C need to be null terminated? (stack overflow)</a></li>
</ul>
</blockquote>
<p>It seems the struggle between efficiency and simplicity has been with us since the dawn of C.</p>
<p><strong>Update</strong>: The Memory's Perspective (2025-11-27)</p>
<p>Although we say <code>byte</code> is the smallest addressable unit of memory, I found out that in DRAM chips, the smallest unit that can be read/written is actually a <code>word</code> (typically 4 or 8 bytes). The concept of <code>byte</code> is more of a logical abstraction provided by the CPU architecture or ISA.</p>
<p>When the CPU accesses memory, it reads/writes in chunks of words. The memory controller then breaks these words down into bytes for the CPU to process. This means that even though we work with bytes in our code, under the hood, the memory system operates on larger units.</p>
<p>In the following reference, there is a detailed explanation of how memory systems work, including the concepts of cache lines, memory hierarchy, and how data is fetched from DRAM to CPU caches.</p>
<p>Reference: <a rel="noopener" target="_blank" href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">Paper: What Every Programmer Should Know About Memory</a></p>
<p>So, the answer to "Why C char are designed this way?" Is to fit the basic unit of memory access in <strong>ISA/CPU architecture</strong> while providing a simple abstraction for programmers to work with text data.</p>
<hr />
<h2 id="part-2-std-string-and-the-template-monster">PART 2: std::string and the Template Monster</h2>
<p>Okay, back to Coogle's bug. I was searching for <code>std::string(std::string)</code> in the AST, but I see zero results. I then printed out the full type name stored in the AST using <code>libclang</code> and there it was, the monster type name:</p>
<p><code>std::basic_string<char, std::char_traits<char>, std::allocator<char>></code></p>
<p>**</p>
<h3 id="the-typedef-illusion">The Typedef Illusion</h3>
<blockquote>
<p><code>std::string</code> is just a typedef (alias) for a specific instantiation of the <code>std::basic_string</code> template class.</p>
</blockquote>
<p>The search failed because I was looking for a name that doesn't exist in the type system. <code>std::string</code> is syntactic sugar for human programmers. The compiler sees only <code>basic_string<char></code> and its full template instantiation with default arguments.</p>
<p><code>basic_string</code> actually is a template with three parameters, two of which have defaults:</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-meta z-template z-c++"><span class="z-storage z-type z-template z-c++">template</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span></span><span class="z-meta z-template z-c++">
</span></span><span class="z-source z-c++"><span class="z-meta z-template z-c++"> <span class="z-storage z-type z-c++">class</span> CharT<span class="z-punctuation z-separator z-c++">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Character type (char, wchar_t, etc.)
</span></span></span><span class="z-source z-c++"><span class="z-meta z-template z-c++"> <span class="z-storage z-type z-c++">class</span> Traits <span class="z-keyword z-operator z-assignment z-c">=</span> char_traits<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>CharT<span class="z-punctuation z-section z-generic z-end z-c++">></span><span class="z-punctuation z-separator z-c++">,</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Character operations (comparison, copying)
</span></span></span><span class="z-source z-c++"><span class="z-meta z-template z-c++"> <span class="z-storage z-type z-c++">class</span> Allocator <span class="z-keyword z-operator z-assignment z-c">=</span> allocator<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>CharT<span class="z-punctuation z-section z-generic z-end z-c++">></span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Memory management strategy
</span></span></span><span class="z-source z-c++"><span class="z-meta z-template z-c++"></span><span class="z-meta z-template z-c++"><span class="z-punctuation z-section z-generic z-end z-c++">></span></span>
</span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-storage z-type z-c++">class</span> <span class="z-entity z-name z-class z-forward-decl z-c++">basic_string</span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>Knowing this aliasing can be enough to solve my issue, but maybe understanding each parameter will help improving our programming skills.</p>
<p><strong>Deconstructing the monster</strong></p>
<ul>
<li>
<p><strong>CharT</strong>:</p>
<ul>
<li>Defines what kind of character the string holds—<code>char</code> for ASCII/UTF-8, <code>wchar_t</code> for wide characters, <code>char16_t</code> for UTF-16, etc. Simple enough.</li>
</ul>
</li>
<li>
<p><strong>Traits</strong>:</p>
<ul>
<li>
<p>Hmm... is more interesting. Accoding to <a rel="noopener" target="_blank" href="https://en.cppreference.com/w/cpp/string/char_traits">cppreference:char_traits</a>:</p>
<blockquote>
<p>The char_traits class is a traits class template that abstracts basic character and string operations for a given character type. The defined operation set is such that generic algorithms almost always can be implemented in terms of it.</p>
</blockquote>
<p>It means we can define custom behaviors for character operations like comparison, copying, length calculation, etc. For example, VHDL files are case-insensitive, so we could define a <code>case_insensitive_char_traits</code> that overrides comparison functions to ignore case differences.</p>
<p>Cool!</p>
</li>
</ul>
</li>
<li>
<p><strong>Allocator</strong>:</p>
<ul>
<li>This is a powerful tool controls memory management. The default uses <code>new</code>/<code>delete</code>, but you can plug in custom allocators for specific memory strategies—arena allocation, pool allocation, debugging allocators that track leaks, etc. This is what C++17's PMR (Polymorphic Memory Resources) builds on, which we'll cover in PART 3.</li>
</ul>
</li>
</ul>
<blockquote>
<p><code>std::basic_string</code> provides a flexible, reusable string class that can adapt to different character types, behaviors, and memory strategies.</p>
</blockquote>
<p><strong>Quick summary diagram:</strong></p>
<pre class="z-code"><code><span class="z-text z-plain"> Template Class (defined once)
</span><span class="z-text z-plain"> ┌────────────────────────────────────────────────────────────┐
</span><span class="z-text z-plain"> │ template<class CharT, class Traits, class Allocator> │
</span><span class="z-text z-plain"> │ class basic_string { │
</span><span class="z-text z-plain"> │ CharT* data_; │
</span><span class="z-text z-plain"> │ size_t size_; │
</span><span class="z-text z-plain"> │ size_t capacity_; │
</span><span class="z-text z-plain"> │ // ... methods ... │
</span><span class="z-text z-plain"> │ }; │
</span><span class="z-text z-plain"> └────────────────────────────────────────────────────────────┘
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> │ Template Instantiation
</span><span class="z-text z-plain"> │
</span><span class="z-text z-plain"> ┌────────────────────────┼────────────────────────┐
</span><span class="z-text z-plain"> │ │ │
</span><span class="z-text z-plain"> ▼ ▼ ▼
</span><span class="z-text z-plain">┌─────────────────┐ ┌─────────────────┐ ┌──────────────────────┐
</span><span class="z-text z-plain">│ std::string │ │ std::wstring │ │ std::pmr::u16string │
</span><span class="z-text z-plain">├─────────────────┤ ├─────────────────┤ ├──────────────────────┤
</span><span class="z-text z-plain">│ CharT: │ │ CharT: │ │ CharT: │
</span><span class="z-text z-plain">│ char │ │ wchar_t │ │ char16_t │
</span><span class="z-text z-plain">│ Traits: │ │ Traits: │ │ Traits: │
</span><span class="z-text z-plain">│ char_traits │ │ char_traits │ │ char_traits │
</span><span class="z-text z-plain">│ <char> │ │ <wchar_t> │ │ <char16_t> │
</span><span class="z-text z-plain">│ Allocator: │ │ Allocator: │ │ Allocator: │
</span><span class="z-text z-plain">│ allocator │ │ allocator │ │ polymorphic_alloc │
</span><span class="z-text z-plain">│ <char> │ │ <wchar_t> │ │ <char16_t> │
</span><span class="z-text z-plain">└─────────────────┘ └─────────────────┘ └──────────────────────┘
</span><span class="z-text z-plain"> "Hello" L"Hello" u"Hello"
</span><span class="z-text z-plain"> 6 bytes 12/24 bytes 12 bytes
</span><span class="z-text z-plain"> PMR runtime alloc!
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">All are typedefs of basic_string with different template arguments!
</span><span class="z-text z-plain">std::pmr::u16string -> DIFFERENT allocator = DIFFERENT type!
</span></code></pre>
<h3 id="class-basic-string">class basic_string</h3>
<p>After discussing the arguments of the template, let's look at how <code>basic_string</code> is implemented under the hood.</p>
<p>This is exactly the same concept as Linux kernel's <code>qstr</code>. Using a <code>struct</code> to hold the pointer and length of the string data. There are some more features like capacity management (just like the previous mentioned <code>strcat</code> from Joel Spolsky's article), but the core idea is the same.</p>
<p>The code is self explanatory, so I will just paste it here:</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-meta z-template z-c++"><span class="z-storage z-type z-template z-c++">template</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span></span><span class="z-meta z-template z-c++"><span class="z-storage z-type z-c++">class</span> CharT<span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-type z-c++">class</span> Traits<span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-type z-c++">class</span> Allocator</span><span class="z-meta z-template z-c++"><span class="z-punctuation z-section z-generic z-end z-c++">></span></span>
</span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-storage z-type z-c++">class</span> </span><span class="z-meta z-class z-c++"><span class="z-entity z-name z-class z-c++">basic_string</span></span><span class="z-meta z-class z-c++"> <span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-storage z-modifier z-c++">private</span><span class="z-punctuation z-section z-class z-c++">:</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> CharT<span class="z-keyword z-operator z-c++">*</span> data_<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Pointer to character buffer
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-support z-type z-sys-types z-c">size_t</span> size_<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Current length (excluding null terminator)
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-support z-type z-sys-types z-c">size_t</span> capacity_<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Allocated capacity
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> Allocator alloc_<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Often zero-size due to EBO
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Many implementations use a union for SSO (Small String Optimization):
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-union z-c++"><span class="z-storage z-type z-c++">union</span> </span><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-struct z-c++"><span class="z-storage z-type z-c++">struct</span> </span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> CharT<span class="z-keyword z-operator z-c++">*</span> ptr<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> <span class="z-support z-type z-sys-types z-c">size_t</span> size<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> <span class="z-support z-type z-sys-types z-c">size_t</span> capacity<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> </span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span> heap<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> For long strings
</span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"> <span class="z-meta z-struct z-c++"><span class="z-storage z-type z-c++">struct</span> </span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++">
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> CharT buffer<span class="z-meta z-brackets z-c++"><span class="z-punctuation z-section z-brackets z-begin z-c++">[</span><span class="z-constant z-numeric z-integer z-decimal z-c++">16</span><span class="z-punctuation z-section z-brackets z-end z-c++">]</span></span><span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Size varies by implementation
</span></span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">char</span> size<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"> </span></span><span class="z-meta z-struct z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span> stack<span class="z-punctuation z-terminator z-c++">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> For short strings (SSO)
</span></span></span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"> </span></span><span class="z-meta z-union z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span> data_<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> Methods, iterators, etc.
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span><span class="z-punctuation z-terminator z-c++">;</span>
</span></code></pre>
<p>For more detail like short string optimization, and <code>pmr</code>/allocator, please refer to PART 3.</p>
<hr />
<h2 id="part-3-the-cost-of-abstraction-sso-linux-and-string-view">PART 3: The Cost of Abstraction (SSO, Linux, and string_view)</h2>
<h3 id="small-string-optimization-sso">Small String Optimization (SSO)</h3>
<p>Looking at the above code snippet of <code>basic_string</code>, I noticed the <code>union</code> that holds either a heap-allocated buffer or a small stack buffer. This is called Small String Optimization (SSO).</p>
<p>The concept of SSO is simple! Stack memory allocation is much faster than heap allocation. So preallocating a small buffer inside the string object can avoid small strings calling <code>new</code>/<code>delete</code> frequently.</p>
<p>In brief, stack allocation in compiler or assembly is just moving the stack pointer (prolog/epilog insertion), while heap allocation involves complex bookkeeping, searching for free blocks, and updating metadata.</p>
<p>I then researched a bit then find Linux kernel also uses this SSO technique under virtual filesystem path. The Linux kernel's <code>dentry</code> structure uses a <code>union</code> called <code>shortname_store</code> to hold either a short string directly in the structure or a pointer to a longer string allocated on the heap. This is exactly the same idea as C++'s SSO. [source: <code>include/linux/dcache.h</code>]</p>
<pre data-lang="C" class="language-C z-code"><code class="language-C" data-lang="C"><span class="z-source z-c"><span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span>
</span></span><span class="z-source z-c"><span class="z-comment z-block z-c"> <span class="z-punctuation z-definition z-comment z-c">*</span> Try to keep struct dentry aligned on 64 byte cachelines (this will
</span></span><span class="z-source z-c"><span class="z-comment z-block z-c"> <span class="z-punctuation z-definition z-comment z-c">*</span> give reasonable cacheline footprint with larger lines without the
</span></span><span class="z-source z-c"><span class="z-comment z-block z-c"> <span class="z-punctuation z-definition z-comment z-c">*</span> large memory footprint increase).
</span></span><span class="z-source z-c"><span class="z-comment z-block z-c"> <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c">#ifdef</span> CONFIG_64BIT
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"><span class="z-keyword z-control z-import z-define z-c"># define</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-entity z-name z-constant z-preprocessor z-c">DNAME_INLINE_WORDS</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-constant z-numeric z-integer z-decimal z-c">5</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> 192 bytes <span class="z-punctuation z-definition z-comment z-c">*/</span></span></span>
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c">#else</span>
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c"># ifdef</span> CONFIG_SMP
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"><span class="z-keyword z-control z-import z-define z-c"># define</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-entity z-name z-constant z-preprocessor z-c">DNAME_INLINE_WORDS</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-constant z-numeric z-integer z-decimal z-c">9</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> 128 bytes <span class="z-punctuation z-definition z-comment z-c">*/</span></span></span>
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c"># else</span>
</span></span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"><span class="z-keyword z-control z-import z-define z-c"># define</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-entity z-name z-constant z-preprocessor z-c">DNAME_INLINE_WORDS</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-constant z-numeric z-integer z-decimal z-c">11</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> 128 bytes <span class="z-punctuation z-definition z-comment z-c">*/</span></span></span>
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c"># endif</span></span>
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-c"><span class="z-keyword z-control z-import z-c">#endif</span></span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-storage z-type z-c">union</span> <span class="z-meta z-union z-c"><span class="z-entity z-name z-union z-c">shortname_store</span></span></span><span class="z-meta z-union z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">char</span> string<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span>DNAME_INLINE_LEN<span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">long</span> words<span class="z-meta z-brackets z-c"><span class="z-punctuation z-section z-brackets z-begin z-c">[</span>DNAME_INLINE_WORDS<span class="z-punctuation z-section z-brackets z-end z-c">]</span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span><span class="z-source z-c">
</span><span class="z-source z-c"><span class="z-meta z-preprocessor z-macro z-c"><span class="z-keyword z-control z-import z-define z-c">#define</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-entity z-name z-constant z-preprocessor z-c">DNAME_INLINE_LEN</span></span><span class="z-meta z-preprocessor z-macro z-c"> <span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span>DNAME_INLINE_WORDS<span class="z-keyword z-operator z-c">*</span><span class="z-keyword z-operator z-word z-c">sizeof</span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-begin z-c">(</span></span><span class="z-meta z-group z-c"><span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">long</span></span><span class="z-meta z-group z-c"><span class="z-punctuation z-section z-group z-end z-c">)</span></span><span class="z-punctuation z-section z-group z-end z-c">)</span></span></span>
</span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">dentry</span></span></span><span class="z-meta z-struct z-c"> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> RCU lookup touched fields <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">unsigned</span> <span class="z-storage z-type z-c">int</span> d_flags<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> protected by d_lock <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> seqcount_spinlock_t d_seq<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> per dentry seqlock <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">struct</span> hlist_bl_node d_hash<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> lookup hash list <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">dentry</span></span></span><span class="z-meta z-struct z-c"> *d_parent</span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> parent directory <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-union z-c"><span class="z-storage z-type z-c">union</span> <span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-begin z-c">{</span></span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c">
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">struct</span> qstr __d_name<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> for use ONLY in fs/dcache.c <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-modifier z-c">const</span> <span class="z-storage z-type z-c">struct</span> qstr d_name<span class="z-punctuation z-terminator z-c">;</span>
</span></span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"> </span></span><span class="z-meta z-union z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-meta z-struct z-c"><span class="z-storage z-type z-c">struct</span> <span class="z-meta z-struct z-c"><span class="z-entity z-name z-struct z-c">inode</span></span></span><span class="z-meta z-struct z-c"> *d_inode</span><span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-block z-c"><span class="z-punctuation z-definition z-comment z-c">/*</span> Where the name belongs to - NULL is
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-comment z-block z-c"> negative <span class="z-punctuation z-definition z-comment z-c">*/</span></span>
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-storage z-type z-c">union</span> shortname_store d_shortname<span class="z-punctuation z-terminator z-c">;</span> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span><-- SSO buffer for short names
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c">
</span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span> ...
</span></span></span></span><span class="z-source z-c"><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"></span></span><span class="z-meta z-struct z-c"><span class="z-meta z-block z-c"><span class="z-punctuation z-section z-block z-end z-c">}</span></span></span><span class="z-punctuation z-terminator z-c">;</span>
</span></code></pre>
<p><strong>Why Linux's SSO is Genius</strong></p>
<p>While the concept is similar to C++, Linux implements it with a crucial twist for performance: Branchless Access.</p>
<p>In C++, accessing <code>std::string</code> often involves a check: "Is this short or long?" before deciding where to read data.</p>
<p>Linux avoids this check completely. The <code>d_name.name</code> pointer (inside <code>struct qstr</code>) is set up during creation to point either to the internal <code>d_shortname</code> buffer or the external heap.</p>
<ul>
<li>Consumer side: When you read a filename, you just follow the pointer. Zero branching. Zero checks.</li>
<li>Cache Line: Also, notice the comments about 64-byte alignment. The buffer size is calculated so the whole struct fits perfectly into a CPU cache line.</li>
</ul>
<p>This shows that for System Programmers, Memory Layout isn't just about saving bytes—it's about saving CPU cycles.</p>
<p><strong>Update</strong>: Community Insights (2025-11-26)</p>
<p>Question from Reddit:</p>
<blockquote>
<p>If you avoid a branch, but gain a pointer indirection, how big win is that in the end, considering the branch prediction that modern CPU are capable of? <br />
u/Supadoplex</p>
</blockquote>
<p>Answer or trying to answer:</p>
<p>I don't know the exact answer, but here is my research.
It depends on predictability vs cache locality:</p>
<p>Branch misprediction cost:</p>
<ul>
<li>Modern x86 (Skylake/Zen): ~15-20 cycles (per Agner Fog's measurements)</li>
<li>Older architectures: can be 30+ cycles</li>
</ul>
<p>Pointer indirection cost:
(Numbers from <a rel="noopener" target="_blank" href="https://www.agner.org/optimize/">agner.org</a>)</p>
<ul>
<li>L1 cache hit: ~4-5 cycles (Intel/AMD spec)</li>
<li>L2 hit: ~12 cycles</li>
<li>L3 hit: ~40 cycles</li>
<li>RAM miss: 200+ cycles</li>
</ul>
<p>So...</p>
<blockquote>
<p>If your branch is unpredictable (<80% hit rate) and your pointer data is cache-resident, indirection usually wins.
— Cloudlet</p>
</blockquote>
<p><strong>Customize size of SSO buffer</strong></p>
<p>Well, sometimes I think I need to customize the size of SSO buffer for specific use cases. For example, I once need to deal with complex Verilog/VHDL mix language path names, which can be quite long. So maybe next time I will try to implement a customize string to support larger SSO buffer. The key is finding the right balance between stack size and heap allocation frequency. That's too far for this article, maybe next time.</p>
<h3 id="the-missing-piece-std-string-view-c-17">The Missing Piece: std::string_view (C++17)</h3>
<p>Look at the internal structure of <code>std::string_view</code> (from GCC's <code>libstdc++</code>). It is strikingly similar to Linux kernel's <code>qstr</code>:</p>
<pre data-lang="cpp" class="language-cpp z-code"><code class="language-cpp" data-lang="cpp"><span class="z-source z-c++"><span class="z-meta z-template z-c++"><span class="z-storage z-type z-template z-c++">template</span><span class="z-punctuation z-section z-generic z-begin z-c++"><</span></span><span class="z-meta z-template z-c++"><span class="z-storage z-type z-c++">typename</span> _CharT<span class="z-punctuation z-separator z-c++">,</span> <span class="z-storage z-type z-c++">typename</span> _Traits <span class="z-keyword z-operator z-assignment z-c">=</span> std<span class="z-punctuation z-accessor z-double-colon z-c++">::</span>char_traits<span class="z-punctuation z-section z-generic z-begin z-c++"><</span>_CharT<span class="z-punctuation z-section z-generic z-end z-c++">></span></span><span class="z-meta z-template z-c++"><span class="z-punctuation z-section z-generic z-end z-c++">></span></span>
</span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-storage z-type z-c++">class</span> </span><span class="z-meta z-class z-c++"><span class="z-entity z-name z-class z-c++">basic_string_view</span></span><span class="z-meta z-class z-c++">
</span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-begin z-c++">{</span></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++">
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-comment z-line z-double-slash z-c"><span class="z-punctuation z-definition z-comment z-c">//</span>...
</span></span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-storage z-modifier z-c++">private</span><span class="z-punctuation z-section z-class z-c++">:</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-support z-type z-sys-types z-c">size_t</span> _M_len<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"> <span class="z-storage z-modifier z-c++">const</span> _CharT<span class="z-keyword z-operator z-c++">*</span> _M_str<span class="z-punctuation z-terminator z-c++">;</span>
</span></span></span><span class="z-source z-c++"><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"></span></span><span class="z-meta z-class z-c++"><span class="z-meta z-block z-c++"><span class="z-punctuation z-section z-block z-end z-c++">}</span></span></span>
</span></code></pre>
<p>Just a pointer and a length. No allocator. No heap. No ownership. The owership is owned by the <code>std::string</code> (the most common), <code>char</code> array, or <code>mmap</code>ed file.</p>
<p>Of course, there is no free lunch. <code>std::string_view</code> is a borrowed reference (like Rust's <code>&str</code>). Must ensure the original data (the Owner) outlives the view.</p>
<h2 id="conclusion-the-swiss-army-knife-vs-the-scalpel">Conclusion: The "Swiss Army Knife" vs. The Scalpel</h2>
<p><code>std::string</code> tries to do too much. It handles ownership, resizing, traits, and allocation all in one class. It is a Swiss Army Knife—great for general applications where productivity comes first. (I have to admit, using std::string and RAII feels good and is incredibly brain-friendly.)</p>
<p>But for a high-performance tool like Linux kernel or LLVM compiler, it is over-engineered.</p>
<p>Linux Kernel's <code>struct qstr</code> shows us the elegance of simplicity: just a pointer and a length (and a hash).</p>
<p>The good news? Modern C++ (C++17) finally admitted this with <code>std::string_view</code>. It strips away the allocator magic and memory ownership, giving us back the raw efficiency of a C-style <code>struct</code>, but with type safety.</p>
<p>So, for writing high-performance tools:</p>
<ul>
<li>Treat <code>std::string</code> as a heavy container (like <code>std::vector</code>).</li>
<li>Use <code>std::string_view</code> as your primary interface.</li>
<li>And always remember: Simplicity is the ultimate sophistication.</li>
</ul>
<h2 id="further-study">Further Study</h2>
<ul>
<li>What is PMR (Polymorphic Memory Resources) in C++17?</li>
<li>How <code>absl::Cord</code> solves the contiguous memory problem for huge strings?</li>
</ul>