Go: Introduction to the Escape Analysis

Vincent Blanchon
Aug 6, 2020 · 5 min read
Image for post
Image for post
Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

ℹ️ This article is based on Go 1.13.

The escape analysis one of the phases of the Go compiler. It analyses the source code and determines what variables should be allocated on the stack and which ones should escape to the heap.

Static analysis

Go statically defines what should be heap or stack-allocated during the compilation phase. This analysis is available via the flag -gcflags="-m" when compiling and/or running your code thanks to go run or go build. Here is a simple example:

Running the escape analysis shows us tmp escapes to the heap:

The first step of the static analysis is building the Abstract Syntax Tree of the source code, allowing Go to understand where assignments and allocations are done along with variables addressing and dereferencing. Here is an example of the previous code:

Image for post
Image for post
Abstract Syntax Tree of the source code

However, for the escape analysis, we can remove the noises produced by the AST and get a simpler version of this tree:

Image for post
Image for post
Simplified AST

Since the tree exposes the defined variables — represented by NAME — and operations on pointer — represented by ADDR or DEREF, it gives all information to Go to perform its escape analysis. Once the tree is built and the functions and parameters parsed, Go can now apply the escape analysis logic to see what should be heap or stack-allocated.

Outlive the stack frame

While running the escape analysis and traversing the functions from AST graph — marked, Go looks for variables that outlive the current stack frame and therefore need to be heap-allocated. Let’s first define what outlive means by representing the stack frame of the previous example if there is no heap allocation. Here is the stack growing downward when calling the two functions:

Image for post
Image for post
Memory stack

Then, any variable created in the function getRandom will not be accessible when the stack frame of the function becomes invalid, i.e., when the program will return from the function:

Image for post
Image for post
Memory stack

In this case, the variable num cannot point to a variable allocated on a previous stack. In this case, Go must allocate the variable on the heap, making sure it will outlive the stack frame:

Image for post
Image for post
Heap allocation

The variable tmp now contains the address of the allocated memory to the stack and can be safely copied from a stack frame to another one. However, the returned values are not the only ones that can outlive. Here are the rules:

  • Any returned value outlives the function since the called function does not know the value.
  • Variables declared outside a loop outlive the assignment within the loop:
  • Variables declared outside a closure outlive the assignment within the closure:

The second part of the escape analysis consist of determining how it manipulates the pointer, helping to understand what could stay on the stack.

Addressing and dereference

Building a weighted graph representing addressing/dereference counts allows Go to optimize the stack allocation. Let’s analyze an example to understand how it works:

Running the escape analysis shows that the allocation escapes to the heap:

Here is the AST code generated in a simpler version:

Image for post
Image for post
Simplified AST

Go defines the allocation by building the weighted graph. Each dereferencing, represented by * in the code or DEREF in the nodes, increase the weight by 1. Each addressing operation, represented by & in the code or ADDR in the nodes, decrease the weight by 1.

Here is the sequence defined by the escape analysis:

Each variable ending up with a negative count escapes to the heap if it outlives the current stack frame. Since the returned value outlives the stack frame of its function and gets a negative count through its edges, the allocation escapes to the heap.

Building this graph allows Go to understand which variable should stay on the stack despite it outliving the stack. Here is another basic example:

The variable n1 outlives the stack frame, but its weight is not negative since func1 does not refer to its address at any place. However, n2 outlives and gets dereferenced; Go can safely allocate it on the heap.

A Journey With Go Language Programming

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store