# Understanding Go's Compilation Process

Golang is a programming language known for its simplicity, efficiency, and fast compilation. One of the key aspects of Go's design is its straightforward and predictable compilation process. In this article, we will delve into the intricacies of how Go code is transformed from human-readable source code into machine-executable programs.

Go uses a unique compilation model that differs from some other languages. While the exact details can vary between different Go compiler implementations, the process generally follows these steps:

  1. Scanning and Parsing:
    • The first step in the compilation process is scanning and parsing the source code. The Go compiler reads your source code and converts it into an abstract syntax tree (AST).
    • This phase involves lexing (breaking down the code into tokens) and parsing (constructing the AST) to understand the code's structure.
  2. Type Checking:
    • Once the AST is constructed, the Go compiler performs type checking. It verifies that your code adheres to Go's type system and rules, ensuring type safety.
    • Go's strong typing system helps catch many errors at this stage, making it less likely for runtime errors to occur.
  3. SSA Generation:
    • Go introduces a unique step that involves the construction of SSA (Static Single Assignment) form. SSA is an intermediate representation of the code that simplifies and optimizes various operations.
  4. Escape Analysis:
    • The escape analysis is a special step in Go's compilation process. It determines whether variables need to escape from the function where they are defined, impacting memory allocation.
  5. Inlining:
    • Inlining involves expanding function calls at the call sites. This can lead to better performance by reducing the overhead of function calls.
  6. Code Generation:
    • After all optimizations, the Go compiler generates machine code specifically targeted for the architecture you are compiling for (e.g., x86, ARM).
  7. Linking:
    • Go has a built-in linker that combines the generated machine code with runtime libraries to produce a standalone executable.

​ We can easily find the main packages from the Go compiler in SDK/src/cmd/compile, so we can read the README document to understand the compilation process.

​ And Go provides a way for us can see the process, let's try it. Now we create a demo first, the name of the file is "sea_demo.go".

package demo

type SSADemo struct {
}

func (*SSADemo) Test1() int {
	var a int = 1
	b := a + 2
	return b
}

func Test2() int {
	var a int = 1
	b := a + 2
	return b
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

​ Then we can go into the path of the file from the terminal, and execute the command below:

GOSSAFUNC='(*SSADemo).Test1' go build ssa_demo.go or GOSSAFUNC='Test2' go build ssa_demo.go.

​ It will generate the ssa.html, we can see the process of the compiler generating the ssa from the source code. Open the ssa.html, it is like the picture below:

ssahtml

Source code

/Users/anson/Documents/workspace/golang-demo/channel/ssa_demo.go
func Test2() int {
	var a int = 1
	b := a + 2
	return b
}
1
2
3
4
5
6

AST

buildssa-enter
buildssa-body
. DCL # ssa_demo.go:13:6
. . NAME-channel.a esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:13:6
. AS tc(1) # ssa_demo.go:13:6
. . NAME-channel.a esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:13:6
. . LITERAL-1 int tc(1) # ssa_demo.go:13:14
. DCL # ssa_demo.go:14:2
. . NAME-channel.b esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:14:2
. AS Def tc(1) # ssa_demo.go:14:4
. . NAME-channel.b esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:14:2
. . ADD int tc(1) # ssa_demo.go:14:9
. . . NAME-channel.a esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:13:6
. . . LITERAL-2 int tc(1) # ssa_demo.go:14:11
. RETURN tc(1) # ssa_demo.go:15:2
. RETURN-Results
. . AS tc(1) # ssa_demo.go:15:2
. . . NAME-channel.~r0 esc(no) Class:PPARAMOUT Offset:0 OnStack int tc(1) # ssa_demo.go:12:14
. . . NAME-channel.b esc(no) Class:PAUTO Offset:0 OnStack int tc(1) # ssa_demo.go:14:2
buildssa-exit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Generating SSA

b1:
	v1 (?) = InitMem <mem>
	v2 (?) = SP <uintptr>
	v3 (?) = SB <uintptr>
	v4 (?) = LocalAddr <*int> {~r0} v2 v1
	v5 (?) = Const64 <int> [0]
	v6 (?) = Const64 <int> [1] (a[int])
	v7 (?) = Const64 <int> [2]
	v8 (14) = Add64 <int> v6 v7 (b[int])
	v9 (15) = MakeResult <int,mem> v8 v1
Ret v9 (+15)
name a[int]: v6
name b[int]: v8
1
2
3
4
5
6
7
8
9
10
11
12
13

Dead code elimination

b1:-
v1 (?) = InitMem <mem>
v6 (?) = Const64 <int> [1] (a[int])
v7 (?) = Const64 <int> [2]
v8 (+14) = Add64 <int> v6 v7 (b[int])
v9 (+15) = MakeResult <int,mem> v8 v1
Ret v9 (+15)
name a[int]: v6
name b[int]: v8
1
2
3
4
5
6
7
8
9

Generating machine code

00000 (12) TEXT "".Test2(SB), ABIInternal
00001 (12) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
00002 (12) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
v4
00003 (+15) MOVL $3, AX
b1
00004 (15) RET
00005 (?) END
1
2
3
4
5
6
7
8
Last update: 11/9/2023, 11:09:46 PM