内容概要
Golang为编译型语言,需要将源代码文件编译之后才能执行。可将Golang的编译过程视为构建Golang程序的第一步,本文结合Golang编译器的源码简单介绍Golang的编译流程。
Golang的自举
Golang从1.5版本开始实现了自举,所谓Golang的自举是指用Golang本身编写Golang编译器的源码,然后将这些源码文件编译即可得到Golang的编译器。下面简单介绍一下编程语言实现自举的流程:
如果要对编程语言B实现自举,需要完成以下步骤:
- 使用A语言(A语言的编译器的实现早于B语言)编写B语言编译器的源码,使用A语言的编译器编译这些源码文件得到B语言编译器对应的可执行文件F。
- 使用B语言本身编写B语言编译器的源码,使用可执行文件F编译这些源码文件得到可执行文件T。
经过上面的2个步骤后,编程语言B就实现了自举。
本文主要参考的Golang源码版本:go1.14 linux/amd64
Golang编译器的执行流程
Golang的编译器源码文件位置:$GOROOT/src/cmd/compile/,入口文件:$GOROOT/src/cmd/compile/internal/main.go
调用此包编译后的文件的命令:go tool compile ..., go run ...等。
$GOROOT/src/cmd/compile/internal/main.go:
func main() {
// disable timestamps for reproducible output
log.SetFlags(0)
log.SetPrefix("compile: ")
archInit, ok := archInits[objabi.GOARCH]
if !ok {
fmt.Fprintf(os.Stderr, "compile: unknown architecture %q\n", objabi.GOARCH)
os.Exit(2)
}
gc.Main(archInit)
gc.Exit(0)
}
显然主要的流程都在gc.Main中,该函数的主要功能为:通过命令行参数获取要编译的文件和配置,编译指定的文件。
$GOROOT/src/cmd/compile/internal/gc/main.go
//通过命令行参数获取要编译的文件和配置,编译指定的文件。
func Main(archInit func(*Arch)) {
... //读入命令行参数并进行相关初始化操作等
//对flag.Args()中给出的文件进行词法分析和语法分析,将每个源码文件对应的抽象语法树的根结点依次存储在xtop[0]...
lines := parseFiles(flag.Args())
... //阶段1:遍历抽象语法树,收集常量,函数等相关的类型信息。
... //阶段2:遍历抽象语法树,处理变量的赋值。
... //阶段3:遍历抽象语法树,对函数主体进行类型检查。
//检查map的key是否合法,合法的map的key必须是可比较的。
checkMapKeys()
... //阶段4:确定变量的捕获方式。
... //阶段5:遍历抽象语法树,处理内联函数的类型。
... //阶段6:遍历抽象语法树,进行逃逸分析。
... //阶段7:遍历抽象语法树,修改闭包主体,使之正确引用捕获的变量。
... //阶段8:编译每个源码文件的顶层函数。
//使用多个Goroutine并发的编译所有函数。
compileFunctions()
... //阶段9:检查外部依赖的声明。
//进一步检查map的key是否合法。
checkMapKeys()
... //将编译的结果写入磁盘文件。
//检查是否存在某些函数的栈帧过大。
for _, large := range largeStackFrames {
if large.callee != 0 {
yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20)
} else {
yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20)
}
}
... //输出编译期间遇到的语法错误等。
}
词法分析和语法分析
从上面的代码中可知,进行词法分析和语法分析的函数为:$GOROOT/src/cmd/compile/internal/gc路径下的parseFiles。
//对filenames中给出的文件进行词法分析和语法分析,将每个源码文件对应的抽象语法树的根结点依次
//存储在xtop[0]...,返回编译的所有文件的总行数。
func parseFiles(filenames []string) uint {
noders := make([]*noder, 0, len(filenames))
//限制并发访问的文件的个数。
sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
for _, filename := range filenames {
p := &noder{
basemap: make(map[*syntax.PosBase]*src.PosBase),
err: make(chan syntax.Error),
}
noders = append(noders, p)
go func(filename string) {
sem <- struct{}{}
defer func() { <-sem }()
defer close(p.err)
base := syntax.NewFileBase(filename)
f, err := os.Open(filename)
if err != nil {
p.error(syntax.Error{Pos: syntax.MakePos(base, 0, 0), Msg: err.Error()})
return
}
defer f.Close()
//对文件f进行词法分析和语法分析。
p.file, _ = syntax.Parse(base, f, p.error, p.pragma, syntax.CheckBranches)
}(filename)
}
var lines uint
for _, p := range noders {
for e := range p.err {
p.yyerrorpos(e.Pos, "%s", e.Msg)
}
p.node()
lines += p.file.Lines
//值为nil,可被GC回收。
p.file = nil
if nsyntaxerrors != 0 {
errorexit()
}
// Always run testdclstack here, even when debug_dclstack is not set, as a sanity measure.
testdclstack()
}
localpkg.Height = myheight
//返回编译的所有文件的总行数。
return lines
}
从上面的源码可看出,使用机器核心数+10个Goroutine进行输入文件的词法分析和语法分析。每个单独的文件使用syntax.Parse进行词法分析和语法分析,返回得到的抽象语法树,也即每个源文件对应一个抽象语法树,切片xtop存储所有抽象语法树的根,其元素数量等于命令行参数中输入的文件个数。
$GOROOT/src/cmd/compile/internal/syntax/syntax.go:
func Parse(base *PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, first error) {
defer func() {
if p := recover(); p != nil {
if err, ok := p.(Error); ok {
first = err
return
}
panic(p)
}
}()
var p parser
//初始化词法分析器
p.init(base, src, errh, pragh, mode)
//定位到源码文件中的第一个token
p.next()
return p.fileOrNil(), p.first
}
从上面的源码可以看出,主要的处理逻辑集中在函数fileOrNil()中。fileOrNil()返回源码文件生成的抽象语法树,具体的逻辑参见下面的注释。
$GOROOT/src/cmd/compile/internal/syntax/parser.go:
//返回p对应源码文件生成的抽象语法树
func (p *parser) fileOrNil() *File {
if trace {
defer p.trace("file")()
}
f := new(File)
f.pos = p.pos()
//博主添加的defer语句
defer func() {
if os.Getenv("ENABLED_DEBUG") == "true" {
spew.Config.Indent = "\t"
fmt.Printf("f:%v\n", spew.Sdump(f))
}
}()
//处理首行的包声明语句
if !p.got(_Package) {
p.syntaxError("package statement must be first")
return nil
}
f.PkgName = p.name()
p.want(_Semi)
//如果包声明语句存在错误, 则终止余下的语法分析流程
if p.first != nil {
return nil
}
//循环处理包导入语句
for p.got(_Import) {
f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
p.want(_Semi)
}
//循环处理顶层声明语句
for p.tok != _EOF {
switch p.tok {
case _Const:
//const定义的常量
p.next() //返回源码文件中的下一个token
f.DeclList = p.appendGroup(f.DeclList, p.constDecl)
case _Type:
//type定义的类型
p.next()
f.DeclList = p.appendGroup(f.DeclList, p.typeDecl)
case _Var:
//var定义的变量
p.next()
f.DeclList = p.appendGroup(f.DeclList, p.varDecl)
case _Func:
p.next()
//func定义的函数
if d := p.funcDeclOrNil(); d != nil {
f.DeclList = append(f.DeclList, d)
}
default:
if p.tok == _Lbrace && len(f.DeclList) > 0 && isEmptyFuncDecl(f.DeclList[len(f.DeclList)-1]) {
//函数体的'{'和函数声明不在同一行
p.syntaxError("unexpected semicolon or newline before {")
} else {
//函数体之外的非声明语句
p.syntaxError("non-declaration statement outside function body")
}
//不断推进p.tok的进度, 直到p.tok为_Const, _Type, _Var, _Func中的某一个(更具体的参见函数advance的定义)
p.advance(_Const, _Type, _Var, _Func)
continue
}
// Reset p.pragma BEFORE advancing to the next token (consuming ';')
// since comments before may set pragmas for the next function decl.
p.pragma = 0
if p.tok != _EOF && !p.got(_Semi) {
p.syntaxError("after top level declaration")
p.advance(_Const, _Type, _Var, _Func)
}
}
// p.tok == _EOF
f.Lines = p.source.line
return f
}
//仅在dcl表示函数声明语句且函数体为空时返回true
func isEmptyFuncDecl(dcl Decl) bool {
f, ok := dcl.(*FuncDecl)
return ok && f.Body == nil
}
//不断推进p.tok的进度(对应连续调用p.next())直到p.tok为followlist和stopset的并集中元素或者为_EOF, 如果
//p.tok当前位置不是在函数内部, 则不考虑stopset集合中的元素
func (p *parser) advance(followlist ...token) {
if trace {
p.print(fmt.Sprintf("advance %s", followlist))
}
//计算使用uint64表示集合的followset
//当集合中的元素个数较少时, 使用二进制整数表示集合可提高程序的执行效率并降低内存占用.
var followset uint64 = 1 << _EOF // don't skip over EOF
if len(followlist) > 0 {
if p.fnest > 0 {
followset |= stopset
}
for _, tok := range followlist {
followset |= 1 << tok
}
}
//循环执行p.next(), 终止条件为p.tok在followset表示的集合中
for !contains(followset, p.tok) {
if trace {
p.print("skip " + p.tok.String())
}
p.next()
if len(followlist) == 0 {
break
}
}
if trace {
p.print("next " + p.tok.String())
}
}
//stopset为语句起始关键字的集合. 在出现语法错误时, (通常)不应该跳过stopset中的元素.
const stopset uint64 = 1<<_Break |
1<<_Const |
1<<_Continue |
1<<_Defer |
1<<_Fallthrough |
1<<_For |
1<<_Go |
1<<_Goto |
1<<_If |
1<<_Return |
1<<_Select |
1<<_Switch |
1<<_Type |
1<<_Var
上面提到p.next()的作用是返回源码文件中的下一个Token,什么是Token?
Token是符合词法规则的字符串,词法分析器的作用就是将源码文件转换为token流,p.next()的作用就是不断获取流中的下一个元素。Golang所有的Token定义在文件:$GOROOT/src/cmd/compile/internal/syntax/tokens.go中。
下面是p.next()方法的整体结构:
$GOROOT/src/cmd/compile/internal/syntax/scanner.go
//返回源码文件对应的Token流中的下一个Token
func (s *scanner) next() {
//博主添加的defer语句
defer func() {
if os.Getenv("ENABLED_DEBUG") == "true" {
spew.Config.Indent = "\t"
fmt.Printf("s.line:%v, s.col:%v, s.tok:%v", s.line, s.col, spew.Sdump(s.tok))
}
}()
//!nlsemi为true表示将'\n'和'EOF'当做';'处理, 此情况下的'\n'不能作为空白字符处理
nlsemi := s.nlsemi
s.nlsemi = false
redo:
c := s.getr()
//跳过空白字符, ' ', '\t', '\r'和!nlsemi条件下的'\n'
for c == ' ' || c == '\t' || c == '\n' && !nlsemi || c == '\r' {
c = s.getr()
}
// token start
s.line, s.col = s.source.line0, s.source.col0
if isLetter(c) || c >= utf8.RuneSelf && s.isIdentRune(c, true) {
//处理下一个Token为关键字或自定义标识符的情况
s.ident()
return
}
switch c {
case -1:
if nlsemi {
s.lit = "EOF"
s.tok = _Semi
break
}
s.tok = _EOF
case '\n':
//将'\n'记录为分号(_Semi)
s.lit = "newline"
s.tok = _Semi
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
s.number(c)
case '"':
s.stdString()
case '`':
s.rawString()
case '\'':
s.rune()
... //省略的代码
}
可以看到本人在源码中添加了一些打印语句,编译修改后的源码,得到新的Golang编译器,使用新的编译器编译下面的代码。
package test
var v1 bool
const C1 = 1
type I interface {}
type S struct {}
/*多行注释*/
//单行注释
func add(a, b int) int {
return a + b
}
得到的输出如下:
s.line:1, s.col:1, s.tok:(syntax.token) package
s.line:1, s.col:9, s.tok:(syntax.token) name
s.line:1, s.col:13, s.tok:(syntax.token) ;
s.line:3, s.col:1, s.tok:(syntax.token) import
s.line:3, s.col:8, s.tok:(syntax.token) literal
s.line:3, s.col:13, s.tok:(syntax.token) ;
s.line:5, s.col:1, s.tok:(syntax.token) var
s.line:5, s.col:5, s.tok:(syntax.token) name
s.line:5, s.col:8, s.tok:(syntax.token) name
s.line:5, s.col:12, s.tok:(syntax.token) ;
s.line:7, s.col:1, s.tok:(syntax.token) const
s.line:7, s.col:7, s.tok:(syntax.token) name
s.line:7, s.col:10, s.tok:(syntax.token) =
s.line:7, s.col:12, s.tok:(syntax.token) literal
s.line:7, s.col:13, s.tok:(syntax.token) ;
s.line:9, s.col:1, s.tok:(syntax.token) type
s.line:9, s.col:6, s.tok:(syntax.token) name
s.line:9, s.col:8, s.tok:(syntax.token) interface
s.line:9, s.col:18, s.tok:(syntax.token) {
s.line:9, s.col:19, s.tok:(syntax.token) }
s.line:9, s.col:20, s.tok:(syntax.token) ;
s.line:14, s.col:1, s.tok:(syntax.token) func
s.line:14, s.col:6, s.tok:(syntax.token) name
s.line:14, s.col:9, s.tok:(syntax.token) (
s.line:14, s.col:10, s.tok:(syntax.token) name
s.line:14, s.col:11, s.tok:(syntax.token) ,
s.line:14, s.col:13, s.tok:(syntax.token) name
s.line:14, s.col:15, s.tok:(syntax.token) name
s.line:14, s.col:18, s.tok:(syntax.token) )
s.line:14, s.col:20, s.tok:(syntax.token) name
s.line:14, s.col:24, s.tok:(syntax.token) {
s.line:15, s.col:2, s.tok:(syntax.token) name
s.line:15, s.col:4, s.tok:(syntax.token) :=
s.line:15, s.col:7, s.tok:(syntax.token) name
s.line:15, s.col:9, s.tok:(syntax.token) op
s.line:15, s.col:11, s.tok:(syntax.token) name
s.line:15, s.col:12, s.tok:(syntax.token) ;
s.line:16, s.col:2, s.tok:(syntax.token) name
s.line:16, s.col:5, s.tok:(syntax.token) .
s.line:16, s.col:6, s.tok:(syntax.token) name
s.line:16, s.col:11, s.tok:(syntax.token) (
s.line:16, s.col:12, s.tok:(syntax.token) name
s.line:16, s.col:13, s.tok:(syntax.token) )
s.line:16, s.col:14, s.tok:(syntax.token) ;
s.line:17, s.col:2, s.tok:(syntax.token) return
s.line:17, s.col:9, s.tok:(syntax.token) name
s.line:17, s.col:10, s.tok:(syntax.token) ;
s.line:18, s.col:1, s.tok:(syntax.token) }
s.line:18, s.col:2, s.tok:(syntax.token) ;
s.line:18, s.col:2, s.tok:(syntax.token) EOF
f:(*syntax.File)(0xc000320fc0)({
PkgName: (*syntax.Name)(0xc000341220)({
Value: (string) (len=4) "test",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:1:9
}
}
}),
DeclList: ([]syntax.Decl) (len=5 cap=8) {
(*syntax.ImportDecl)(0xc000073ad0)({
LocalPkgName: (*syntax.Name)(<nil>),
Path: (*syntax.BasicLit)(0xc000073b00)({
Value: (string) (len=5) "\"fmt\"",
Kind: (syntax.LitKind) 4,
Bad: (bool) false,
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:3:8
}
}
}),
Group: (*syntax.Group)(<nil>),
decl: (syntax.decl) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:3:8
}
}
}),
(*syntax.VarDecl)(0xc000068550)({
NameList: ([]*syntax.Name) (len=1 cap=1) {
(*syntax.Name)(0xc000341240)({
Value: (string) (len=2) "v1",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:5:5
}
}
})
},
Type: (*syntax.Name)(0xc000341260)({
Value: (string) (len=4) "bool",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:5:8
}
}
}),
Values: (syntax.Expr) <nil>,
Group: (*syntax.Group)(<nil>),
decl: (syntax.decl) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:5:5
}
}
}),
(*syntax.ConstDecl)(0xc0000685a0)({
NameList: ([]*syntax.Name) (len=1 cap=1) {
(*syntax.Name)(0xc0003412a0)({
Value: (string) (len=2) "C1",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:7:7
}
}
})
},
Type: (syntax.Expr) <nil>,
Values: (*syntax.BasicLit)(0xc000073e90)({
Value: (string) (len=1) "1",
Kind: (syntax.LitKind) 0,
Bad: (bool) false,
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:7:12
}
}
}),
Group: (*syntax.Group)(<nil>),
decl: (syntax.decl) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:7:7
}
}
}),
(*syntax.TypeDecl)(0xc000321040)({
Name: (*syntax.Name)(0xc0003412c0)({
Value: (string) (len=1) "I",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:9:6
}
}
}),
Alias: (bool) false,
Type: (*syntax.InterfaceType)(0xc000354060)({
MethodList: ([]*syntax.Field) <nil>,
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:9:8
}
}
}),
Group: (*syntax.Group)(<nil>),
Pragma: (syntax.Pragma) 0,
decl: (syntax.decl) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:9:6
}
}
}),
(*syntax.FuncDecl)(0xc000321080)({
Attr: (map[string]bool) <nil>,
Recv: (*syntax.Field)(<nil>),
Name: (*syntax.Name)(0xc0003412e0)({
Value: (string) (len=3) "add",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:6
}
}
}),
Type: (*syntax.FuncType)(0xc0003210c0)({
ParamList: ([]*syntax.Field) (len=2 cap=2) {
(*syntax.Field)(0xc000354330)({
Name: (*syntax.Name)(0xc000341300)({
Value: (string) (len=1) "a",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:10
}
}
}),
Type: (*syntax.Name)(0xc000341340)({
Value: (string) (len=3) "int",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:15
}
}
}),
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:10
}
}),
(*syntax.Field)(0xc000354420)({
Name: (*syntax.Name)(0xc000341320)({
Value: (string) (len=1) "b",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:13
}
}
}),
Type: (*syntax.Name)(0xc000341340)({
Value: (string) (len=3) "int",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:15
}
}
}),
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:13
}
})
},
ResultList: ([]*syntax.Field) (len=1 cap=1) {
(*syntax.Field)(0xc0003545d0)({
Name: (*syntax.Name)(<nil>),
Type: (*syntax.Name)(0xc000341360)({
Value: (string) (len=3) "int",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:20
}
}
}),
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:20
}
})
},
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:9
}
}
}),
Body: (*syntax.BlockStmt)(0xc000321100)({
List: ([]syntax.Stmt) (len=3 cap=4) {
(*syntax.AssignStmt)(0xc000321180)({
Op: (syntax.Operator) :,
Lhs: (*syntax.Name)(0xc000341380)({
Value: (string) (len=1) "c",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:15:2
}
}
}),
Rhs: (*syntax.Operation)(0xc000321140)({
Op: (syntax.Operator) +,
X: (*syntax.Name)(0xc0003413a0)({
Value: (string) (len=1) "a",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:15:7
}
}
}),
Y: (*syntax.Name)(0xc0003413c0)({
Value: (string) (len=1) "b",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:15:11
}
}
}),
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:15:9
}
}
}),
simpleStmt: (syntax.simpleStmt) {
stmt: (syntax.stmt) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:15:4
}
}
}
}),
(*syntax.ExprStmt)(0xc000341440)({
X: (*syntax.CallExpr)(0xc0003211c0)({
Fun: (*syntax.SelectorExpr)(0xc000354960)({
X: (*syntax.Name)(0xc0003413e0)({
Value: (string) (len=3) "fmt",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:2
}
}
}),
Sel: (*syntax.Name)(0xc000341400)({
Value: (string) (len=5) "Print",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:6
}
}
}),
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:5
}
}
}),
ArgList: ([]syntax.Expr) (len=1 cap=1) {
(*syntax.Name)(0xc000341420)({
Value: (string) (len=1) "c",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:12
}
}
})
},
HasDots: (bool) false,
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:11
}
}
}),
simpleStmt: (syntax.simpleStmt) {
stmt: (syntax.stmt) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:16:11
}
}
}
}),
(*syntax.ReturnStmt)(0xc000341480)({
Results: (*syntax.Name)(0xc0003414a0)({
Value: (string) (len=1) "c",
expr: (syntax.expr) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:17:9
}
}
}),
stmt: (syntax.stmt) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:17:2
}
}
})
},
Rbrace: (syntax.Pos) main.go:18:1,
stmt: (syntax.stmt) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:24
}
}
}),
Pragma: (syntax.Pragma) 0,
decl: (syntax.decl) {
node: (syntax.node) {
pos: (syntax.Pos) main.go:14:6
}
}
})
},
Lines: (uint) 18,
node: (syntax.node) {
pos: (syntax.Pos) main.go:1:1
}
})
上面1~50行每行对应一个Token,s.line, s.col和s.tok分别为token的起始行,列和字符串表示。
51~369行的部分为抽象语法树的结构。
52~59行为源码文件所在包的信息。
60~364行为源文件中顶层声明的信息,其中:
(1)61~79行对应源码中的语句:import "fmt"
(2)80~106行对应源码中的语句:var v1 bool
(3)107~135行对应源码中的语句:const C1 = 1
(4)136~161行对应源码中的语句:type I interface {}
(5)162~363行对应源码中的语句:
func add(a, b int) int {
c := a + b
fmt.Print(c)
return c
}
至此,对Golang的词法分析和语法分析已经具备了整体的认知。
类型检查
先介绍有关类型检查的概念,静态类型检查和动态类型检查。
静态类型检查:通过对源码的分析,在程序执行之前即可确定类型的合法与否。
动态类型检查:在程序的执行过称中动态的检查类型的合法性。
Golang的编译器不仅使用静态类型检查来保证程序的类型安全,还会在编译期间引入类型信息,使程序在运行时可通过反射来获取变量的类型信息。例如空接口转换成具体类型(对应空接口的断言)时就会进行动态类型检查。
回到gc.Main()的这个函数:
$GOROOT/src/cmd/compile/internal/gc/main.go
func Main(archInit func(*Arch)) {
... //省略的代码
lines := parseFiles(flag.Args())
... //省略的代码
//阶段1:遍历抽象语法树,收集常量,函数等相关的类型信息。
timings.Start("fe", "typecheck", "top1")
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) {
xtop[i] = typecheck(n, ctxStmt)
}
}
//阶段2:遍历抽象语法树,处理变量的赋值。
timings.Start("fe", "typecheck", "top2")
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias {
xtop[i] = typecheck(n, ctxStmt)
}
}
... //省略的代码
}
静态类型检查主要体现在上面代码中的阶段1和阶段2,具体的检查逻辑在typecheck函数中,下面是typecheck函数的定义:
func typecheck(n *Node, top int) (res *Node) {
... //类型检查前的准备工作
n = typecheck1(n, top)
... //省略的代码
return n
}
从typecheck的定义中可以看出类型检查主要在typecheck1函数中完成,typecheck1的主要逻辑是在一个超过1500行的switch语句中根据n.op的值执行不同的分支,下面给出typecheck1的整体结构:
//对以n为根的抽象语法树进行类型检查和相关属性修改,返回修改后的根结点
func typecheck1(n *Node, top int) (res *Node) {
... //省略的代码
switch n.Op {
... //省略的代码
case OTMAP:
ok |= ctxType
n.Left = typecheck(n.Left, ctxType)
n.Right = typecheck(n.Right, ctxType)
l := n.Left
r := n.Right
if l.Type == nil || r.Type == nil {
n.Type = nil
return n
}
if l.Type.NotInHeap() {
yyerror("go:notinheap map key not allowed")
}
if r.Type.NotInHeap() {
yyerror("go:notinheap map value not allowed")
}
setTypeNode(n, types.NewMap(l.Type, r.Type))
mapqueue = append(mapqueue, n) // check map keys when all types are settled
n.Left = nil
n.Right = nil
case OTCHAN:
ok |= ctxType
n.Left = typecheck(n.Left, ctxType)
l := n.Left
if l.Type == nil {
n.Type = nil
return n
}
if l.Type.NotInHeap() {
yyerror("chan of go:notinheap type not allowed")
}
... //省略的代码
case OMAKE:
... //省略的代码
switch t.Etype {
default:
yyerror("cannot make type %v", t)
n.Type = nil
return n
case TSLICE:
... //省略的代码
n.Op = OMAKESLICE
case TMAP:
... //省略的代码
n.Op = OMAKEMAP
case TCHAN:
... //省略的代码
n.Op = OMAKECHAN
}
... //省略的代码
}
... //省略的代码
}
终点看上面代码中的case OTMAP,case OTCHAN和case OMAKE分支。
对于case OTMAP和case OTCHAN分支,终点看下面的代码:
if l.Type.NotInHeap() {
yyerror("go:notinheap map key not allowed")
}
if r.Type.NotInHeap() {
yyerror("go:notinheap map value not allowed")
}
... //省略的代码
if l.Type.NotInHeap() {
yyerror("chan of go:notinheap type not allowed")
}
上面的代码表明map的key和value以及chan的elem必须是可以在堆上分配的,为何如此?这和map和chan的实现,栈的动态复制都有关系,在后面介绍到map,chan以及Golang栈的复制时会进一步介绍。
从在case OMAKE分支的代码可以看出编译器会根据参数的不同(slice,map或chan)对n.op进行更具体的替换。对于除slice,map和chan之外的类型的参数,编译器会给出错误提示"cannot make type xxx"。
中间代码生成