若干年后的纠葛,从茫茫戈壁上的相遇开始,早已冥冥注定...
类型推断依赖编译器的处理能力,编译器执行的过程为:词法解析=>语法分析=>类型检查=>中间代码=>代码优化=>生成机器码。编译阶段的代码位于go/src/cmd/compile文件中
在词法解析阶段,会将赋值语句右边的常量解析为一个未定义的类型,例如,ImagLit代表复数,FloatLit 代表浮点数,IntLit代表整数。
//go/src/cmd/compile/internal/syntax
const (
IntLit LitKind = iota
FloatLit
ImagLit
RuneLit
StringLit
)
Go语言源代码采用UTF-8的编码方式,在进行词法解析时当,遇到需要赋值的常量操作时,会逐个读取后面常量的UTF-8字符。字符串的首字符为",数字的首字符为'0'~'9'。实现位于syntax.next函数中。
// go/src/cmd/compile/internal/syntax
func (s *scanner) next() {
...
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
s.number(c)
case '"':
s.stdString()
case '`':
s.rawString()
...
因此对于整数、小数等常量的识别就显得非常简单。如图3-2所示,整数就是字符中全是“0”~“9”的数字,浮点数就是字符中有“.”号的数字,字符串的首字符为"或'。
图 词法解析阶段解析未定义的常量
下面列出的number函数为语法分析阶段处理数字的具体实现。数字首先会分被为小数部分与整数部分,通过字符. 进行区分。如果整数部分是以0开头的,则可能有不同的含义,例如0x代表十六进制数、0b代表二进制数。
// go/src/cmd/compile/internal/syntax
func (s *scanner) number(c rune) {
// 整数部分
var ds int
if c != '.' {
s.kind = IntLit
if c == '0' {
c = s.getr()
switch lower(c) {
case 'x':
c = s.getr()
base, prefix = 16, 'x'
case 'o':
c = s.getr()
base, prefix = 8, 'o'
case 'b':
c = s.getr()
base, prefix = 2, 'b'
}
}
c, ds = s.digits(c, base, &invalid)
digsep |= ds
}
// 小数部分
if c == '.' {
s.kind = FloatLit
c, ds = s.digits(s.getr(), base, &invalid)
digsep |= ds
}
以赋值语句a := 333为例, 完成词法解析与语法分析时, 此赋值语句将以AssignStmt结构表示。
AssignStmt struct {
Op Operator
Lhs, Rhs Expr
simpleStmt
}
其中Op代表操作符,在这里是赋值操作OAS。Lhs与Rhs分别代表左右两个表达式,左边代表变量a,右边代表常量333,此时其类型为intLit。
完成语法解析后,进入抽象语法树阶段。在该阶段会将词法解析阶段生成的解析为一个Node,Node结构体是对抽象语法树中节点的抽象。
type Node struct {
Left *Node
Right *Node
Ninit Nodes
Nbody Nodes
List Nodes
Rlist Nodes
Type *types.Type
E interface{}
...
其中,Left(左节点)代表左边的变a,Right(右节点)代表整数333,其Op操作为。Right的E接口字段会存储值333,如果前一阶段为IntLit类型,则需要转换为Mpint类型。Mpint类型用于存储整数常量,具体结构如下所示。
// Mpint 代表整数常量
type Mpint struct {
Val big.Int
Ovf bool
Rune bool
}
从Mpint类型的结构可以看到,在编译时AST阶段整数通过math/big.Int进行高精度存储,浮点数通过big.Float进行高精度存储
在类型检查阶段,右节点中的Type字段存储的类型会变为types.Types[TINT]。存储了不同标识对应的Go语言中的实际类型,其中,types.Types[TINT]对应Go语言内置的int类型。
接着完成最终的赋值操作,并将右边常量的类型赋值给左边变量的类型。具体实现位于typecheckas函数中。
func typecheckas(n *Node) {
if n.Left.Name != nil && n.Left.Name.Defn == n && n.Left.Name.Param.Ntype == nil {
n.Right = defaultlit(n.Right, nil)
n.Left.Type = n.Right.Type
}
}
...
}
在SSA阶段,变量a中存储的大数类型的333最终会调用big.Int包中Int64函数将其转换为int64类型的常量,形如:v4 (?) = MOVQconst
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8