今天我们一起来研究 Go 1.11 的编译器,以及它将 Go 程序代码编译成可执行文件的过程。以便了解我们日常使用的工具是如何工作的。
本文还会带你了解 Go 程序为什么这么快,以及编译器在这中间起到了什么作用。
首先,编译器的三个阶段:
tokenparserparsertokenAST(抽象语法树)AST
注意:下面使用的代码包(go/scanner,go/parser,go/token,go/ast)主要是让我们可以方便地对 Go 代码进行解析和生成,做出更有趣的事情。但是 Go 本身的编译器并不是用这些代码包实现的。
扫描代码,进行词法分析
tokentokentoken"package""main""func"token
tokentoken
package main
import (
"fmt"
"go/scanner"
"go/token"
)
func main() {
src := []byte(`
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
`)
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
s.Init(file, src, nil, 0)
for {
pos, tok, lit := s.Scan()
fmt.Printf("%-6s%-8s%q\n", fset.Position(pos), tok, lit)
if tok == token.EOF {
break
}
}
}
tokenscan.ScannerScan()token
输出:
2:1 package "package"
2:9 IDENT "main"
2:13 ; "\n"
4:1 import "import"
4:8 STRING "\"fmt\""
4:13 ; "\n"
6:1 func "func"
6:6 IDENT "main"
6:10 ( ""
6:11 ) ""
6:13 { ""
7:2 IDENT "fmt"
7:5 . ""
7:6 IDENT "Println"
7:13 ( ""
7:14 STRING "\"Hello, world!\""
7:29 ) ""
7:30 ; "\n"
8:1 } ""
8:2 ; "\n"
8:3 EOF ""
2:1packagetokenpackage"package"Scanner\n;Scanner
语法分析
token抽象语法树(AST)ASTAST
AST
package main
import (
"go/ast"
"go/parser"
"go/token"
"log"
)
func main() {
src := []byte(`
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
`)
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
log.Fatal(err)
}
ast.Print(fset, file)
}
输出:
0 *ast.File {
1 . Package: 2:1
2 . Name: *ast.Ident {
3 . . NamePos: 2:9
4 . . Name: "main"
5 . }
6 . Decls: []ast.Decl (len = 2) {
7 . . 0: *ast.GenDecl {
8 . . . TokPos: 4:1
9 . . . Tok: import
10 . . . Lparen: -
11 . . . Specs: []ast.Spec (len = 1) {
12 . . . . 0: *ast.ImportSpec {
13 . . . . . Path: *ast.BasicLit {
14 . . . . . . ValuePos: 4:8
15 . . . . . . Kind: STRING
16 . . . . . . Value: "\"fmt\""
17 . . . . . }
18 . . . . . EndPos: -
19 . . . . }
20 . . . }
21 . . . Rparen: -
22 . . }
23 . . 1: *ast.FuncDecl {
24 . . . Name: *ast.Ident {
25 . . . . NamePos: 6:6
26 . . . . Name: "main"
27 . . . . Obj: *ast.Object {
28 . . . . . Kind: func
29 . . . . . Name: "main"
30 . . . . . Decl: *(obj @ 23)
31 . . . . }
32 . . . }
33 . . . Type: *ast.FuncType {
34 . . . . Func: 6:1
35 . . . . Params: *ast.FieldList {
36 . . . . . Opening: 6:10
37 . . . . . Closing: 6:11
38 . . . . }
39 . . . }
40 . . . Body: *ast.BlockStmt {
41 . . . . Lbrace: 6:13
42 . . . . List: []ast.Stmt (len = 1) {
43 . . . . . 0: *ast.ExprStmt {
44 . . . . . . X: *ast.CallExpr {
45 . . . . . . . Fun: *ast.SelectorExpr {
46 . . . . . . . . X: *ast.Ident {
47 . . . . . . . . . NamePos: 7:2
48 . . . . . . . . . Name: "fmt"
49 . . . . . . . . }
50 . . . . . . . . Sel: *ast.Ident {
51 . . . . . . . . . NamePos: 7:6
52 . . . . . . . . . Name: "Println"
53 . . . . . . . . }
54 . . . . . . . }
55 . . . . . . . Lparen: 7:13
56 . . . . . . . Args: []ast.Expr (len = 1) {
57 . . . . . . . . 0: *ast.BasicLit {
58 . . . . . . . . . ValuePos: 7:14
59 . . . . . . . . . Kind: STRING
60 . . . . . . . . . Value: "\"Hello, world!\""
61 . . . . . . . . }
62 . . . . . . . }
63 . . . . . . . Ellipsis: -
64 . . . . . . . Rparen: 7:29
65 . . . . . . }
66 . . . . . }
67 . . . . }
68 . . . . Rbrace: 8:1
69 . . . }
70 . . }
71 . }
72 . Scope: *ast.Scope {
73 . . Objects: map[string]*ast.Object (len = 1) {
74 . . . "main": *(obj @ 27)
75 . . }
76 . }
77 . Imports: []*ast.ImportSpec (len = 1) {
78 . . 0: *(obj @ 12)
79 . }
80 . Unresolved: []*ast.Ident (len = 1) {
81 . . 0: *(obj @ 46)
82 . }
83 }
Decls
main函数Typefmt.Println("Hello, world!")
fmt.PrintlnASTExprStmt
ExprStmtCallExprFunArgsFunSelectorExprfmtPrintlnASTfmtAST
ArgsSTRINGBasicLit
ASTASTInspect
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
)
func main() {
src := []byte(`
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
`)
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
fmt.Println(err)
}
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
printer.Fprint(os.Stdout, fset, call.Fun)
return false
})
}
输出:
fmt.Println
*ast.CallExprFun
ASTGOPATH
代码生成
AST
ASTSSA
SSA
SSA
package main
import "fmt"
func main() {
fmt.Println(2)
}
如你所见,此程序只有一个函数和一个导入。它会在运行时打印 2。但是,此例足以让我们了解SSA。
SSAGOSSAFUNCmain
GOSSAFUNC=main GOOS=linux GOARCH=amd64 go build -gcflags -S main.go
SSAssa.html
ssa.htmlstartASTSSAlowerSSASSAgenssa
start
b1:
v1 = InitMem <mem>
v2 = SP <uintptr>
v3 = SB <uintptr>
v4 = ConstInterface <interface {}>
v5 = ArrayMake1 <[1]interface {}> v4
v6 = VarDef <mem> {.autotmp_0} v1
v7 = LocalAddr <*[1]interface {}> {.autotmp_0} v2 v6
v8 = Store <mem> {[1]interface {}} v7 v5 v6
v9 = LocalAddr <*[1]interface {}> {.autotmp_0} v2 v8
v10 = Addr <*uint8> {type.int} v3
v11 = Addr <*int> {"".statictmp_0} v3
v12 = IMake <interface {}> v10 v11
v13 = NilCheck <void> v9 v8
v14 = Const64 <int> [0]
v15 = Const64 <int> [1]
v16 = PtrIndex <*interface {}> v9 v14
v17 = Store <mem> {interface {}} v16 v12 v8
v18 = NilCheck <void> v9 v17
v19 = IsSliceInBounds <bool> v14 v15
v24 = OffPtr <*[]interface {}> [0] v2
v28 = OffPtr <*int> [24] v2
If v19 → b2 b3 (likely) (line 6)
b2: ← b1
v22 = Sub64 <int> v15 v14
v23 = SliceMake <[]interface {}> v9 v22 v22
v25 = Copy <mem> v17
v26 = Store <mem> {[]interface {}} v24 v23 v25
v27 = StaticCall <mem> {fmt.Println} [48] v26
v29 = VarKill <mem> {.autotmp_0} v27
Ret v29 (line 7)
b3: ← b1
v20 = Copy <mem> v17
v21 = StaticCall <mem> {runtime.panicslice} v20
Exit v21 (line 6)
SSA
vb2b3b1If v19 → b2 b3 (likely)v19IsSliceInBounds v14 v15IsSliceInBounds0 <= arg0 <= arg1v14v15v14 = Const64 [0]0 <= 0 <= 1
optv19ConstBool [true]opt deadcodeb3b3
SSA机器特定的SSAlowerlowered deadcodelower
b1:
BlockInvalid (6)
b2:
v2 (?) = SP <uintptr>
v3 (?) = SB <uintptr>
v10 (?) = LEAQ <*uint8> {type.int} v3
v11 (?) = LEAQ <*int> {"".statictmp_0} v3
v15 (?) = MOVQconst <int> [1]
v20 (?) = MOVQconst <uintptr> [0]
v25 (?) = MOVQconst <*uint8> [0]
v1 (?) = InitMem <mem>
v6 (6) = VarDef <mem> {.autotmp_0} v1
v7 (6) = LEAQ <*[1]interface {}> {.autotmp_0} v2
v9 (6) = LEAQ <*[1]interface {}> {.autotmp_0} v2
v16 (+6) = LEAQ <*interface {}> {.autotmp_0} v2
v18 (6) = LEAQ <**uint8> {.autotmp_0} [8] v2
v21 (6) = LEAQ <**uint8> {.autotmp_0} [8] v2
v30 (6) = LEAQ <*int> [16] v2
v19 (6) = LEAQ <*int> [8] v2
v23 (6) = MOVOconst <int128> [0]
v8 (6) = MOVOstore <mem> {.autotmp_0} v2 v23 v6
v22 (6) = MOVQstore <mem> {.autotmp_0} v2 v10 v8
v17 (6) = MOVQstore <mem> {.autotmp_0} [8] v2 v11 v22
v14 (6) = MOVQstore <mem> v2 v9 v17
v28 (6) = MOVQstoreconst <mem> [val=1,off=8] v2 v14
v26 (6) = MOVQstoreconst <mem> [val=1,off=16] v2 v28
v27 (6) = CALLstatic <mem> {fmt.Println} [48] v26
v29 (5) = VarKill <mem> {.autotmp_0} v27
Ret v29 (+7)
v15 (?) = MOVQconst [1]v15MOVQconstConst64v15v15
ASTSSA
SSA
结论
Go 是一种非常高效且高性能的语言,由其编译器及其优化支撑。要了解有关 Go 编译器的更多信息,源代码的 README 是不错的选择。