Golang編譯器源碼分析(1)

1、Golang編譯原理概述

編譯過程

一、Go 的編譯器在邏輯上能夠被分紅四個階段:詞法與語法分析、類型檢查和 AST 轉換、通用 SSA 生成和最後的機器代碼生成前端

編譯前端     編譯後端
---------- | ---------- |
| 詞法分析 | 中間碼生成 |
|---------------------- |
| 語法分析 | 代碼優化 |
|---------------------- |
| 語義分析 | 機器碼生成 |
|--------- | ---------- |
  1. 詞法分析生成token [scanner.next()];
  2. 語法分析生成抽象語法樹AST,每個 AST 都對應着一個單獨的 Go 語言文件 [parse.decls()];
  3. 語義分析進行類型檢查,優化改寫ast [checktype1()];
  4. 中間碼生成將抽象語法樹會通過多輪處理轉變成最後的擁有 靜態單賦值(SSA)特性的中間碼,分析出代碼中的無用變量和片斷並對代碼進行優化;
  5. 高效代碼替換低效的代碼,力所能及的進行優化;
  6. 生成彙編代碼(Plan9),進而生成機器碼。

詞法與語法分析

詞法分析是計算機科學中將字符序列轉換爲標記(token)序列的過程,詞法分析就是解析源代碼文件,它將文件中的字符串序列轉換成Token序列,方便後面的處理和解析。
語法分析的輸入就是詞法分析器輸出的 Token 序列,這些序列會按照順序被語法分析器進行解析,語法分析過程就是將詞法分析生成的 Token 按照語言定義好的文法(Grammar)自下而上或者自上而下的進行規約,每個 Go 的源代碼文件最終會被概括成一個SourceFile結構。
詞法分析會返回一個不包含空格、換行等字符的 Token 序列,例如:package, import, (, io, ), …,而語法分析會把 Token 序列轉換成有意義的結構體,也就是抽象語法樹(AST)。
每個 AST 都對應着一個單獨的 Go 語言文件,這個抽象語法樹中包括當前文件屬於的包名、定義的常量、結構體和函數等。node

類型檢查

當拿到一組文件的抽象語法樹以後,Go 語言的編譯器會對語法樹中定義和使用的類型進行檢查,類型檢查分別會按照如下的順序對不一樣類型的節點進行驗證和處理:golang

  1. 常量、類型和函數名及類型;
  2. 變量的賦值和初始化;
  3. 函數和閉包的主體;
  4. 哈希鍵值對的類型;
  5. 導入函數體;
  6. 外部的聲明;

經過對整棵抽象語法樹的遍歷,咱們在每個節點上都會對當前子樹的類型進行驗證,以保證當前節點上不會出現類型錯誤的問題,全部的類型錯誤和不匹配都會在這一個階段被發現和暴露出來,結構體是否實現了某些接口也會在這一階段被檢查出來。express

中間代碼生成

當咱們將源文件轉換成了抽象語法樹、對整棵樹的語法進行解析並進行類型檢查以後,就能夠認爲當前文件中的代碼不存在語法錯誤和類型錯誤的問題了,Go 語言的編譯器就會將輸入的抽象語法樹轉換成中間代碼。
在類型檢查以後,就會經過一個名爲 compileFunctions 的函數開始對整個 Go 語言項目中的所有函數進行編譯,這些函數會在一個編譯隊列中等待幾個後端工做協程的消費,這些併發執行的 Goroutine 會將全部函數對應的抽象語法樹轉換成中間代碼。
因爲 Go 語言編譯器的中間代碼使用了 SSA 的特性,因此在這一階段咱們就可以分析出代碼中的無用變量和片斷並對代碼進行優化。後端

機器碼生成

Go 語言源代碼的 src/cmd/compile/internal 目錄中包含了不少機器碼生成相關的包,不一樣類型的 CPU 分別使用了不一樣的包生成機器碼,其中包括 amd6四、arm、arm6四、mips、mips6四、ppc6四、s390x、x86 和 wasm。數組

2、Golang編譯器源碼下載及安裝

官網地址:
https://golang.google.cn/dl/
下載安裝包或源碼包都可
wget https://golang.google.cn/dl/go1.15.4.src.tar.gz
安裝包下載很簡單 設置好環境變量。

源碼安裝
cd go/src
./all.bash

安裝報錯
# ./all.bash 
./make.bash: line 165: /root/go1.4/bin/go: No such file or directory
Building Go cmd/dist using /root/go1.4. ()
ERROR: Cannot find /root/go1.4/bin/go.
Set $GOROOT_BOOTSTRAP to a working Go tree >= Go 1.4.

GOROOT_BOOTSTRAP 設置go1.4版本地址 實現了自舉,編譯後續版本
咱們學習的對象就是Go語言版本的編譯器程序源碼,源碼目錄go/src/cmd/compilebash

3、編譯器代碼調試方法

一、DLV調試編譯過程

調試編譯器執行過程,必須調試go安裝目錄下的編譯器源碼,即GOROOT下源碼,不然報內部包衝突錯誤。
編譯器目錄爲go/src/cmd/compile,調試命令以下:閉包

#一樣必須是GOROOT下源碼
 dlv debug /usr/local/go/src/cmd/compile/main.go
 #設置斷點 經過包名及方法設置斷點
 b main.main
 #設置編譯器編譯目標文件
 r /Users/xxx/go/src/test/debug1/main.go
 #開始調試 continue 
 c 
 #執行下一行代碼
 n
 # 打印關心的變量
 p archInit
cmd/compile/internal/amd64.Init
# 經過代碼文件及行數設置斷點
b /usr/local/go/src/cmd/compile/internal/gc/main.go:345
p outfile
#直接經過函數名設置斷點
b parseFiles
b funccompile

二、Goland調試編譯過程

同理 goland調試編譯器 調試的源碼也必須位於go安裝目錄
首先須要經過Goland新建項目go 項目目錄位於GOROOT,例如/user/local/go
Goland Debug配置方法以下 和dlv相似併發

打開 /usr/local/go/src/cmd/compile/main.go
點擊main函數左側綠色小三角形,執行 debug go build main.go
(未設置則自動)彈出go build配置框或點擊右上方Edit Debug Configurations 菜單按鈕從新設置
Files /usr/local/go/src/cmd/compile/main.go
# 設置有權限的工做目錄
Working Directory /Users/xxx/golang/compile/w
# 設置編譯目標文件
Program arguments /Users/xxx/go/src/test/debug1/main.go

image
以上是Goland debug的關鍵配置,具體使用方法,自行百度,和普通程序debug調試用法一致。app

4、編譯源代碼學習

詞法語法分析基本流程圖

image
經過main函數,不難追蹤到文件解析入口函數parseFiles,該函數爲每一個文件建立了一個noder對象,
noders := make([]*noder, 0, len(filenames))
noder擁有一個詞法文件屬性存儲該源文件對應語法分析的結果。
file *syntax.File //詞法分析文件對象指針
單個源文件對應的語法樹noder結構體,noder結構體以下

// noder transforms package syntax's AST into a Node tree.
type noder struct {
   basemap   map[*syntax.PosBase]*src.PosBase
   basecache struct {
      last *syntax.PosBase
      base *src.PosBase
   }
  file       *syntax.File //詞法語法分析文件對象指針
  linknames  []linkname
  pragcgobuf [][]string
  err        chan syntax.Error
  scope      ScopeID
 // scopeVars is a stack tracking the number of variables declared in the
 // current function at the moment each open scope was opened. scopeVars []int
  lastCloseScopePos syntax.Pos
}

syntax.File詞法分析生成結果定義以下:

// package PkgName; DeclList[0], DeclList[1], ...
type File struct {
   PkgName  *Name
   DeclList []Decl
   Lines    uint
   node
}

main入口經過parseFiles方法開啓協程經過syntax.Parse進行文件解析.
Parse函數代碼以下:

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)
   p.next() //開始識別token
   return p.fileOrNil(), p.first //p.fileOrNil()開始生成token詞法樹
}

解析過程當中,建立了語法分析器parser,
var p parser //聲名語法分析樹
其中繼承了詞法分析器 sanner 的方法和屬性,parser定義以下:

#解析器結構體
type parser struct {
   file *PosBase
   errh ErrorHandler
   mode Mode
   scanner //繼承掃描器scanner的方法屬性
   base   *PosBase // current position base
   first  error // first error encountered
   errcnt int // number of errors encountered
   pragma Pragma // pragma flags
   fnest  int // function nesting level (for error handling)
   xnest  int // expression nesting level (for complit ambiguity resolution)
   indent []byte // tracing support
}

詞法分析器scanner結構體定義以下:

#詞法分析掃描器結構體
type scanner struct {
   source //持有源文件對象
   mode   uint
   nlsemi bool // if set 'n' and EOF translate to ';'
 // current token, valid after calling next() line, col uint
   tok       token
   lit       string // valid if tok is _Name, _Literal, or _Semi ("semicolon", "newline", or "EOF"); may be malformed if bad is true
   bad       bool // valid if tok is _Literal, true if a    syntax error occurred, lit may be malformed
   kind      LitKind // valid if tok is _Literal
   op        Operator // valid if tok is _Operator, _AssignOp, or _IncOp
   prec      int // valid if tok is _Operator, _AssignOp, or _IncOp
}

源文件對象定義以下:

type source struct {
    src  io.Reader
    errh func(line, pos uint, msg string)

    // source buffer
    buf         [4 << 10]byte
    r0, r, w    int   // previous/current read and write buf positions, excluding sentinel
    line0, line uint  // previous/current line
    col0, col   uint  // previous/current column (byte offsets from line start)
    ioerr       error // pending io error

    // literal buffer
    lit []byte // literal prefix
    suf int    // literal suffix; suf >= 0 means we are scanning a literal
}

詞法分析函數(syntax.Parse)的 fileOrNil() 方法返回文件結構體syntax.File,也就是解析完畢的詞法分析詞法結果樹。

詞法分析過程

Go 語言的詞法解析是經過src/cmd/compile/internal/syntax/scanner.go文件中的 scanner 結構體實現的,這個結構體(見上文定義)會持有當前掃描的數據源文件、啓用的模式和當前被掃描到的Token。
src/cmd/compile/internal/syntax/tokens.go 文件中定義了 Go 語言中支持的所有 Token 類型,全部的 token 類型都是正整數,你能夠在這個文件中找到一些常見 Token 的定義,例如:操做符、括號和關鍵字等:

const (
   _ token = iota
   _EOF // EOF
 // names and literals _Name // name
   _Literal // literal
 // operators and operations // _Operator is excluding '*' (_Star) _Operator // op
   _AssignOp // op=
   _IncOp // opop
   _Assign // =
   _Define // :=
   _Arrow // <-
   _Star // *
 // delimiters _Lparen // (
   _Lbrack // [
   _Lbrace // {
   _Rparen // )
   _Rbrack // ]
   _Rbrace // }
   _Comma // ,
   _Semi // ;
   _Colon // :
   _Dot // .
   _DotDotDot // ...
 // keywords _Break // break
   _Case // case
   _Chan // chan
   _Const // const
   _Continue // continue
   _Default // default
   _Defer // defer
   _Else // else
   _Fallthrough // fallthrough
   _For // for
   _Func // func
   _Go // go
   _Goto // goto
   _If // if
   _Import // import
   _Interface // interface
   _Map // map
   _Package // package
   _Range // range
   _Return // return
   _Select // select
   _Struct // struct
   _Switch // switch
   _Type // type
   _Var // var
 // empty line comment to exclude it from .String
   tokenCount //
)

最右推導加向前查看構成了Go語言詞法分析器的最基本原理,主要是由 scanner 這個結構體中的 next 方法驅動來獲取下一個詞法token,方法代碼以下:

func (s *scanner) next() {
   nlsemi := s.nlsemi
   s.nlsemi = false
redo:
   // skip white space
   c := s.getr()
   //過濾空字符,獲取下一個字符
   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) {
      s.ident()
      return
 }
   switch c {
   case -1:
      if nlsemi {
         s.lit = "EOF"
         s.tok = _Semi
         break
       }
      s.tok = _EOF
   case 'n':
      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()
   case '(':
      s.tok = _Lparen
   case '[':
      s.tok = _Lbrack
   case '{':
      s.tok = _Lbrace
   case ',':
      s.tok = _Comma
   case ';':
      s.lit = "semicolon"
      s.tok = _Semi
   case ')':
      s.nlsemi = true
      s.tok = _Rparen
   case ']':
      s.nlsemi = true
      s.tok = _Rbrack
   case '}':
      s.nlsemi = true
      s.tok = _Rbrace
   case ':':
      if s.getr() == '=' {
         s.tok = _Define
         break
      }
      s.ungetr()
      s.tok = _Colon
   case '.':
      c = s.getr()
      if isDecimal(c) {
         s.ungetr()
         s.unread(1) // correct position of '.' (needed by startLit in number)
         s.number('.')
         break
      }
      if c == '.' {
         c = s.getr()
         if c == '.' {
            s.tok = _DotDotDot
            break
         }
         s.unread(1)
      }
      s.ungetr()
      s.tok = _Dot
 case '+':
      s.op, s.prec = Add, precAdd
      c = s.getr()
      if c != '+' {
         goto assignop
      }
      s.nlsemi = true
      s.tok = _IncOp
 case '-':
      s.op, s.prec = Sub, precAdd
      c = s.getr()
      if c != '-' {
         goto assignop
      }
      s.nlsemi = true
      s.tok = _IncOp
 case '*':
      s.op, s.prec = Mul, precMul
 // don't goto assignop - want _Star token
      if s.getr() == '=' {
         s.tok = _AssignOp
         break
      }
      s.ungetr()
      s.tok = _Star
 case '/':
      c = s.getr()
      if c == '/' {
         s.lineComment()
         goto redo
      }
      if c == '*' {
         s.fullComment()
         if s.source.line > s.line && nlsemi {
            // A multi-line comment acts like a newline;
 // it translates to a ';' if nlsemi is set. s.lit = "newline"
           s.tok = _Semi
           break
         }
         goto redo
      }
      s.op, s.prec = Div, precMul
      goto assignop
   case '%':
      s.op, s.prec = Rem, precMul
      c = s.getr()
      goto assignop
   case '&':
      c = s.getr()
      if c == '&' {
         s.op, s.prec = AndAnd, precAndAnd
         s.tok = _Operator
         break
      }
      s.op, s.prec = And, precMul
      if c == '^' {
         s.op = AndNot
         c = s.getr()
      }
      goto assignop
   case '|':
      c = s.getr()
      if c == '|' {
         s.op, s.prec = OrOr, precOrOr
 s.tok = _Operator
 break
 }
      s.op, s.prec = Or, precAdd
      goto assignop
   case '^':
      s.op, s.prec = Xor, precAdd
      c = s.getr()
      goto assignop
   case '<':
      c = s.getr()
      if c == '=' {
         s.op, s.prec = Leq, precCmp
         s.tok = _Operator
         break
      }
      if c == '<' {
         s.op, s.prec = Shl, precMul
         c = s.getr()
         goto assignop
      }
      if c == '-' {
         s.tok = _Arrow
         break
      }
      s.ungetr()
      s.op, s.prec = Lss, precCmp
      s.tok = _Operator
   case '>':
      c = s.getr()
      if c == '=' {
         s.op, s.prec = Geq, precCmp
         s.tok = _Operator
         break
      }
      if c == '>' {
         s.op, s.prec = Shr, precMul
 c = s.getr()
         goto assignop
      }
      s.ungetr()
      s.op, s.prec = Gtr, precCmp
      s.tok = _Operator
   case '=':
      if s.getr() == '=' {
         s.op, s.prec = Eql, precCmp
         s.tok = _Operator
         break
      }
      s.ungetr()
      s.tok = _Assign
   case '!':
      if s.getr() == '=' {
         s.op, s.prec = Neq, precCmp
         s.tok = _Operator
         break
      }
      s.ungetr()
      s.op, s.prec = Not, 0
      s.tok = _Operator
   default:
      s.tok = 0
      s.errorf("invalid character %#U", c)
      goto redo
   }
   return
assignop:
 if c == '=' {
      s.tok = _AssignOp
      return
 }
 s.ungetr()
 s.tok = _Operator
}

經過getr獲取下一個字符,經過許多不一樣的switch/case來識別token。
而經過解析器(parser)的fileOrNil方法來將識別的token加入到語法樹:

func (p *parser) fileOrNil() *File {
   if trace {
      defer p.trace("file")()
   }
   f := new(File)
   f.pos = p.pos()
   // PackageClause
   if !p.got(_Package) {
      p.syntaxError("package statement must be first")
      return nil
   }
   f.PkgName = p.name()
   p.want(_Semi)
   // don't bother continuing if package clause has errors
   if p.first != nil {
      return nil
   }
   // { ImportDecl ";" }
   for p.got(_Import) {
      f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
      p.want(_Semi)
   }
   // { TopLevelDecl ";" }
   for p.tok != _EOF {
      switch p.tok {
      case _Const:
         p.next()
         f.DeclList = p.appendGroup(f.DeclList, p.constDecl)
      case _Type:
         p.next()
         f.DeclList = p.appendGroup(f.DeclList, p.typeDecl)
      case _Var:
         p.next()
         f.DeclList = p.appendGroup(f.DeclList, p.varDecl)
      case _Func:
         p.next()
         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]) {
            // opening { of function declaration on next line
             p.syntaxError("unexpected semicolon or newline before {")
         } else {
            p.syntaxError("non-declaration statement outside function body")
         }
         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
}

經過fileOrNil方法,不難看出go語言四種不一樣的頂級聲明token(TopLevelDecl)詞法節點:_Const(常量)、_Type(類型)、_Var(變量)、_Func(函數及方法)以及_Package和_Import聲明token。
解析器還封裝了p.want()和p.got(),進行前驅分析和意圖判斷。

func (p *parser) got(tok token) bool {
   if p.tok == tok {
      p.next()//若是當前tok和指定token相等,返回true並自動前驅獲取下個token
      return true
   }
   return false
}
func (p *parser) want(tok token) {
   if !p.got(tok) {//若是不符合預期則報語法錯誤。
      p.syntaxError("expecting " + tokstring(tok))
      p.advance()
   }
}

got 其實只是用於快速判斷一些語句中的關鍵字,若是當前解析器中的 Token 是傳入的 Token 就會直接跳過該 Token 並返回 true;而 want 就是對 got 的簡單封裝了,若是當前的 Token 不是咱們指望的,就會馬上返回語法錯誤並結束此次編譯。

詞法分析節點介紹

// 頂級聲明
type (
   Decl interface {
      Node
      aDecl()
   }
   //              Path
 // LocalPkgName Path 
   ImportDecl struct {
      LocalPkgName *Name // including "."; nil means no rename present
      Path         *BasicLit
      Group        *Group // nil means not part of a group
      decl
 }
 // NameList
 // NameList      = Values // NameList Type = Values 
    ConstDecl struct {
      NameList []*Name
      Type     Expr // nil means no type
      Values   Expr // nil means no values
      Group    *Group // nil means not part of a group
      decl
    }
   // Name Type
    TypeDecl struct {
      Name   *Name
      Alias  bool
      Type   Expr
      Group  *Group // nil means not part of a group
      Pragma Pragma
      decl 
    }
   // NameList Type
 // NameList Type = Values // NameList      = Values 
    VarDecl struct {
      NameList []*Name
      Type     Expr // nil means no type
      Values   Expr // nil means no values
      Group    *Group // nil means not part of a group
      decl
 }
   // func          Name Type { Body }
   // func          Name Type // func Receiver Name Type { Body } // func Receiver Name Type 
   FuncDecl struct {
      Attr   map[string]bool // go:attr map
      Recv   *Field // nil means regular function
      Name   *Name
      Type   *FuncType
      Body   *BlockStmt // nil means no body (forward declaration)
      Pragma Pragma // TODO(mdempsky): Cleaner solution.
      decl
   }
)

以FuncDecl爲例,函數體存儲類型爲BlockStmt指針,常見statments定義以下:

// Statements
type (
   Stmt interface {
      Node
      aStmt()
   }
   SimpleStmt interface {
      Stmt
      aSimpleStmt()
   }
   EmptyStmt struct {
      simpleStmt
   }
   LabeledStmt struct {
      Label *Name
      Stmt  Stmt
      stmt 
   }
   BlockStmt struct {
      List   []Stmt
      Rbrace Pos
      stmt 
   }
   ExprStmt struct {
      X Expr
      simpleStmt 
   }
   SendStmt struct {
      Chan, Value Expr // Chan <- Value
      simpleStmt
 }
   DeclStmt struct {
      DeclList []Decl
      stmt 
   }
   AssignStmt struct {
      Op       Operator // 0 means no operation
      Lhs,Rhs Expr // Rhs == ImplicitOne means Lhs++ (Op == Add) or Lhs-- (Op == Sub)
      simpleStmt
   }
   BranchStmt struct {
      Tok   token // Break, Continue, Fallthrough, or Goto
      Label *Name
 // Target is the continuation of the control flow after executing
 // the branch; it is computed by the parser if CheckBranches is set. // Target is a *LabeledStmt for gotos, and a *SwitchStmt, *SelectStmt, // or *ForStmt for breaks and continues, depending on the context of // the branch. Target is not set for fallthroughs. Target Stmt
      stmt 
 }
   CallStmt struct {
      Tok  token // Go or Defer
      Call *CallExpr
      stmt 
    }
   ReturnStmt struct {
      Results Expr // nil means no explicit return values
      stmt
   }
   IfStmt struct {
      Init SimpleStmt
      Cond Expr
      Then *BlockStmt
      Else Stmt // either nil, *IfStmt, or *BlockStmt
      stmt
   }
   ForStmt struct {
      Init SimpleStmt // incl. *RangeClause
      Cond Expr
      Post SimpleStmt
      Body *BlockStmt
      stmt 
    }
   SwitchStmt struct {
      Init   SimpleStmt
      Tag    Expr // incl. *TypeSwitchGuard
      Body   []*CaseClause
      Rbrace Pos
      stmt 
    }
   SelectStmt struct {
      Body   []*CommClause
      Rbrace Pos
      stmt 
   }
)

這些不一樣類型的 Stmt 構成了所有命令式的 Go 語言代碼,從中咱們能夠看到很是多熟悉的控制結構,例如 ifforswitchselect

語法分析

由於詞法分析器 scanner 做爲結構體被嵌入到了 parser 中,因此這個方法中的 p.next() 實際上調用的是 scannernext 方法,它會直接獲取文件中的下一個 Token,因此詞法分析和語法分析實際上是一塊兒進行的。
上面parseFiles方法中詞法掃描器和語法解析器(syntax.Parse)生成源文件對應的syntax.File對象後,生成抽象語法樹的過程是經過p.decls()方法生成的xtop = append(xtop, p.decls(p.file.DeclList)...)
xtop是go語言抽象語法樹數組
var xtop []*Node

func (p *noder) node() {
   types.Block = 1
   imported_unsafe = false
   p.setlineno(p.file.PkgName)
   mkpackage(p.file.PkgName.Value)
   xtop = append(xtop, p.decls(p.file.DeclList)...)//生成抽象語法樹
   for _, n := range p.linknames {
      if !imported_unsafe {
         p.yyerrorpos(n.pos, "//go:linkname only allowed in Go files that import "unsafe"")
         continue
   }
   s := lookup(n.local)
   if n.remote != "" {
      s.Linkname = n.remote
   } else {
         // Use the default object symbol name if the
 // user didn't provide one. 
        if myimportpath == "" {
            p.yyerrorpos(n.pos, "//go:linkname requires linkname argument or -p compiler flag")
        } else {
            s.Linkname = objabi.PathToPrefix(myimportpath) + "." + n.local
         }
      }
   }
   // The linker expects an ABI0 wrapper for all cgo-exported
 // functions. for _, prag := range p.pragcgobuf {
      switch prag[0] {
      case "cgo_export_static", "cgo_export_dynamic":
         if symabiRefs == nil {
            symabiRefs = make(map[string]obj.ABI)
         }
         symabiRefs[prag[1]] = obj.ABI0
      }
   }
   pragcgobuf = append(pragcgobuf, p.pragcgobuf...)
   lineno = src.NoXPos
   clearImports()
}
//xtop聲明以下:
var xtop []*Node

語法樹單個樹節點定義以下:

// A Node is a single node in the syntax tree.
// Actually the syntax tree is a syntax DAG, because there is only one
// node with Op=ONAME for a given instance of a variable x.
// The same is true for Op=OTYPE and Op=OLITERAL. See Node.mayBeShared.
type Node struct {
   // Tree structure.
 // Generic recursive walks should follow these fields.   Left  *Node
  Right *Node
  Ninit Nodes
  Nbody Nodes
  List  Nodes
  Rlist Nodes
 // most nodes
  Type *types.Type
  Orig *Node // original form, for printing, and tracking copies of ONAMEs
 // func Func *Func
 // ONAME, OTYPE, OPACK, OLABEL, some OLITERAL
  Name *Name
  Sym *types.Sym // various
  E   interface{} // Opt or Val, see methods below
 // Various. Usually an offset into a struct. For example: // - ONAME nodes that refer to local variables use it to identify their stack frame position. // - ODOT, ODOTPTR, and ORESULT use it to indicate offset relative to their base address. // - OSTRUCTKEY uses it to store the named field's offset. // - Named OLITERALs use it to store their ambient iota value. // - OINLMARK stores an index into the inlTree data structure. // - OCLOSURE uses it to store ambient iota value, if any. // Possibly still more uses. If you find any, document them. Xoffset int64
  Pos src.XPos
  flags bitset32
  Esc uint16 // EscXXX
  Op  Op
  aux uint8
}

語法樹實例分析

下來咱們來經過Goland IDE來debug下golang編譯器
golang程序源碼以下

package main

import "fmt"

type st struct {
    name string
    age  int
}
type St st
var (
    avg int32  = 123
    tag string = "aaa"
    //ttt int64
)
const (
    A = 111
    B = 222
)
func Echo(t string, f interface{}) {
    fmt.Printf(t, f)
}
func init() {
}
func main() {
    a := [...]int{1, 2, 3}
    Echo("a:%+v", a)
}
2、斷點設置

斷點設置go/src/cmd/compile/internal/gc/noder.go:246
image.png
運行至端點後鼠標浮於xtop變量之上,點擊+號浮框展現變量內容:
image
展開xtop變量觀察語法樹數組結構以下:
image
上圖展現的是xtop[0]和xtop[2]及部分xtop[8]

總結

瞭解 Go 語言的詞法分析器 scanner 和語法分析器 parser 讓咱們對解析器處理源代碼的過程有着比較清楚的認識,同時咱們也在 Go 語言的文法和語法分析器中找到了熟悉的關鍵字和語法結構,加深了對 Go 語言的理解。

相關文章
相關標籤/搜索