Go学习笔记-Go编译器简介
1.编译器
1.1 三阶段编译器
- 编译器前端: 主要用于理解源代码、扫描解析源代码并进行语义表达
- IR: Intermediate Representation,可能有多个,编译器会使用多个 IR 阶段、多种数据结构表示程序,并在中间阶段对代码进行多次优化
- 优化器: 主要目的是降低程序资源的消耗,但有理论已经表明某些优化存在着NP难题,所以编译器无法进行最佳优化,通常常用折中方案
- 编译后端: 主要用于生成特定目标机器上的程序,可能是可执行文件,也可能是需要进一步处理的obj、汇编语言等
1.2 Go语言编译器
- go compiler: Go语言编译器的主要源代码位于"src/cmd/compile/internal"目录下
Tips: 注意:大写的GC表示垃圾回收
2.词法解析
_IncOp":="_Definego/src/cmd/compile/internal/syntax/tokens.gogo/src/go/scannergo/src/go/token2.1 scanner.go 代码简介
go/src/go/scanner[]byteScan- type ErrorHandler func(pos token.Position, msg string): 可以向Scanner.Init 提供 ErrorHandler,如果遇到语法错误并安装了处理程序,则调用处理程序并提供位置和错误消息,该位置指向错误标记的开始。
- type Scanner struct: 扫描器在处理给定文本时保持扫描器的内部状态。
- const bom = 0xFEFF: 字节顺序标记,只允许作为第一个字符。
- next()函数: 将下一个Unicode字符读入 s.ch,S.ch < 0 表示文件结束。
- peek()函数: Peek返回跟随最近读取字符的字节,而不推进扫描仪,如果扫描器在 EOF, peek 返回 0。
- type Mode uint: 模式值是一组标志(或 0),它们控制扫描行为。
const (
ScanComments Mode = 1 << iota // 返回注释作为注释标记
dontInsertSemis // 不要自动插入分号-仅用于测试
)- Init()函数: Init 通过在 src 的开头设置扫描器来让扫描器 s 标记文本src,扫描器使用文件集文件来获取位置信息,并为每一行添加行信息。当重新扫描相同的文件时,可以重复使用相同的文件,因为已经存在的行信息被忽略了。如果文件大小与 src 大小不匹配,Init 会导致 panic。如果遇到语法错误且 err 不是nil,则调用 Scan 将调用错误处理程序 err。此外,对于遇到的每个错误, Scanner 字段 ErrorCount 增加 1,mode 参数决定如何处理注释。
Tips: 注意,如果文件的第一个字符有错误,Init 可能会调用 err。
a-zA-Z_utf8.RuneSelffunc isLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
}0-9utf8.RuneSelffunc isDigit(ch rune) bool {
return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
}- digitVal()函数: 返回 rune 字符对应的整型数。
- scanEscape()函数: scanEscape 解析转义序列,其中 rune 是接受的转义引号,在语法错误的情况下,它在错误字符处停止(不使用它)并返回 false,否则返回 true。
2.2 token.go 代码代码简介
go/src/go/token- init()函数: 初始化关键字keywords变量(map[string]Token)的值
map[break:61 case:62 chan:63 const:64 continue:65 default:66 defer:67 else:68 fallthrough:69 for:70 func:71 go:72 goto:73 if:74 import:75 interface:76 map:77 package:78 range:79 return:80 select:81 struct:82 switch:83 type:84 var:85]- String()函数: 返回与令牌 tok 对应的字符串,对于操作符、分隔符和关键字,字符串是实际的标记字符序列(例如,对于标记ADD,字符串是“+”),对于所有其他令牌,字符串对应于令牌常量名(例如,对于令牌IDENT,字符串为“IDENT”)
- Precedence()函数: 返回二进制操作符 op 的操作符优先级,如果op不是二进制操作符,则结果为最低优先级
- Lookup()函数: 查找标记符对应的token
- IsLiteral() 函数: 判断token值是否是基本类型范围的值,若是返回true,否则返回false
literal_beg
// Identifiers and basic type literals
// (these tokens stand for classes of literals)
IDENT // main
INT // 12345
FLOAT // 123.45
IMAG // 123.45i
CHAR // 'a'
STRING // "abc"
literal_end- IsOperator() 函数: 判断token值是否是操作范围的值,若是返回true,否则返回false
operator_beg
// Operators and delimiters
ADD // +
SUB // -
MUL // *
QUO // /
REM // %
AND // &
OR // |
XOR // ^
SHL // <<
SHR // >>
AND_NOT // &^
ADD_ASSIGN // +=
SUB_ASSIGN // -=
MUL_ASSIGN // *=
QUO_ASSIGN // /=
REM_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
XOR_ASSIGN // ^=
SHL_ASSIGN // <<=
SHR_ASSIGN // >>=
AND_NOT_ASSIGN // &^=
LAND // &&
LOR // ||
ARROW // <-
INC // ++
DEC // --
EQL // ==
LSS // <
GTR // >
ASSIGN // =
NOT // !
NEQ // !=
LEQ // <=
GEQ // >=
DEFINE // :=
ELLIPSIS // ...
LPAREN // (
LBRACK // [
LBRACE // {
COMMA // ,
PERIOD // .
RPAREN // )
RBRACK // ]
RBRACE // }
SEMICOLON // ;
COLON // :
operator_end- IsKeyword() 函数: 判断token值是否是关键字范围的值,若是返回true,否则返回false
keyword_beg
// Keywords
BREAK
CASE
CHAN
CONST
CONTINUE
DEFAULT
DEFER
ELSE
FALLTHROUGH
FOR
FUNC
GO
GOTO
IF
IMPORT
INTERFACE
MAP
PACKAGE
RANGE
RETURN
SELECT
STRUCT
SWITCH
TYPE
VAR
keyword_end3.语法解析
go/src/cmd/compile/internal/syntax/nodes.gogo/src/cmd/compile/internal/syntax/parser.go//包导入声明
ImportSpec = [ "." | PackageName ] ImportPath .
ImportPath = string_lit .
//静态常量
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
//类型声明
TypeSpec = identifier [ "=" ] Type .
//变量声明
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .3.1 nodes.go 代码代码简介
- 函数声明:
AssignStmt struct {
Op Operator // 表示当前的操作符,即":=",0 表示没有操作
Lhs, Rhs Expr // Lhs, Rhs 分别代表左右两个表达式,Rhs == ImplicitOne means Lhs++ (Op == Add) or Lhs-- (Op == Sub)
simpleStmt
}- type Node interface:
type Node interface {
//Pos() 返回与节点相关联的位置,如下所示:
// 1)表示终端语法产物(Name,BasicLit等)的节点位置是相应产物在源中的位置。
// 2)表示非终端产品(IndexExpr, IfStmt等)的节点的位置是与该产品唯一关联的令牌的位置;通常是最左边的一个('['表示IndexExpr, 'if'表示IfStmt,等等)
Pos() Pos
aNode()
}包 PkgName; DeclList[0], DeclList[1], ...3.2 parser.go 代码代码简介
- importDecl()函数: 包导入声明。
- constDecl()函数: 静态常量。
- typeDecl()函数: 类型声明。
- varDecl()函数: 变量声明。
- type parser struct:
type parser struct {
file *PosBase
errh ErrorHandler
mode Mode
scanner
base *PosBase // 当前位置的基准
first error // 遇到的第一个错误
errcnt int // 遇到的错误数
pragma Pragma // 编译指示标志
fnest int // 函数嵌套级别(用于错误处理)
xnest int // 表达式嵌套级别(用于解决编译歧义)
indent []byte //跟踪支持
}- updateBase()函数: updateBase 将当前的位置基设置为 pos 处的新行的基准,该行基准的文件名、行和列值从定位于 (tline, tcol) 的文本中提取(仅在错误消息中需要)。
- posAt()函数: posAt返回 (line, col) 的 Pos 值和当前位置的基数。
- errorAt()函数: 错误报告在给定位置的错误。
- syntaxErrorAt()函数: syntaxErrorAt 在给定位置报告语法错误。
- tokstring()函数: tokstring 为更可读的错误消息返回所选标点符号的英文单词。
- const stopset uint64: 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- advance()函数: Advance 消耗令牌,直到找到 stopset 或 followlist 的令牌。只有在函数 (p.fnest > 0) 中才考虑 stopset,如果它是空的,则只使用一个 (非eof) 令牌来确保进度。
- fileOrNil()函数: 包文件 Parse 方法会根据需要使用匹配的 Go 结果进行注释,注释的目的只是作为引导,因为单个 Go 语法规则可能被多个解析方法覆盖,排除返回切片的方法,名为 xOrNil 的解析方法可能返回 nil,所有其他节点都将返回一个有效的非 nil 节点。
- list()函数: List 解析一个可能为空的、由 sep 分隔的列表,可选后跟 sep 并由 (and) 或 {and} 括起来。open 是 Lparen 的一个,sep 是 Comma 或 Semi 的一个对于每个列表元素,调用 fmf 返回 true 后,不再接受列表元素,List 返回结束令牌的位置。
3.3 语法解析举例
a := b + c(10)Tips: c(10) 表示强类型转换。
a := b + c(10)4.抽象语法树构建
go/src/cmd/compile/internal/gc/noder.gogo/src/cmd/compile/internal/gc/syntax.gofunc (p *noder) decls(decls []syntax.Decl) (l []*Node) {
var cs constState
for _, decl := range decls {
p.setlineno(decl)
switch decl := decl.(type) {
case *syntax.ImportDecl:
p.importDecl(decl)
case *syntax.VarDecl:
l = append(l, p.varDecl(decl)...)
case *syntax.ConstDecl:
l = append(l, p.constDecl(decl, &cs)...)
case *syntax.TypeDecl:
l = append(l, p.typeDecl(decl))
case *syntax.FuncDecl:
l = append(l, p.funcDecl(decl))
default:
panic("unhandled Decl")
}
}
return
}5.类型检查
var a intgo/src/cmd/compile/internal/gc/typecheck.go6.变量捕获
go/src/cmd/compile/internal/gc/closure.gocapturevarspackage main
import (
"fmt"
)
func main() {
a := "qinshixian"
b := make(map[string]int)
c := "haoweilai"
go func() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}()
a = "asddss"
}go tool compile -m=2 getVar.go| grep capturingarefbcvalue7.函数内联
go/src/cmd/compile/internal/gc/inl.goforrangegoselectpackage main
import "testing"
//go:noinline
func max(a, b int) int {
if a > b {
return a
}
return b
}
var Result int
func BenchmarkMax(b *testing.B) {
var r int
for i := 0; i < b.N; i++ {
r = max(-1, i)
}
Result = r
}//go:noinline//go:noinlinego test leetcode_test.go -bench=.//go:noinlinego test leetcode_test.go -bench=.forrangegoselectgo tool compile -m=2 leetcode_test.go8.逃逸分析
go/src/cmd/compile/internal/gc/escape .go(1)原则1:指向栈上对象的指针不能被存储到堆中
(2)原则2:指向栈上对象的指针不能超过该栈对象的生命周期- 逃逸现象举例:
package main
var n *int
func escape(){
a := 100
n = &a
}
func main() {
escape()
}go tool compile -m getVar.gonintanaa