<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Compilers on Casaderic Compendivm</title><link>http://kasader.dev/tags/compilers/</link><description>Recent content in Compilers on Casaderic Compendivm</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Tue, 07 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://kasader.dev/tags/compilers/index.xml" rel="self" type="application/rss+xml"/><item><title>Zero-Allocation Logging in Go</title><link>http://kasader.dev/posts/001_go_zero_alloc/</link><pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate><guid>http://kasader.dev/posts/001_go_zero_alloc/</guid><description>&lt;p&gt;Go&amp;rsquo;s structured logging library (&lt;code&gt;log/slog&lt;/code&gt;) is quite the clever piece of standard library code. This is thanks to the processing of log attributes &lt;code&gt;{key:value}&lt;/code&gt; with &lt;strong&gt;zero heap allocations&lt;/strong&gt;. And despite the &lt;code&gt;slog.Logger&lt;/code&gt; interface (inspired by packages &lt;code&gt;uber-go/zap&lt;/code&gt; and &lt;code&gt;rs/zerolog&lt;/code&gt;) now long being the go-to idiom for any generic logging use-cases in Go; it is additionally highly-optimized for use in application hot-paths.&lt;/p&gt;
&lt;p&gt;Wanting to know exactly how Go achieves this with the the compiler optimizing away unnecessary overhead, I wrote a quick mock logger following the &lt;code&gt;log/slog&lt;/code&gt; implementation and analyzed the output assembly via &lt;a href="https://www.google.com/search?q=https://go.godbolt.org"&gt;compiler explorer (go.godbolt.org)&lt;/a&gt; (thank you &lt;a href="https://xania.org/MattGodbolt"&gt;Matt Godbolt&lt;/a&gt;, sire).&lt;/p&gt;
&lt;p&gt;In the following sections, I am going to walk through how the Go compiler avoids common &amp;ldquo;interface boxing&amp;rdquo; (&lt;a href="https://goperf.dev/01-common-patterns/interface-boxing/"&gt;allocating variables to the heap when converting to interfaces&lt;/a&gt;), fully inlines function calls to save CPU cycles, and passes data between stack frames without copying entire arrays.&lt;/p&gt;
&lt;h2 id="code-setup"&gt;Code Setup&lt;/h2&gt;
&lt;p&gt;Here is a stripped-down look at how a zero-allocation logger takes in attributes. Instead of using a variadic type &lt;code&gt;T&lt;/code&gt; set (&lt;code&gt;...any&lt;/code&gt;) (which forces Go to box primitives into interfaces on the heap), we define a concrete &lt;code&gt;intAttr&lt;/code&gt; struct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;my msg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newIntAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newIntAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;//go:noinline&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;intAttr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intAttr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newIntAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intAttr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;(I suggest you follow along using the same code above to fully understand this process).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Your prize for compiling this is a fat block of Plan 9 Assembly. But before we decode the output, let&amp;rsquo;s first address the weird symbol naming.&lt;/p&gt;
&lt;h3 id="checking-out-command-line-arguments"&gt;Checking Out &lt;code&gt;command-line-arguments&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If you look at the raw assembly, you&amp;rsquo;ll see memory locations and function calls prefixed with &lt;code&gt;command-line-arguments&lt;/code&gt;, like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CALL command-line-arguments.log(SB)&lt;/code&gt; or &lt;code&gt;MOVQ DX, command-line-arguments..autotmp_8+40(SP)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why? In Go&amp;rsquo;s assembly dialect, the assembler attaches human-readable symbols to memory locations to help the debugger and the Garbage Collector map machine code back to your source file. When you compile a standalone Go file without a properly initialized Go module (like in a compiler explorer sandbox), the compiler assigns it a default package name of &lt;code&gt;command-line-arguments&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;..autotmp_8&lt;/code&gt; simply means the compiler created a hidden, &amp;ldquo;automatic temporary&amp;rdquo; variable on the stack to hold our data. The CPU never sees these names; it only sees the raw memory offsets.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="optimization-1-function-inlining--struct-initialization"&gt;Optimization 1: Function Inlining &amp;amp; Struct Initialization&lt;/h2&gt;
&lt;p&gt;In our Go code, we call &lt;code&gt;newIntAttr(&amp;quot;k1&amp;quot;, x)&lt;/code&gt; and &lt;code&gt;newIntAttr(&amp;quot;k2&amp;quot;, 2)&lt;/code&gt;. You might expect the assembly to set up a stack frame, push arguments, jump to the &lt;code&gt;newIntAttr&lt;/code&gt; function, and return a struct.&lt;/p&gt;
&lt;p&gt;The Go compiler is much smarter than that. It completely &lt;strong&gt;inlines&lt;/strong&gt; the calls to save CPU cycles.&lt;/p&gt;
&lt;p&gt;A Go &lt;code&gt;string&lt;/code&gt; is a 16-byte struct (an 8-byte pointer to the text, and an 8-byte length). Our &lt;code&gt;int&lt;/code&gt; is 8 bytes. That means each &lt;code&gt;intAttr&lt;/code&gt; struct is exactly 24 bytes. Because we pass two of them in our variadic &lt;code&gt;...intAttr&lt;/code&gt; slice, the compiler needs 48 bytes of contiguous memory.&lt;/p&gt;
&lt;p&gt;Instead of calling &lt;code&gt;newIntAttr&lt;/code&gt;, the compiler manually writes the data directly to the stack, byte by byte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Building the first intAttr: newIntAttr(&amp;#34;k1&amp;#34;, x) ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Grab the pointer to the &amp;#34;k1&amp;#34; string bytes and put it in DX
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;LEAQ&lt;/span&gt; &lt;span class="no"&gt;go&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="no"&gt;string.&lt;/span&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt;&lt;span class="no"&gt;k1&lt;/span&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SB&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;DX&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the Pointer to SP+40
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;DX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the string Length (2) to SP+48
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the integer value (x, held in AX) to SP+56
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Building the second intAttr: newIntAttr(&amp;#34;k2&amp;#34;, 2) ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Grab the pointer to the &amp;#34;k2&amp;#34; string bytes and put it in DX
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;LEAQ&lt;/span&gt; &lt;span class="no"&gt;go&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="no"&gt;string.&lt;/span&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt;&lt;span class="no"&gt;k2&lt;/span&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SB&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;DX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the Pointer to SP+64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;DX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the string Length (2) to SP+72
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Write the integer value (2) to SP+80
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments..autotmp_8&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="optimization-2-escape-analysis-and-the-stack"&gt;Optimization 2: Escape Analysis and the Stack&lt;/h2&gt;
&lt;p&gt;Notice &lt;em&gt;where&lt;/em&gt; it wrote those 48 bytes: &lt;code&gt;+40(SP)&lt;/code&gt; up through &lt;code&gt;+80(SP)&lt;/code&gt;. &lt;code&gt;SP&lt;/code&gt; is our &lt;strong&gt;Stack Pointer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If we had passed our attributes as &lt;code&gt;...any&lt;/code&gt; (the standard &lt;code&gt;fmt.Printf&lt;/code&gt; approach), the compiler wouldn&amp;rsquo;t know the size or type of the arguments at compile time. It would be forced to allocate them dynamically on the heap (a process called &amp;ldquo;boxing&amp;rdquo;), which requires the Garbage Collector to clean them up later.&lt;/p&gt;
&lt;p&gt;Because we used a concrete type (&lt;code&gt;intAttr&lt;/code&gt;), the compiler&amp;rsquo;s &lt;strong&gt;Escape Analysis&lt;/strong&gt; kicked in. It realized this variadic slice never &amp;ldquo;escapes&amp;rdquo; the lifecycle of &lt;code&gt;myFunction&lt;/code&gt; and &lt;code&gt;log&lt;/code&gt;. Therefore, it entirely skips the &lt;code&gt;runtime.newobject&lt;/code&gt; heap allocation and safely builds the array directly within &lt;code&gt;myFunction&lt;/code&gt;&amp;rsquo;s local stack space. This is the zero allocation we are looking for!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="optimization-3-the-register-based-calling-convention"&gt;Optimization 3: The Register-Based Calling Convention&lt;/h2&gt;
&lt;p&gt;Now that we have a 48-byte backing array on &lt;code&gt;myFunction&lt;/code&gt;&amp;rsquo;s stack, how do we pass it to &lt;code&gt;log&lt;/code&gt;? A common misconception is that the whole array is copied into the new function&amp;rsquo;s stack frame.&lt;/p&gt;
&lt;p&gt;Thanks to Go&amp;rsquo;s modern register-based ABI (Application Binary Interface), arguments are passed to functions via CPU registers whenever possible. Our &lt;code&gt;log&lt;/code&gt; function expects two arguments: a &lt;code&gt;string&lt;/code&gt; (Pointer, Length) and a &lt;code&gt;slice&lt;/code&gt; (Pointer, Length, Capacity).&lt;/p&gt;
&lt;p&gt;Right before the &lt;code&gt;CALL&lt;/code&gt; to &lt;code&gt;log&lt;/code&gt;, the compiler packs these 5 words of data into 5 CPU registers:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;strong&gt;Reg&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;Arg&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;
					&lt;th&gt;&lt;strong&gt;Assembly Instruction&lt;/strong&gt;&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;AX&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;msg&lt;/code&gt; Ptr&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;&amp;quot;my msg&amp;quot;&lt;/code&gt; Addr.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;LEAQ go:string.&amp;quot;my msg&amp;quot;(SB), AX&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;BX&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;msg&lt;/code&gt; Len&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;MOVL $10, BX&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;CX&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;attrs&lt;/code&gt; Ptr&lt;/td&gt;
					&lt;td&gt;Stack Array Addr.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;LEAQ com...autotmp_8+40(SP), CX&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DI&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;attrs&lt;/code&gt; Len&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;MOVL $2, DI&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SI&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;attrs&lt;/code&gt; Cap&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;MOVQ DI, SI&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CALL&lt;/span&gt; &lt;span class="no"&gt;command-line-arguments.log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When execution jumps into &lt;code&gt;log&lt;/code&gt;, the 48 bytes of array data are left untouched, sitting safely back in &lt;code&gt;myFunction&lt;/code&gt;&amp;rsquo;s stack frame. The &lt;code&gt;log&lt;/code&gt; function simply uses the pointer passed in the &lt;code&gt;CX&lt;/code&gt; register to reach &amp;ldquo;up&amp;rdquo; the stack and read the structs one by one.&lt;/p&gt;
&lt;h3 id="takeaway"&gt;Takeaway&lt;/h3&gt;
&lt;p&gt;By passing a 24-byte slice header in registers rather than copying a whole 48-byte array across stack frames, and by avoiding the heap completely, Go keeps function calls blazingly fast. This combination of &lt;strong&gt;inlining&lt;/strong&gt;, &lt;strong&gt;stack allocation&lt;/strong&gt;, and &lt;strong&gt;register-passing&lt;/strong&gt; is that which makes our &lt;code&gt;log/slog&lt;/code&gt; so performant. Lovely, isn&amp;rsquo;t it?&lt;/p&gt;</description></item></channel></rss>