日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

了解Go编译处理(三)—— 初识go compile

發布時間:2024/8/1 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 了解Go编译处理(三)—— 初识go compile 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

了解Go編譯處理(二)—— go build一文中對go build的過程進行了追蹤,build的源碼中并不負責代碼的編譯,而是交給專門的編譯工具進行編譯,完整的build過程使用到以下工具:

cover-cgo-compile-asm-pack-buildid-link

部分工具因文件類型的不一或編譯參數的設置,可能不會調用。

關于.go的文件的編譯由compile工具進行處理,本文先大致了解下compile的大致處理過程。

compile

compile是指位于安裝目錄($/go/pkg/tool/$GOOS_$GOARCH)下compile工具,build過程中會調用compile相關命令對文件等內容進行處理。

直接運行compile命令,可看到如下提示:

usage: compile [options] file.go...-% debug non-static initializers-+ compiling runtime-B disable bounds checking-C disable printing of columns in error messages-D pathset relative path for local imports...

compile本身也是go實現的,其源碼位于src/cmd/compile目錄下(源碼的查找小秘訣,我們在之前的文章中說過了呦😉)。

編譯入口compile/main

var archInits = map[string]func(*gc.Arch){"386": x86.Init,"amd64": amd64.Init,"arm": arm.Init,"arm64": arm64.Init,"mips": mips.Init,"mipsle": mips.Init,"mips64": mips64.Init,"mips64le": mips64.Init,"ppc64": ppc64.Init,"ppc64le": ppc64.Init,"riscv64": riscv64.Init,"s390x": s390x.Init,"wasm": wasm.Init, } func main() {// disable timestamps for reproducible outputlog.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) }

archInits包含了各個處理器架構的處理初始化方式。main中會通過獲取當前處理器架構,再從archInits獲取archInit,最后調用gc的Main及Exit。

main主要是為調用gc中的相關方法提供一個入口。

gc

此處的gc的意思是Go compiler,并不是常見的垃圾收集器。

Main

根據Main的注釋可以知道:Main對命令行中flags及go源文件進行解析,對解析的包進行類型檢查,然后將functions編譯成機器碼,最后將編譯好的文件寫入磁盤。

Main的代碼實在太長了,此處僅保留關注處。

parseFiles是最關鍵的部分,負責文件的解析、語法解析、轉換,封裝成一個個的Node,然后append至xtop,隨后就是對xtop中數據的檢查。

// Main parses flags and Go source files specified in the command-line // arguments, type-checks the parsed Go package, compiles functions to machine // code, and finally writes the compiled package definition to disk. func Main(archInit func(*Arch)) {...// pseudo-package, for scopingbuiltinpkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin?builtinpkg.Prefix = "go.builtin" // not go%2ebuiltin// pseudo-package, accessed by import "unsafe"unsafepkg = types.NewPkg("unsafe", "unsafe")...// 頭部是一系列flags的說明及解析,這里對應的就是前言中提到的命令提示哦// 這里還可以看到花式的flag的使用方式,如指定func,感興趣的可以了解下flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime")flag.BoolVar(&compiling_std, "std", false, "compiling standard library")...lines := parseFiles(flag.Args())//解析文件,將import、var、const、type、func等相關的聲明封裝成node,然后append至xtop...// Process top-level declarations in phases.// Phase 1: const, type, and names and types of funcs.// This will gather all the information about types// and methods but doesn't depend on any of it.//// We also defer type alias declarations until phase 2// to avoid cycles like #18640.// TODO(gri) Remove this again once we have a fix for #25838.// Don't use range--typecheck can add closures to xtop.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)}}// Phase 2: Variable assignments.// To check interface assignments, depends on phase 1.// Don't use range--typecheck can add closures to xtop.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)}}// Phase 3: Type check function bodies.// Don't use range--typecheck can add closures to xtop.timings.Start("fe", "typecheck", "func")var fcount int64for i := 0; i < len(xtop); i++ {n := xtop[i]if op := n.Op; op == ODCLFUNC || op == OCLOSURE {Curfn = ndecldepth = 1saveerrors()typecheckslice(Curfn.Nbody.Slice(), ctxStmt)checkreturn(Curfn)if nerrors != 0 {Curfn.Nbody.Set(nil) // type errors; do not compile}// Now that we've checked whether n terminates,// we can eliminate some obviously dead code.deadcode(Curfn)fcount++}}// With all types checked, it's now safe to verify map keys. One single// check past phase 9 isn't sufficient, as we may exit with other errors// before then, thus skipping map key errors.checkMapKeys()timings.AddEvent(fcount, "funcs")if nsavederrors+nerrors != 0 {errorexit()}// Phase 4: Decide how to capture closed variables.// This needs to run before escape analysis,// because variables captured by value do not escape.timings.Start("fe", "capturevars")for _, n := range xtop {if n.Op == ODCLFUNC && n.Func.Closure != nil {Curfn = ncapturevars(n)}}capturevarscomplete = trueCurfn = nilif nsavederrors+nerrors != 0 {errorexit()}// Phase 5: Inliningtimings.Start("fe", "inlining")if Debug_typecheckinl != 0 {// Typecheck imported function bodies if debug['l'] > 1,// otherwise lazily when used or re-exported.for _, n := range importlist {if n.Func.Inl != nil {saveerrors()typecheckinl(n)}}if nsavederrors+nerrors != 0 {errorexit()}}if Debug['l'] != 0 {// Find functions that can be inlined and clone them before walk expands them.visitBottomUp(xtop, func(list []*Node, recursive bool) {for _, n := range list {if !recursive {caninl(n)} else {if Debug['m'] > 1 {fmt.Printf("%v: cannot inline %v: recursive\n", n.Line(), n.Func.Nname)}}inlcalls(n)}})}// Phase 6: Escape analysis.// Required for moving heap allocations onto stack,// which in turn is required by the closure implementation,// which stores the addresses of stack variables into the closure.// If the closure does not escape, it needs to be on the stack// or else the stack copier will not update it.// Large values are also moved off stack in escape analysis;// because large values may contain pointers, it must happen early.timings.Start("fe", "escapes")escapes(xtop)// Collect information for go:nowritebarrierrec// checking. This must happen before transformclosure.// We'll do the final check after write barriers are// inserted.if compiling_runtime {nowritebarrierrecCheck = newNowritebarrierrecChecker()}// Phase 7: Transform closure bodies to properly reference captured variables.// This needs to happen before walk, because closures must be transformed// before walk reaches a call of a closure.timings.Start("fe", "xclosures")for _, n := range xtop {if n.Op == ODCLFUNC && n.Func.Closure != nil {Curfn = ntransformclosure(n)}}// Prepare for SSA compilation.// This must be before peekitabs, because peekitabs// can trigger function compilation.initssaconfig()// Just before compilation, compile itabs found on// the right side of OCONVIFACE so that methods// can be de-virtualized during compilation.Curfn = nilpeekitabs()// Phase 8: Compile top level functions.// Don't use range--walk can add functions to xtop.timings.Start("be", "compilefuncs")fcount = 0for i := 0; i < len(xtop); i++ {n := xtop[i]if n.Op == ODCLFUNC {funccompile(n)fcount++}}timings.AddEvent(fcount, "funcs")if nsavederrors+nerrors == 0 {fninit(xtop)}compileFunctions()if nowritebarrierrecCheck != nil {// Write barriers are now known. Check the// call graph.nowritebarrierrecCheck.check()nowritebarrierrecCheck = nil}// Finalize DWARF inline routine DIEs, then explicitly turn off// DWARF inlining gen so as to avoid problems with generated// method wrappers.if Ctxt.DwFixups != nil {Ctxt.DwFixups.Finalize(myimportpath, Debug_gendwarfinl != 0)Ctxt.DwFixups = nilgenDwarfInline = 0}// Phase 9: Check external declarations.timings.Start("be", "externaldcls")for i, n := range externdcl {if n.Op == ONAME {externdcl[i] = typecheck(externdcl[i], ctxExpr)}}// Check the map keys again, since we typechecked the external// declarations.checkMapKeys()if nerrors+nsavederrors != 0 {errorexit()}// Write object data to disk.timings.Start("be", "dumpobj")dumpdata()Ctxt.NumberSyms(false)dumpobj()if asmhdr != "" {dumpasmhdr()}// Check whether any of the functions we have compiled have gigantic stack frames.sort.Slice(largeStackFrames, func(i, j int) bool {return largeStackFrames[i].pos.Before(largeStackFrames[j].pos)})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)}}if len(compilequeue) != 0 {Fatalf("%d uncompiled functions", len(compilequeue))}logopt.FlushLoggedOpts(Ctxt, myimportpath)if nerrors+nsavederrors != 0 {errorexit()}flusherrors()timings.Stop()if benchfile != "" {if err := writebench(benchfile); err != nil {log.Fatalf("cannot write benchmark data: %v", err)}} }

粗略來看,Main的過程大致分為以下幾個部分:

  • 處理器架構初始化及上下文的關聯
  • 構建偽包,方便對應包中func的使用
  • flags聲明及解析,這是對命令參數解析的核心
  • 參數檢查,確認是否滿足編譯條件
  • 解析文件,轉換為語法樹,構造node,存入xtop(解析的過程是并發的)
  • 對xtop中數據依次進行處理
  • 寫入編譯后的文件至磁盤
  • 對xtop數據的處理過程具體可以分為以下幾個階段。

  • const、type、var、func的類型檢查,此步驟不處理賦值。
  • 在1的基礎上對變量賦值。
  • 對func body進行類型檢查。
    以上3步檢查結束后會進行map keys的檢查。
  • 決定如何捕獲閉合變量。
  • 內聯檢查。
  • 逃逸分析。
  • 轉換閉包體以正確引用捕獲的變量。
    準備SSA編譯。
  • 編譯頂級函數。
    func間的調用處理在此處進行。
  • 檢查外部聲明。
    再次檢查map keys。
  • 以上是編譯的大致過程,后續會關注細節處理。

    總結

    本文主要從源碼及其注釋的角度對compile的過程有個初步了解,在稍后的文章中我們會關注更具體的處理細節。概括一下,compile的過程:

    解析文件->解析語法->類型檢查->編譯->寫入文件

    公眾號

    鄙人剛剛開通了公眾號,專注于分享Go開發相關內容,望大家感興趣的支持一下,在此特別感謝。

    總結

    以上是生活随笔為你收集整理的了解Go编译处理(三)—— 初识go compile的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。