了解Go编译处理(三)—— 初识go compile
前言
了解Go編譯處理(二)—— go build一文中對go build的過程進(jìn)行了追蹤,build的源碼中并不負(fù)責(zé)代碼的編譯,而是交給專門的編譯工具進(jìn)行編譯,完整的build過程使用到以下工具:
cover-cgo-compile-asm-pack-buildid-link
部分工具因文件類型的不一或編譯參數(shù)的設(shè)置,可能不會調(diào)用。
關(guān)于.go的文件的編譯由compile工具進(jìn)行處理,本文先大致了解下compile的大致處理過程。
compile
compile是指位于安裝目錄($/go/pkg/tool/$GOOS_$GOARCH)下compile工具,build過程中會調(diào)用compile相關(guān)命令對文件等內(nèi)容進(jìn)行處理。
直接運行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實現(xiàn)的,其源碼位于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包含了各個處理器架構(gòu)的處理初始化方式。main中會通過獲取當(dāng)前處理器架構(gòu),再從archInits獲取archInit,最后調(diào)用gc的Main及Exit。
main主要是為調(diào)用gc中的相關(guān)方法提供一個入口。
gc
此處的gc的意思是Go compiler,并不是常見的垃圾收集器。
Main
根據(jù)Main的注釋可以知道:Main對命令行中flags及go源文件進(jìn)行解析,對解析的包進(jìn)行類型檢查,然后將functions編譯成機器碼,最后將編譯好的文件寫入磁盤。
Main的代碼實在太長了,此處僅保留關(guān)注處。
parseFiles是最關(guān)鍵的部分,負(fù)責(zé)文件的解析、語法解析、轉(zhuǎn)換,封裝成一個個的Node,然后append至xtop,隨后就是對xtop中數(shù)據(jù)的檢查。
// 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的說明及解析,這里對應(yīng)的就是前言中提到的命令提示哦// 這里還可以看到花式的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等相關(guān)的聲明封裝成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的過程大致分為以下幾個部分:
對xtop數(shù)據(jù)的處理過程具體可以分為以下幾個階段。
以上3步檢查結(jié)束后會進(jìn)行map keys的檢查。
準(zhǔn)備SSA編譯。
func間的調(diào)用處理在此處進(jìn)行。
再次檢查map keys。
以上是編譯的大致過程,后續(xù)會關(guān)注細(xì)節(jié)處理。
總結(jié)
本文主要從源碼及其注釋的角度對compile的過程有個初步了解,在稍后的文章中我們會關(guān)注更具體的處理細(xì)節(jié)。概括一下,compile的過程:
解析文件->解析語法->類型檢查->編譯->寫入文件
公眾號
鄙人剛剛開通了公眾號,專注于分享Go開發(fā)相關(guān)內(nèi)容,望大家感興趣的支持一下,在此特別感謝。
總結(jié)
以上是生活随笔為你收集整理的了解Go编译处理(三)—— 初识go compile的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go语言特性
- 下一篇: 《SPSS统计分析与行业应用实战》之序言