<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Chapter 15 The Compiler Pipeline on Go: Under the Hood</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/</link><description>Recent content in Chapter 15 The Compiler Pipeline on Go: Under the Hood</description><generator>Hugo</generator><language>en</language><atom:link href="https://golang.design/under-the-hood/en/part5toolchain/ch15compile/index.xml" rel="self" type="application/rss+xml"/><item><title>15.1 Lexing and Grammar</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/parse/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/parse/</guid><description>&lt;h1 id="151-lexing-and-grammar"&gt;15.1 Lexing and Grammar&lt;/h1&gt;
&lt;p&gt;The first stop of compilation is turning source text into a structured &lt;strong&gt;abstract syntax tree&lt;/strong&gt; (AST). This takes
&lt;strong&gt;lexical analysis&lt;/strong&gt; (slicing a character stream into tokens) and &lt;strong&gt;syntactic analysis&lt;/strong&gt; (organizing tokens into a tree
according to the grammar). &lt;a href="https://golang.design/under-the-hood/en/part1overview/ch03life/compile/"&gt;3.2&lt;/a&gt; surveyed the whole pipeline from above; this
section looks only at its front end, and at why Go&amp;rsquo;s grammar was designed to be so &amp;ldquo;easy to parse&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The package that carries out these two steps is a self-contained part of the compiler, &lt;code&gt;cmd/compile/internal/syntax&lt;/code&gt;. It
is built from two instruments: the &lt;strong&gt;scanner&lt;/strong&gt; reads characters and emits a token stream; the &lt;strong&gt;parser&lt;/strong&gt; consumes tokens
in a &lt;strong&gt;recursive descent&lt;/strong&gt; fashion and builds the syntax tree. The package&amp;rsquo;s own comments even note with some pride that
several of its files, &lt;code&gt;scanner.go&lt;/code&gt;, &lt;code&gt;source.go&lt;/code&gt;, and &lt;code&gt;tokens.go&lt;/code&gt;, do not depend on the rest of the compiler and can be
compiled on their own into a standalone library. The reason lexing and grammar can be carved out this cleanly lies in the
simplicity of the Go grammar itself.&lt;/p&gt;</description></item><item><title>15.2 Intermediate Representation</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/ssa/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/ssa/</guid><description>&lt;h1 id="152-intermediate-representation"&gt;15.2 Intermediate Representation&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././parse"&gt;15.1&lt;/a&gt; turned source code into a syntax tree (AST). The AST faithfully records the structure the programmer wrote down: variables, scopes, the nesting of expressions. But the moment we want to optimize, this structure sits too &amp;ldquo;high.&amp;rdquo; Consider the most unremarkable line, &lt;code&gt;x = x + 1&lt;/code&gt;: in the AST, the &lt;code&gt;x&lt;/code&gt; on the left and the &lt;code&gt;x&lt;/code&gt; on the right are the same name, and if the compiler wants to know &amp;ldquo;which assignment produced the value of the &lt;code&gt;x&lt;/code&gt; used here,&amp;rdquo; it has to repeatedly perform scope lookups and data-flow analysis. Names get shadowed, scopes nest, an assignment wipes out the old value, and all of this makes &amp;ldquo;where a value comes from and where it goes,&amp;rdquo; which ought to be the most basic thing, murky. What the optimizer wants most is exactly to lay the data flow out in the open.&lt;/p&gt;</description></item><item><title>15.3 The Optimizer</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/optimize/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/optimize/</guid><description>&lt;h1 id="153-the-optimizer"&gt;15.3 The Optimizer&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././ssa"&gt;15.2&lt;/a&gt; lowered the front end&amp;rsquo;s syntax tree down to the SSA intermediate
representation and explained why SSA&amp;rsquo;s &amp;ldquo;each variable is assigned exactly once&amp;rdquo;
makes the optimization passes both accurate and fast to write. This section
continues from there: on top of this representation, what optimizations does the
compiler actually run, and why precisely these few.&lt;/p&gt;
&lt;p&gt;To read the Go optimizer well, we first have to read its disposition. Faced with
the same task of turning a high-level language into machine code, GCC and LLVM
are willing to spend seconds or even tens of seconds kneading a function over and
over to win the last few percent of run-time performance. Go takes a different
road: it does only the batch of optimizations with the best cost-to-benefit
ratio, and hands the time it saves back to compilation speed
(&lt;a href="https://golang.design/under-the-hood/en/part1overview/ch01intro/history/"&gt;1.1&lt;/a&gt;). This is not a shortfall in
ability but a clear-eyed ordering of values, and this section will return to that
red line at the end. We first look at the optimizations Go is willing to do, then
at Profile-Guided Optimization (PGO), introduced in Go 1.21, which pushes
optimization from &amp;ldquo;static guessing&amp;rdquo; toward &amp;ldquo;data-driven.&amp;rdquo;&lt;/p&gt;</description></item><item><title>15.4 The Pointer Checker</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/unsafe/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/unsafe/</guid><description>&lt;h1 id="154-the-pointer-checker"&gt;15.4 The Pointer Checker&lt;/h1&gt;
&lt;p&gt;Go is a memory-safe language. In ordinary code, the type system guarantees that every pointer refers to a legal object of its declared type, garbage collection (&lt;a href="https://golang.design/under-the-hood/en/part4memory/ch13gc/"&gt;13&lt;/a&gt;) guarantees that an object is not reclaimed while it is still referenced, and the runtime keeps out-of-bounds accesses behind bounds checks. These guarantees are not free. They rest on the premise that the compiler always knows the type and layout of every value. Yet there is always a small set of scenarios that need to step outside this system: interoperating with C (&lt;a href="https://golang.design/under-the-hood/en/part5toolchain/ch15compile/cgo/"&gt;15.6&lt;/a&gt;) means interpreting a span of bytes according to C&amp;rsquo;s memory layout, laying out a system structure for the operating system means placing bytes one by one, and reinterpreting a &lt;code&gt;[]byte&lt;/code&gt; as a &lt;code&gt;string&lt;/code&gt; with zero copies (&lt;a href="https://golang.design/under-the-hood/en/part2lang/ch05data/slice/"&gt;5.1&lt;/a&gt;) means letting two types share the same underlying memory. Go leaves an escape hatch for these scenarios: the &lt;code&gt;unsafe&lt;/code&gt; package.&lt;/p&gt;</description></item><item><title>15.5 Escape Analysis</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/escape/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/escape/</guid><description>&lt;h1 id="155-escape-analysis"&gt;15.5 Escape Analysis&lt;/h1&gt;
&lt;p&gt;Go programmers never manually decide whether a variable lives on the stack or the heap; the compiler&amp;rsquo;s &lt;strong&gt;escape analysis&lt;/strong&gt; does it automatically. It is the unsung hero of Go&amp;rsquo;s performance: keeping as many objects on the stack as possible greatly lightens the load on the garbage collector (&lt;a href="https://golang.design/under-the-hood/en/part4memory/ch13gc/"&gt;13 Garbage Collection&lt;/a&gt;). This section explains how it decides, how it is implemented, and why it matters.&lt;/p&gt;
&lt;h2 id="1551-escape-deciding-stack-or-heap"&gt;15.5.1 Escape: Deciding Stack or Heap&lt;/h2&gt;
&lt;p&gt;The core question: should a variable be allocated on the &lt;strong&gt;stack&lt;/strong&gt; (vanishing automatically when the function returns, at zero GC cost) or on the &lt;strong&gt;heap&lt;/strong&gt; (with an indefinite lifetime, managed by the GC)? The criterion is &lt;strong&gt;lifetime&lt;/strong&gt;: if a reference to a variable may still be used after the function returns, it cannot live on the stack (the stack frame is destroyed on return) and must &lt;strong&gt;escape&lt;/strong&gt; to the heap. Escape analysis answers this question statically, deciding whether a variable&amp;rsquo;s address can leave the scope of the function it lives in.&lt;/p&gt;</description></item><item><title>15.6 cgo</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/cgo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/cgo/</guid><description>&lt;h1 id="156-cgo"&gt;15.6 cgo&lt;/h1&gt;
&lt;p&gt;&amp;ldquo;cgo is not Go.&amp;rdquo; This is the verdict Rob Pike handed down on cgo in a blog post. It names a thing that is easy to overlook: when you write &lt;code&gt;import &amp;quot;C&amp;quot;&lt;/code&gt; in a Go source file and then write a single line of &lt;code&gt;C.foo()&lt;/code&gt;, you have already stepped out of the world that the Go language drew for you and into another world, one made of C&amp;rsquo;s ABI, C&amp;rsquo;s stack, and C&amp;rsquo;s memory model. cgo is the bridge between these two worlds. The bridge is useful, but crossing it costs a toll, and the toll is not cheap.&lt;/p&gt;</description></item><item><title>15.7 Past, Present, and Future</title><link>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/future/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part5toolchain/ch15compile/future/</guid><description>&lt;h1 id="157-past-present-and-future"&gt;15.7 Past, Present, and Future&lt;/h1&gt;
&lt;p&gt;The compiler is the part of the Go toolchain that changes most often, yet remains the most transparent to users. Take the same source code, change nothing, recompile it with a new version, and it often comes out faster, smaller, and better, without you ever knowing what happened in between. This section pulls the camera back to look at the road the compiler itself has traveled, then at what it is doing now and where it is heading next. Running through all of it is one unchanging order of priorities: &lt;strong&gt;compilation speed comes first, quality of generated code second, and both are traded off under the constraint of being engineerable&lt;/strong&gt; (&lt;a href="https://golang.design/under-the-hood/en/part1overview/ch01intro/history/"&gt;1.1&lt;/a&gt;).&lt;/p&gt;</description></item></channel></rss>