cmd/go/internal/load

包:根据包的ImportPath确定引用名称

如果是顶级路径:log, fmt等,则直接使用其本身作为包名
非顶级路径:如果没有使用go模块,则直接使用路径分割的最后一个,如果;如果使用go模块(> go1.11), 则如果最后一个是v2,v3,… v10 (v+有效数字, 除了v0,v1,v05), 则判定是版本,使用倒数第二个路径元素作为引入名称。

cmd/go/internal/modload

加载模块

loader.resolveMissingImports
buildList

一个全局对象,包含所有需要构建的模块

queryImport
queryImport
func queryImport(ctx context.Context, path string) (module.Version, error) {
}
Init
go mod initfindModuleRootGO111MODULEonoffautoauto
$GOPATH/go.mod
cfg.ModulesEnabled = true
findModuleRoot
LoadPackages
LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string)
Lookup

检查模块冲突

buildList
cmd/go/internal/cfg
-a-o
cmd/go/internal/work
go build
-work-debug-actiongraph=graph.jsongraph.json
go build
work.runBuild()
    -> BuildInit()
        -> modload.Init()
load.PackagesForBuild(args)  
    -> load.PackagesAndErros(args)
        -> 将命令行的patterns转换成初始的packages
            -> 预加载通过Context.Import和Context.ImportDir来获取所有的Import依赖(递归进行,go协程,最大并发数=CPU数,见下面的介绍)
 work.CompileAction(mainPackage)   // 加载main包,构建依赖树,从依赖为0的包开始编译,直到最终编译完主包(main)
load.PackagesForBuild(args)

并发加载pkg信息的过程

首先准备一个大小为N的并发控制管道,每次获得一个管道占用的机会,就启动一个协程加载包信息,加载完成后释放管道。然后对引用的所有包,递归执行这个过程。

由于所有子包的加载都在协程中进行,所以假如所有的初始包都能获得管道占用的机会,那么这个过程就不再阻塞,但是此时依赖包、依赖包的依赖包可能都还没有加载完成,因为协程还在递归产生更多协程。

加载的包将会保存在缓存中。

如何检测import环?

LoadImportPackage.Internal.Imports
Package.Internal.ImportsPackage.Internal.Imports
CompileAction

假定有包的依赖关系:

main
   |-->a
   |   |-->b
   |
   |-->c
       |-->a

Action结构如下:

type Action struct {
	Mode       string                                         // description of action operation
	Package    *load.Package                                  // the package this action works on
	Deps       []*Action                                      // actions that must happen before this one
	Func       func(*Builder, context.Context, *Action) error // the action itself (nil = no-op)

	Args       []string                                       // additional args for runProgram

	triggers []*Action // inverse of deps

	// Generated files, directories.
	Objdir   string         // directory for intermediate objects
	Target   string         // goal of the action: the created package or executable
	built    string         // the actual created package or executable
	actionID cache.ActionID // cache ID of action input
	buildID  string         // build ID of action output


	// Execution state.
	pending   int         // number of deps yet to complete
	priority  int         // relative execution priority
	Failed    bool        // whether the action failed
	//...
}
Depstriggers
work.Builder.Do()Action
-debug-actiongraph=graph.json
unsafebuiltin

构建过程:首先根据Imports从main节点构造出Action的依赖树,然后深度遍历转换成列表,使用一个同样大小的channel来记录待完成的任务,根据Deps构造triggers逆反关系,依赖数量使用pending记录。

Actiontriggerspendingpending==0main.pending==0

核心编译过程

cmd/go/internal/workBuilder.buildgo tool compile

每个包的Action都会使用所有的编译参数来生成一个唯一的Hash, 包括本次编译涉及的所有源文件内容和标志等。

go tool compileobjdirobjdir/_pkg_.agoccasmc.o
go tool packobjdir/_pkg_.apack.a.ogo tool packar
Build-Cache
-workBuild-CacheBuild-Cacheobjdir/_pkg_.a

编译缓存

Action.Hashgo-build~/Library/Caches/go-build/4b/4bb6ff70820dec2d60de038c0e2a6f264995f0f233d945672f7f2dd1778646bb-d
Build-Cachearmainlink
go compile
go build
-work
go build somefile.go
go build *.go*.gogo build "*.go"
go buildgo build init.go main.gocfg.BuildO = initgo build main.go init.gocfg.BuildO = main
go build
go build .
go build some
./../
allallallGOPATH
...
  • 名称为testdata
  • 含有go.mod(也就是说不会递归导入模块)
  • 不含有go文件的空目录(因为不是一个go包,go包至少含有一个go文件)

单个包和多个包构建的区别

go build somego build-ocfg.BuildO.alink
mainlinkWorkWORK-work
linkbuildbuildBuild-Cachelinklinkbuild
build actionlink actionbuild actionbuild actionBuild-Cachebuild action
go build -o dir/
-o/main
cmd/go/internal/imports

构建标签(buildtags)

loadTags(), 首先会把 GOOS,GOARCH, Compiler(值:“gc”, “gccgo”) 作为tag传入,根据环境变量注入"cgo"标签,然后是命令行指定的标签

BuildID
BuildIDActionID + "/" + ActionIDAction.TargetBuildIDActionActionID + "/"
go/build
Context

BuildContext 定义了默认的Context
Context结构,定义了构建的上下文,build tags, 并提供了几个可以定制的函数,如果没有赋值使用默认实现。

// A Context specifies the supporting context for a build.
type Context struct {
	GOARCH string // target architecture
	GOOS   string // target operating system
	GOROOT string // Go root
	GOPATH string // Go path

	// Dir is the caller's working directory, or the empty string to use
	// the current directory of the running process. In module mode, this is used
	// to locate the main module.
	//
	// If Dir is non-empty, directories passed to Import and ImportDir must
	// be absolute.
	Dir string

	CgoEnabled  bool   // whether cgo files are included
	UseAllFiles bool   // use files regardless of +build lines, file names
	Compiler    string // compiler to assume when computing target paths

	// The build and release tags specify build constraints
	// that should be considered satisfied when processing +build lines.
	// Clients creating a new context may customize BuildTags, which
	// defaults to empty, but it is usually an error to customize ReleaseTags,
	// which defaults to the list of Go releases the current release is compatible with.
	// BuildTags is not set for the Default build Context.
	// In addition to the BuildTags and ReleaseTags, build constraints
	// consider the values of GOARCH and GOOS as satisfied tags.
	// The last element in ReleaseTags is assumed to be the current release.
	BuildTags   []string
	ReleaseTags []string

	// The install suffix specifies a suffix to use in the name of the installation
	// directory. By default it is empty, but custom builds that need to keep
	// their outputs separate can set InstallSuffix to do so. For example, when
	// using the race detector, the go command uses InstallSuffix = "race", so
	// that on a Linux/386 system, packages are written to a directory named
	// "linux_386_race" instead of the usual "linux_386".
	InstallSuffix string

	// By default, Import uses the operating system's file system calls
	// to read directories and files. To read from other sources,
	// callers can set the following functions. They all have default
	// behaviors that use the local file system, so clients need only set
	// the functions whose behaviors they wish to change.

	// JoinPath joins the sequence of path fragments into a single path.
	// If JoinPath is nil, Import uses filepath.Join.
	JoinPath func(elem ...string) string

	// SplitPathList splits the path list into a slice of individual paths.
	// If SplitPathList is nil, Import uses filepath.SplitList.
	SplitPathList func(list string) []string

	// IsAbsPath reports whether path is an absolute path.
	// If IsAbsPath is nil, Import uses filepath.IsAbs.
	IsAbsPath func(path string) bool

	// IsDir reports whether the path names a directory.
	// If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
	IsDir func(path string) bool

	// HasSubdir reports whether dir is lexically a subdirectory of
	// root, perhaps multiple levels below. It does not try to check
	// whether dir exists.
	// If so, HasSubdir sets rel to a slash-separated path that
	// can be joined to root to produce a path equivalent to dir.
	// If HasSubdir is nil, Import uses an implementation built on
	// filepath.EvalSymlinks.
	HasSubdir func(root, dir string) (rel string, ok bool)

	// ReadDir returns a slice of fs.FileInfo, sorted by Name,
	// describing the content of the named directory.
	// If ReadDir is nil, Import uses ioutil.ReadDir.
	ReadDir func(dir string) ([]fs.FileInfo, error)

	// OpenFile opens a file (not a directory) for reading.
	// If OpenFile is nil, Import uses os.Open.
	OpenFile func(path string) (io.ReadCloser, error)
}
Context.Import
Context.Importgo list -e -- path-e
go tool compile

支持的命令行标志

-+-std-d strgo tool compile -d help-packfile.afile.o-trimpath-p pkg
go build
go tool compile -o $WORK/b001/_pkg_.a -trimpath $WORK/b001=> -p main -lang=go1.13 -complete -
buildid 9_0557aJwaAvYDCxLHSA/9_0557aJwaAvYDCxLHSA -D  -importcfg $WORK/b001/importcfg -pack -c=4 ./init.go ./main.go $WORK/b001/_gomod_.go

初始化工作

gc.Maingo.builtinunsaferuntime
	localpkg = types.NewPkg("", "")
	localpkg.Prefix = "\"\""

	// We won't know localpkg's height until after import
	// processing. In the mean time, set to MaxPkgHeight to ensure
	// height comparisons at least work until then.
	localpkg.Height = types.MaxPkgHeight

	// pseudo-package, for scoping
	builtinpkg = 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")

	// Pseudo-package that contains the compiler's builtin
	// declarations for package runtime. These are declared in a
	// separate package to avoid conflicts with package runtime's
	// actual declarations, which may differ intentionally but
	// insignificantly.
	Runtimepkg = types.NewPkg("go.runtime", "runtime")
	Runtimepkg.Prefix = "runtime"

	// pseudo-packages used in symbol tables
	itabpkg = types.NewPkg("go.itab", "go.itab")
	itabpkg.Prefix = "go.itab" // not go%2eitab

	itablinkpkg = types.NewPkg("go.itablink", "go.itablink")
	itablinkpkg.Prefix = "go.itablink" // not go%2eitablink

	trackpkg = types.NewPkg("go.track", "go.track")
	trackpkg.Prefix = "go.track" // not go%2etrack

	// pseudo-package used for map zero values
	mappkg = types.NewPkg("go.map", "go.map")
	mappkg.Prefix = "go.map"

	// pseudo-package used for methods with anonymous receivers
	gopkg = types.NewPkg("go", "")

-importcfg

读取包名到已编译的文件对象的映射,后续的编译只需要使用这个map来确定编译好的文件。
该文件内容格式:

packagefile name=path
...
importmap alias=name
....
initUniverse
intdoubleerrorbyteruneunsafe.Pointer
builtinPointerunsafe

因此实际上这些包是不会专门去编译生成的。

parseFiles(files)
*syntax.File*Nodedeclsxtop
import语句.a.o
resolve
intiota*NoderesolveOp==ONONAME
const (
	C0 int = iota   // int和iota是未解析的
	C1
)
var a NONE  // NONE是未解析的
typecheck

调用类型检查

resolveOp==ONONAME:: undefined: 
deadcode

无用代码是什么意思呢?举个例子:

a && b   如果a=true, 则简化为b
         如果a=false, 则简化为a

a || b   如果a=true,则简化为a
         如果a=false,则简化为b

if a {
   left
}else{
   right
}
           如果a=true则简化为left,a=false则简化为right

if a {
  ...
  panic or return   
}
...
          如果a=true,且if或else的语句以panic或return结束,   则if后面的所有语句都可以消除
fninit(nodes)
init

遍历语法树,构建出变量初始化依赖图。如果存在环状依赖,则报错

var g = b
var b = d
var d = g
cycleinit.go:5:5: typechecking loop involving g = b
        cycleinit.go:5:5 g
        cycleinit.go:8:5 d = g
        cycleinit.go:8:5 d
        cycleinit.go:6:5 b = d
        cycleinit.go:6:5 b
        cycleinit.go:5:5 g = b
capturevars(fn)

变量捕获分析(值捕获或引用捕获)

typecheckinl

内联

escapes(xtop)

逃逸分析

transformclosure

闭包转换

initssaconfigcompileFunctions

ssa编译和pass优化

typecheck(external)

外部依赖分析

GOSSAFUNCGOSSADIR
GOSSAFUNC
GOSSADIR$GOSSADIR/$importPath/$funcName.html
compile
go tool compile -o tmp/moduleworld.a -p main -lang=go1.13 -complete *.go
syntax.ParseFile
ParseFilego tool compile