6.3 总体逻辑

创建初始化任务的主逻辑在文件cmd/compile/internal/pkginit/init.go的方法Task()中,刨去细节处理,总体逻辑可以整理如下:

func Task() *ir.Name {
	// Step 1: 处理 import 语句,按顺序查找出所有依赖包中的初始化任务
	var deps []*obj.LSym
	for _, pkg := range typecheck.Target.Imports {
		n := typecheck.Resolve(ir.NewIdent(base.Pos, pkg.Lookup(".inittask")))
		deps = append(deps, n.(*ir.Name).Linksym())
	}

	// Step 2: 处理全局变量赋值语句,函数 initOrder 用来确定初始化顺序,并且返回需要在运行时初始化的赋值语句
	nf := initOrder(typecheck.Target.Decls)
	var fns []*obj.LSym
	if len(nf) > 0 {
		// 如果有需要动态执行的赋值语句,则创建一个 init 函数,并将该函数的函数体设置为所有的动态赋值语句
		// 该 init 函数在所有用户自定义的 init 函数之前
		initializers := typecheck.Lookup("init")
		fn := typecheck.DeclFunc(initializers, ir.NewFuncType(base.Pos, nil, nil, nil))
		fn.Body = nf
		typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
		fns = append(fns, fn.Linksym())
	}

	// Step 3: 处理用户申明的 init 函数,按照顺序添加到 fns 中
	for _, fn := range typecheck.Target.Inits {
		// 代码优化,去除函数中的无效代码,如果优化之后函数体为空,则忽略该 init 函数
		deadcode.Func(fn)

		if len(fn.Body) == 1 {
			if stmt := fn.Body[0]; stmt.Op() == ir.OBLOCK && len(stmt.(*ir.BlockStmt).List) == 0 {
				continue
			}
		}
		fns = append(fns, fn.Nname.Linksym())
	}

	// Step 4: 创建初始化任务 .inittask, 依次写入 deps 及 fns
	sym := typecheck.Lookup(".inittask")
	task := typecheck.NewName(sym)
	task.Class = ir.PEXTERN
	sym.Def = task
	lsym := task.Linksym()
	ot := 0
	ot = objw.Uintptr(lsym, ot, 0) // state: not initialized yet
	ot = objw.Uintptr(lsym, ot, uint64(len(deps)))
	ot = objw.Uintptr(lsym, ot, uint64(len(fns)))
	for _, d := range deps {
		ot = objw.SymPtr(lsym, ot, d, 0)
	}
	for _, f := range fns {
		ot = objw.SymPtr(lsym, ot, f, 0)
	}
	return task
}

Step 4 的代码细节不用太在意,重点是我们知道其最终创建了一个名叫.inittask的符号对象,并依次将前面三步的初始化内容写了进去。回顾代码结构, 编译器最终将.inittask设置为 Export 符号,并最终在编译的最后阶段将该符号对象写入对象文件(.o 或者 .a 文件)。所以在 Step 1 中可以通过该名字查找到其他依赖包中的初始化任务。

接下来,我们详细探讨一下 Step 2 中对全局变量的赋值语句的处理逻辑。

最后更新于