Go 語言 - 類型推斷解析 [2]
若干年後的糾葛,從茫茫戈壁上的相遇開始,早已冥冥註定...
類型推斷依賴編譯器的處理能力,編譯器執行的過程爲:詞法解析 => 語法分析 => 類型檢查 => 中間代碼 => 代碼優化 => 生成機器碼。編譯階段的代碼位於 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 [333] (a[int])。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/SURKglDlaAQhbuJkzBYnKQ