<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Abi on Opensource internals</title><link>https://articles.9j.nz/tags/abi/</link><description>Posts filed under Abi on Opensource internals.</description><generator>Hugo</generator><language>en-US</language><managingEditor>Jay Hubbert</managingEditor><lastBuildDate>Wed, 21 Oct 2026 04:16:00 +0000</lastBuildDate><atom:link href="https://articles.9j.nz/tags/abi/index.xml" rel="self" type="application/rss+xml"/><item><title>Go Passes Arguments in Registers: The Register ABI and Its Transparent Wrappers</title><link>https://articles.9j.nz/posts/how-go-passes-arguments-in-registers/</link><pubDate>Wed, 21 Oct 2026 04:16:00 +0000</pubDate><dc:creator>Jay Hubbert</dc:creator><guid isPermaLink="true">https://articles.9j.nz/posts/how-go-passes-arguments-in-registers/</guid><category>go</category><category>internals</category><category>abi</category><category>compiler</category><category>performance</category><description>Since Go 1.17 every Go function passes arguments and results in machine registers, and the compiler silently inserts wrappers so that assembly code can keep using the old stack-based ABI0.</description><content:encoded>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Go has two calling conventions. &lt;strong&gt;ABI0&lt;/strong&gt; is the old stack-based one&#10;that assembly code must use, and it is frozen forever. &lt;strong&gt;ABIInternal&lt;/strong&gt; is the&#10;one every function written in Go actually uses: arguments and results go in&#10;machine registers, with a fixed register holding the current goroutine&#10;pointer and another fixed to zero. Because both worlds have to coexist, the&#10;compiler generates &lt;em&gt;transparent wrappers&lt;/em&gt; that translate one convention into&#10;the other — you never write them, but they are why a hand-written assembly&#10;routine can still be called from Go without costing you a stack shuffle.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Every Go program you have ever written calls functions through an ABI you&#10;have probably never thought about. And for most of Go&amp;rsquo;s life, that ABI put&#10;every argument on the stack.&lt;/p&gt;&#10;&lt;p&gt;Then Go 1.17 changed the rules. The change is invisible from Go source — no&#10;syntax changed, no semantics changed — but it is one of the largest&#10;performance-relevant redesigns the language has ever done, and the source&#10;tree documents it in unusual detail.&lt;/p&gt;&#10;&lt;h2 id="two-abis-and-why-there-are-two"&gt;Two ABIs, and why there are two&lt;/h2&gt;&#10;&lt;p&gt;The distinction is baked into the object file format. From&#10;&lt;code&gt;src/cmd/internal/obj/link.go&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/cmd/internal/obj/link.go:843&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ABI is the calling convention of a text symbol.&lt;/span&gt;&lt;span class="w"&gt;&#10;&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;ABI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABI0 is the stable stack-based ABI. It&amp;#39;s important that the&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// value of this is &amp;#34;0&amp;#34;: we can&amp;#39;t distinguish between&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// references to data and ABI0 text symbols in assembly code,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// and hence this doesn&amp;#39;t distinguish between symbols without&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// an ABI and text symbols with ABI0.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;ABI0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ABI&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 class="kc"&gt;iota&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABIInternal is the internal ABI that may change between Go&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// versions. All Go functions use the internal ABI and the&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// compiler generates wrappers for calls to and from other&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABIs.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;ABIInternal&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;ABICount&lt;/span&gt;&lt;span class="w"&gt;&#10;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Read that comment carefully, because it contains a whole design constraint in&#10;four lines: &lt;strong&gt;ABI0 must have the value zero&lt;/strong&gt;, because the linker and the&#10;assembler need to tell &amp;ldquo;this symbol is a data symbol&amp;rdquo; apart from &amp;ldquo;this symbol&#10;is a text symbol with the default ABI&amp;rdquo;. Anything else would require an extra&#10;bit in every relocation.&lt;/p&gt;&#10;&lt;p&gt;So the two ABIs have asymmetric properties on purpose:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;ABI0 is stable.&lt;/strong&gt; It is the contract with hand-written assembly, with&#10;the runtime&amp;rsquo;s own assembly, with the debugger, and with anything that&#10;pokes at a Go stack frame. It will not change. Ever.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;ABIInternal is unstable.&lt;/strong&gt; It is free to change between Go releases&#10;because only the compiler and the runtime, which ship together, depend on&#10;it.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The tree says exactly this in &lt;code&gt;src/cmd/compile/abi-internal.md&lt;/code&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;This document describes Go&amp;rsquo;s internal application binary interface&#10;(ABI), known as ABIInternal.&#10;Go&amp;rsquo;s ABI defines the layout of data in memory and the conventions for&#10;calling between Go functions.&#10;This ABI is &lt;em&gt;unstable&lt;/em&gt; and will change between Go versions.&#10;If you&amp;rsquo;re writing assembly code, please instead refer to Go&amp;rsquo;s&#10;assembly documentation, which describes Go&amp;rsquo;s stable ABI, known as ABI0.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="the-register-assignment-algorithm"&gt;The register assignment algorithm&lt;/h2&gt;&#10;&lt;p&gt;ABIInternal is a common ABI across all architectures — the same &lt;em&gt;algorithm&lt;/em&gt;&#10;runs everywhere, only the register sequences differ. The algorithm, straight&#10;out of the spec, breaks arguments and results down recursively into base&#10;values and hands them out in order:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;If T is a boolean or integral type that fits in an integer register, assign V to register I and increment I.&lt;/li&gt;&#10;&lt;li&gt;If T is an integral type that fits in two integer registers, assign the least significant and most significant halves of V to registers I and I+1, respectively, and increment I by 2&lt;/li&gt;&#10;&lt;li&gt;If T is a floating-point type and can be represented without loss of precision in a floating-point register, assign V to register FP and increment FP.&lt;/li&gt;&#10;&lt;li&gt;If T is a complex type, recursively register-assign its real and imaginary parts.&lt;/li&gt;&#10;&lt;li&gt;If T is a pointer type, map type, channel type, or function type, assign V to register I and increment I.&lt;/li&gt;&#10;&lt;li&gt;If T is a string type, interface type, or slice type, recursively register-assign V&amp;rsquo;s components (2 for strings and interfaces, 3 for slices).&lt;/li&gt;&#10;&lt;li&gt;If T is a struct type, recursively register-assign each field of V.&lt;/li&gt;&#10;&lt;li&gt;If T is an array type of length 0, do nothing.&lt;/li&gt;&#10;&lt;li&gt;If T is an array type of length 1, recursively register-assign its one element.&lt;/li&gt;&#10;&lt;li&gt;If T is an array type of length &amp;gt; 1, fail.&lt;/li&gt;&#10;&lt;li&gt;If I &amp;gt; NI or FP &amp;gt; NFP, fail.&lt;/li&gt;&#10;&lt;li&gt;If any recursive assignment above fails, fail.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Two details there are worth dwelling on.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;A string costs two registers. A slice costs three.&lt;/strong&gt; Not because the&#10;pointer and the length are physically separate, but because the ABI treats&#10;them as separate base values and each gets its own register. The same is true&#10;of an interface: the itab pointer and the data pointer are two independent&#10;register assignments.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;An array of more than one element fails.&lt;/strong&gt; A &lt;code&gt;[2]uintptr&lt;/code&gt; cannot be&#10;register-assigned at all, so it goes on the stack — the &lt;em&gt;entire&lt;/em&gt; array, not&#10;just its second word. This is a subtle and frequently surprising rule. It&#10;also means an array is not a first-class citizen in the ABI: it is either&#10;entirely in registers (length 1) or entirely on the stack.&lt;/p&gt;&#10;&lt;p&gt;The failure mode is graceful. If any recursive assignment fails, the whole&#10;value is reassigned to the stack:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;ol start="3"&gt;&#10;&lt;li&gt;Try to register-assign V.&lt;/li&gt;&#10;&lt;li&gt;If step 3 failed, reset I and FP to the values from step 1, add T to the stack sequence S, and assign V to this field in S.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Note the &amp;ldquo;reset I and FP&amp;rdquo; — a failed attempt does not burn registers.&lt;/p&gt;&#10;&lt;h2 id="the-stack-frame-layout"&gt;The stack frame layout&lt;/h2&gt;&#10;&lt;p&gt;Arguments that could not be register-assigned live on the stack, and the&#10;layout is not the obvious one. From the spec:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The final stack sequence looks like: stack-assigned receiver, stack-assigned arguments, pointer-alignment, stack-assigned results, pointer-alignment, spill space for each register-assigned argument, pointer-alignment.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;So a call frame looks like this, with the arrow pointing at lower addresses:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;text&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +------------------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | . . . |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 2nd reg argument spill space |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 1st reg argument spill space |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | &amp;lt;pointer-sized alignment&amp;gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | . . . |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 2nd stack-assigned result |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 1st stack-assigned result |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | &amp;lt;pointer-sized alignment&amp;gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 2nd stack-assigned argument |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 1st stack-assigned argument |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | stack-assigned receiver |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Two things jump out.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Results are stored on the stack too, below the arguments.&lt;/strong&gt; The caller has&#10;to reserve space for the results &lt;em&gt;before&lt;/em&gt; making the call, because it does&#10;not know the callee&amp;rsquo;s stack frame size. So the caller reserves the result&#10;slots, and the callee writes into them.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Every register-assigned argument gets a spill slot.&lt;/strong&gt; The caller reserves&#10;space for these but leaves them uninitialized. The callee may spill an&#10;argument to its own frame, but if it wants to spill to the &lt;em&gt;caller&amp;rsquo;s&lt;/em&gt; frame&#10;(the classic &amp;ldquo;callee-saved register&amp;rdquo; trick) the space is already there. This&#10;is why the ABI has no callee-save registers at all:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;There are no callee-save registers, so a call may overwrite any register that doesn&amp;rsquo;t have a fixed meaning, including argument registers.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;That is a genuinely radical choice. It removes the entire save/restore&#10;choreography from the call sequence, at the cost of the caller having to&#10;reserve spill space it might never use. On a modern OoO core the spilled&#10;slot is usually still in L1 by the time it is needed, so the trade usually&#10;wins.&lt;/p&gt;&#10;&lt;h2 id="fixed-registers-the-goroutine-pointer-and-the-zero-register"&gt;Fixed registers: the goroutine pointer and the zero register&lt;/h2&gt;&#10;&lt;p&gt;On amd64 the integer register sequence is nine registers:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The amd64 architecture uses the following sequence of 9 registers for integer arguments and results:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;p&gt;It uses X0 – X14 for floating-point arguments and results.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Nine, not fifteen. On x86-64 there are fifteen usable GPRs, but &lt;code&gt;RSP&lt;/code&gt; is the&#10;stack pointer, &lt;code&gt;RBP&lt;/code&gt; is the frame pointer, and three more are reserved as&#10;scratch. The scratch reservation is explained in the spec:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Registers R12 and R13 are permanent scratch registers.&#10;R15 is a scratch register except in dynamically linked binaries.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Rationale&lt;/em&gt;: Some operations such as stack growth and reflection calls&#10;need dedicated scratch registers in order to manipulate call frames&#10;without corrupting arguments or results.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;That is a real constraint, not a stylistic one: &lt;code&gt;runtime.morestack&lt;/code&gt; has to&#10;move the whole argument area of the frame it is growing, and it has to do so&#10;without a scratch register it is allowed to clobber.&lt;/p&gt;&#10;&lt;p&gt;And then there are the two famous fixed registers:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Register&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Call meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Return meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Body meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;R14&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Current goroutine&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;X15&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Zero value (*)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Scratch&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;R14 always holds the current goroutine pointer.&lt;/strong&gt; This is the single most&#10;important consequence of the register ABI for everyday Go code. Before Go&#10;1.17, every function that touched &lt;code&gt;getg()&lt;/code&gt; had to make a TLS lookup — a&#10;memory load, potentially a function call, definitely a cache miss on the&#10;TLS block. Now &lt;code&gt;g&lt;/code&gt; is just a register.&lt;/p&gt;&#10;&lt;p&gt;Look at how the runtime maintains it. When the runtime switches to a&#10;different goroutine&amp;rsquo;s stack it reloads R14:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;asm&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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;// src/runtime/asm_amd64.s:1086&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nf"&gt;MOVQ&lt;/span&gt;&#9;&lt;span class="no"&gt;SI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;R14&lt;/span&gt; &lt;span class="c1"&gt;// set the g register, as required by ABIInternal.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nf"&gt;XORPS&lt;/span&gt;&#9;&lt;span class="no"&gt;X15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;X15&lt;/span&gt; &lt;span class="c1"&gt;// clear X15, as required by ABIInternal.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Two instructions on every goroutine switch. That is the entire cost of the&#10;fixed-register convention, and it replaces a TLS lookup in every function&#10;that needs &lt;code&gt;g&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;X15 is always zero.&lt;/strong&gt; The rationale is given:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;Rationale&lt;/em&gt;: We designate X15 as a fixed zero register because functions often have to bulk zero their stack frames, and this is more efficient with a designated zero register.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Go zeroes stack frames when a function has pointer-containing locals that&#10;the GC might otherwise see as garbage. With a dedicated zero register, the&#10;zeroing becomes a &lt;code&gt;MOVUPS&lt;/code&gt; from a known-zero source rather than a &lt;code&gt;PXOR&lt;/code&gt;&#10;against itself plus a store — one fewer instruction on the dependency&#10;chain, and the store can be a plain vector store.&lt;/p&gt;&#10;&lt;h2 id="the-wrappers-how-two-worlds-coexist"&gt;The wrappers: how two worlds coexist&lt;/h2&gt;&#10;&lt;p&gt;Here is the interesting engineering. Assembly code in the runtime (and in&#10;third-party packages like &lt;code&gt;github.com/bytedance/sonic&lt;/code&gt;) is written against&#10;ABI0. Go code calls it. The compiler bridges the gap automatically.&lt;/p&gt;&#10;&lt;p&gt;From &lt;code&gt;src/cmd/compile/internal/ssagen/abi.go&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/cmd/compile/internal/ssagen/abi.go:286&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABI0-to-ABIInternal wrappers will be mainly loading params from&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// stack into registers (and/or storing stack locations back to&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// registers after the wrapped call); in most cases they won&amp;#39;t&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// need to allocate stack space, so it should be OK to mark them&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// as NOSPLIT in these cases. In addition, my assumption is that&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// functions written in assembly are NOSPLIT in most (but not all)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// cases. In the case of an ABIInternal target that has too many&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// parameters to fit into registers, the wrapper would need to&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// allocate stack space, but this seems like an unlikely scenario.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Hence: mark these wrappers NOSPLIT.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABIInternal-to-ABI0 wrappers on the other hand will be taking&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// things in registers and pushing them onto the stack prior to&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// the ABI0 call, meaning that they will always need to allocate&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// stack space. If the compiler marks them as NOSPLIT this seems&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// as though it could lead to situations where the linker&amp;#39;s&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// nosplit-overflow analysis would trigger a link failure. On the&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// other hand if they not tagged NOSPLIT then this could cause&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// problems when building the runtime (since there may be calls to&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// asm routine in cases where it&amp;#39;s not safe to grow the stack). In&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// most cases the wrapper would be (in effect) inlined, but are&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// there (perhaps) indirect calls from the runtime that could run&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// into trouble here.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// FIXME: at the moment all.bash does not pass when I leave out&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// NOSPLIT for these wrappers, so all are currently tagged with NOSPLIT.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pragma&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="nx"&gt;ir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Nosplit&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;That comment is a fossil of a real debugging session, and it tells you the&#10;whole story: the wrapper-generation code has subtleties that were discovered&#10;by running the full test suite.&lt;/p&gt;&#10;&lt;p&gt;Then there is the tail-call optimisation, with an architecture-specific&#10;carve-out:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/cmd/compile/internal/ssagen/abi.go:328&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ctxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Arch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&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="s"&gt;&amp;#34;amd64&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wrapperABI&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="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABIInternal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// cannot tailcall from ABIInternal to ABI0 on AMD64, as we need&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// to special registers (X15) when returning to ABIInternal.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;tailcall&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 class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&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;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;A tail call would be perfect: no frame, no return address, minimal cost.&#10;But on amd64, returning from an ABI0 function to an ABIInternal caller&#10;requires restoring X15 (the zero register) to zero, and you cannot do that&#10;after a &lt;code&gt;JMP&lt;/code&gt; because there is no return point to execute it at. So the&#10;wrapper has to be a real call with a real frame.&lt;/p&gt;&#10;&lt;p&gt;The same file has a similar carve-out for ppc64 with dynamic linking, where&#10;the link register &lt;code&gt;R2&lt;/code&gt; must be restored.&lt;/p&gt;&#10;&lt;h2 id="regargs-the-assembly-facing-view"&gt;&lt;code&gt;RegArgs&lt;/code&gt;: the assembly-facing view&lt;/h2&gt;&#10;&lt;p&gt;Assembly code that wants to participate in ABIInternal needs to know where&#10;the registers are. That is the job of &lt;code&gt;internal/abi.RegArgs&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/internal/abi/abi.go:21&lt;/span&gt;&lt;span class="w"&gt;&#10;&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;RegArgs&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Values in these slots should be precisely the bit-by-bit&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// representation of how they would appear in a register.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// This means that on big endian arches, integer values should&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// be in the top bits of the slot. Floats are usually just&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// directly represented, but some architectures treat narrow&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// width floating point values specially (e.g. they&amp;#39;re promoted&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// first, or they need to be NaN-boxed).&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;Ints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;IntArgRegs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// untyped integer registers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;Floats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FloatArgRegs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// untyped float registers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Fields above this point are known to assembly.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Ptrs is a space that duplicates Ints but with pointer type,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// used to make pointers passed or returned in registers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// visible to the GC by making the type unsafe.Pointer.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;Ptrs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;IntArgRegs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ReturnIsPtr is a bitmap that indicates which registers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// contain or will contain pointers on the return path from&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// a reflectcall. The i&amp;#39;th bit indicates whether the i&amp;#39;th&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// register contains or will contain a valid Go pointer.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;ReturnIsPtr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IntArgRegBitmap&lt;/span&gt;&lt;span class="w"&gt;&#10;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The &lt;code&gt;Ptrs&lt;/code&gt; field is a small, very clever hack. The GC scans the argument&#10;registers of a goroutine&amp;rsquo;s stack when the goroutine is scanned or when it&#10;transitions to a syscall. Those registers hold integers of unknown&#10;provenance. If the GC treated every register as an integer, it would miss&#10;pointers passed in registers and could collect live objects.&lt;/p&gt;&#10;&lt;p&gt;The trick: &lt;code&gt;RegArgs&lt;/code&gt; has &lt;em&gt;two&lt;/em&gt; copies of the integer registers — &lt;code&gt;Ints&lt;/code&gt; as&#10;plain &lt;code&gt;uintptr&lt;/code&gt;, and &lt;code&gt;Ptrs&lt;/code&gt; as &lt;code&gt;unsafe.Pointer&lt;/code&gt;. The &lt;code&gt;Ptrs&lt;/code&gt; copy has pointer&#10;type, so the GC&amp;rsquo;s stack map sees it as pointers and scans it correctly. The&#10;&lt;code&gt;Ints&lt;/code&gt; copy is what the code actually reads. Two arrays, one extra field per&#10;register, and the GC problem disappears.&lt;/p&gt;&#10;&lt;p&gt;The comment &amp;ldquo;Fields above this point are known to assembly&amp;rdquo; is the&#10;compiler-ABI contract: the assembly in &lt;code&gt;src/runtime/asm_amd64.s&lt;/code&gt; and the&#10;&lt;code&gt;RegArgs&lt;/code&gt; struct layout must agree, and the compiler enforces it.&lt;/p&gt;&#10;&lt;h2 id="how-many-registers-per-architecture"&gt;How many registers, per architecture&lt;/h2&gt;&#10;&lt;p&gt;The register counts are per-architecture constants:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/internal/abi/abi_amd64.go:7&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// See abi_generic.go.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;IntArgRegs&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 class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// X0 -&amp;gt; X14.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;FloatArgRegs&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 class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// We use SSE2 registers which support 64-bit float operations.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;EffectiveFloatRegSize&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 class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;&#10;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/internal/abi/abi_arm64.go:7&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// See abi_generic.go.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// R0 - R15.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;IntArgRegs&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 class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// F0 - F15.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;FloatArgRegs&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 class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;EffectiveFloatRegSize&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 class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;&#10;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;And for platforms that did not get the register ABI, there is a graceful&#10;degradation path:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;go&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &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="c1"&gt;// src/internal/abi/abi_generic.go:5&lt;/span&gt;&lt;span class="w"&gt;&#10;&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:build !goexperiment.regabiargs &amp;amp;&amp;amp; !amd64 &amp;amp;&amp;amp; !arm64 &amp;amp;&amp;amp; !loong64 &amp;amp;&amp;amp; !ppc64 &amp;amp;&amp;amp; !ppc64le &amp;amp;&amp;amp; !riscv64 &amp;amp;&amp;amp; !s390x&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;abi&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ABI-related constants.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// In the generic case, these are all zero&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// which lets them gracefully degrade to ABI0.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// IntArgRegs is the number of registers dedicated&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// to passing integer argument values. Result registers are identical&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// to argument registers, so this number is used for those too.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;IntArgRegs&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 class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// FloatArgRegs is the number of registers dedicated&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// to passing floating-point argument values. Result registers are&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// identical to argument registers, so this number is used for&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// those too.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;FloatArgRegs&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 class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// EffectiveFloatRegSize describes the width of floating point&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// registers on the current platform from the ABI&amp;#39;s perspective.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Since Go only supports 32-bit and 64-bit floating point primitives,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// this number should be either 0, 4, or 8. 0 indicates no floating&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// point registers for the ABI or that floating point values will be&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// passed via the softfloat ABI.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// For platforms that support larger floating point register widths,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// such as x87&amp;#39;s 80-bit &amp;#34;registers&amp;#34; (not that we support x87 currently),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// use 8.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;EffectiveFloatRegSize&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 class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zero registers means &amp;ldquo;everything goes on the stack&amp;rdquo; — ABI0 behaviour, with&#10;no separate code path. The whole ABI machinery collapses to a no-op on&#10;platforms that did not adopt it. That is the kind of design decision that&#10;makes a big change shippable: the fallback is not a maintenance burden, it&#10;is the absence of one.&lt;/p&gt;&#10;&lt;h2 id="why-the-register-abi-was-worth-doing"&gt;Why the register ABI was worth doing&lt;/h2&gt;&#10;&lt;p&gt;The register ABI is not just about saving a few stores. It removes an entire&#10;class of work from every call:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;No argument shuffling through the stack.&lt;/strong&gt; An old-style call wrote each&#10;argument to a stack slot, then the callee read it back. With registers,&#10;that round trip disappears for the first nine integer arguments.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;No callee-save register discipline.&lt;/strong&gt; Because there are no callee-save&#10;registers, there is no push/pop prologue epilogue for the compiler to&#10;emit.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;g&lt;/code&gt; in a register.&lt;/strong&gt; The goroutine pointer is available without a TLS&#10;lookup. This is the single biggest win: it makes &lt;code&gt;runtime.getg&lt;/code&gt; — called&#10;by the scheduler, by the allocator, by anything that touches the heap —&#10;essentially free.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;A fixed zero register.&lt;/strong&gt; Frame zeroing gets cheaper.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Better register allocation.&lt;/strong&gt; The SSA backend&amp;rsquo;s register allocator&#10;starts with the argument registers already live, which gives it a&#10;natural place to put incoming values.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The costs are equally real:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Assembly became harder to write.&lt;/strong&gt; You now have to know which ABI you&#10;are targeting, and mixing them requires the wrappers.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The ABI can change.&lt;/strong&gt; ABIInternal is explicitly unstable. A Go release&#10;can change how arguments are passed, and your assembly — if it targets&#10;ABIInternal — breaks. This is why the tree pushes assembly authors to&#10;ABI0.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Debuggers have to know about it.&lt;/strong&gt; A stack frame in ABIInternal is not&#10;laid out like an ABI0 frame, so unwinding code has to be ABI-aware.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="trade-offs"&gt;Trade-offs&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Register ABI vs stack ABI.&lt;/strong&gt; Registers win on the call overhead and on&#10;&lt;code&gt;g&lt;/code&gt; access. The stack wins on ABI stability and on debuggability. Go took&#10;both: registers for Go code, stack for assembly.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Common ABI vs platform ABI.&lt;/strong&gt; Go invented its own convention rather than&#10;using the platform&amp;rsquo;s (System V AMD64, AAPCS64). The cost is that Go&#10;assembly is not compatible with anything else, and every call crossing the&#10;Go/C boundary needs a wrapper. The benefit is uniformity: one spec, one&#10;implementation, and the same register-assignment algorithm everywhere, so a&#10;compiler change fixes all platforms at once.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;No callee-save registers.&lt;/strong&gt; This is the most counter-intuitive choice in&#10;the whole design, and the one that pays off the most on modern hardware.&#10;The cost is reserved spill space in every frame — memory that is often never&#10;touched. The benefit is a call sequence with nothing to save and nothing to&#10;restore.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Fixed registers.&lt;/strong&gt; R14 for &lt;code&gt;g&lt;/code&gt; and X15 for zero cost two registers out of&#10;the general pool on amd64, which is why only nine integer registers remain&#10;for arguments. The benefit is that the two most-frequently-needed values are&#10;free.&lt;/p&gt;&#10;&lt;h2 id="caveats"&gt;Caveats&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;You almost certainly should not write assembly.&lt;/strong&gt; If you do, target&#10;&lt;strong&gt;ABI0&lt;/strong&gt;, not ABIInternal. ABI0 is stable across Go releases; ABIInternal is&#10;not. The tree says so explicitly, and the &lt;code&gt;//go:linkname&lt;/code&gt; &amp;ldquo;hall of shame&amp;rdquo;&#10;comments in &lt;code&gt;src/runtime/map.go&lt;/code&gt; are a good illustration of how much&#10;trouble hand-written linkage causes.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Register assignment is not free of surprises.&lt;/strong&gt; Arrays longer than one&#10;element go on the stack. Structs are flattened field-by-field, so a struct&#10;with a &lt;code&gt;[2]uintptr&lt;/code&gt; field is stack-assigned entirely. A function with ten&#10;integer arguments spills the last one. If you are reasoning about the ABI&#10;by hand, read the algorithm in &lt;code&gt;src/cmd/compile/abi-internal.md&lt;/code&gt; rather than&#10;guessing.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;Ptrs&lt;/code&gt; duplication in &lt;code&gt;RegArgs&lt;/code&gt; exists for the GC.&lt;/strong&gt; If you write&#10;assembly that manipulates &lt;code&gt;RegArgs&lt;/code&gt;, you must keep the &lt;code&gt;Ints&lt;/code&gt; and &lt;code&gt;Ptrs&lt;/code&gt;&#10;copies consistent, or the GC will miss a live pointer.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;ABI changes are visible in profiles.&lt;/strong&gt; When Go changed the ABI, function&#10;entry costs moved. A profile captured before and after a Go upgrade is not&#10;comparable at the level of individual function entry costs, even though the&#10;code is identical.&lt;/p&gt;&#10;&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;The one thing to remember:&lt;/strong&gt; Go has two ABIs. ABI0 (stack-based, stable,&#10;for assembly) and ABIInternal (register-based, unstable, for Go code), and&#10;the compiler generates transparent wrappers between them so you never have&#10;to think about it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;The part people get wrong:&lt;/strong&gt; the fixed registers. On amd64, &lt;code&gt;R14&lt;/code&gt; holds&#10;the goroutine pointer and &lt;code&gt;X15&lt;/code&gt; is always zero, in &lt;em&gt;every&lt;/em&gt; function. That is&#10;why the runtime can switch goroutines with two instructions and why frame&#10;zeroing is cheap. If you are reading runtime assembly and wondering why&#10;&lt;code&gt;XORPS X15, X15&lt;/code&gt; shows up after every context switch, that is the answer.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;What to go do about it:&lt;/strong&gt; if you are writing assembly, read&#10;&lt;code&gt;src/cmd/compile/abi-internal.md&lt;/code&gt; end to end and target ABI0. If you are&#10;just writing Go, the practical takeaways are that calls are cheaper than they&#10;used to be, &lt;code&gt;g&lt;/code&gt; is free, and the only reason to care about any of this is&#10;when you are benchmarking across Go versions.&lt;/p&gt;&#10;&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;src/cmd/compile/abi-internal.md&lt;/code&gt; — the full ABIInternal specification:&#10;memory layout, the register assignment algorithm, per-architecture&#10;register maps, and the rationale for every choice.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/cmd/internal/obj/link.go&lt;/code&gt; — the &lt;code&gt;ABI&lt;/code&gt; type, &lt;code&gt;ABI0&lt;/code&gt;, &lt;code&gt;ABIInternal&lt;/code&gt;,&#10;and the comment explaining why &lt;code&gt;ABI0&lt;/code&gt; must be zero.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/internal/abi/abi.go&lt;/code&gt; — the &lt;code&gt;RegArgs&lt;/code&gt; struct, the assembly-facing&#10;view of the register file, including the &lt;code&gt;Ptrs&lt;/code&gt; GC trick.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/internal/abi/abi_amd64.go&lt;/code&gt;, &lt;code&gt;abi_arm64.go&lt;/code&gt;, &lt;code&gt;abi_generic.go&lt;/code&gt; — the&#10;per-architecture register counts and the ABI0 fallback.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/cmd/compile/internal/ssagen/abi.go&lt;/code&gt; — ABI wrapper generation, the&#10;&lt;code&gt;NOSPLIT&lt;/code&gt; discussion, and the tail-call carve-outs.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/runtime/asm_amd64.s&lt;/code&gt; — the runtime&amp;rsquo;s own assembly, and a live&#10;example of the &lt;code&gt;&amp;lt;ABIInternal&amp;gt;&lt;/code&gt; symbol suffix.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;src/cmd/compile/internal/noder/lex.go&lt;/code&gt; — where the &lt;code&gt;//go:registerparams&lt;/code&gt;&#10;pragma is parsed, and the TODO noting it is not yet honoured.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</content:encoded></item></channel></rss>