构建Golang编译器的漫长步骤
构建Golang编译器的漫长步骤 从源代码构建Go编译器二进制文件时,我得到了以下输出:
Building Go cmd/dist using /home/jauhararifin/.gvm/gos/go1.21. (go1.21.0 linux/amd64)
Building Go toolchain1 using /home/jauhararifin/.gvm/gos/go1.21.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.
看起来,仅仅为了构建一个单一的 go 二进制文件,就有一个非常漫长的步骤。首先,它使用旧版本的Go构建了一个叫做 dist 的二进制文件。然后,它使用旧版本的Go构建了 toolchain1,并用它来构建 go,接着用这个 go 重新构建自身,然后再一次重新构建自身。最后,它又被用来再次重新构建自身。
我不确定它是不是这样做的,但有人知道为什么会有这么复杂的过程吗?为什么不简单地使用旧版本的Go来构建当前版本的Go呢?
我想通过在源代码中添加一堆打印语句来学习Go编译器,但重新构建它太慢了。我想知道原因,以及我能做些什么。
更多关于构建Golang编译器的漫长步骤的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于构建Golang编译器的漫长步骤的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Go编译器构建过程之所以如此复杂,是因为它采用了自举(bootstrapping)和渐进式构建策略,以确保编译器的可靠性和可重复性。这个过程被称为“编译器自举”,是许多编程语言编译器的标准做法。
为什么需要多阶段构建?
-
自举依赖:Go编译器是用Go语言自身编写的。要构建新版本的Go,你需要一个可用的Go编译器。这个过程通过使用旧版本Go(如go1.21)作为起点,逐步构建更可靠的新版本来解决。
-
工具链验证:多阶段构建确保编译器能够正确编译自身,这是编译器正确性的重要验证。如果编译器能够成功编译自身,说明它足够稳定。
-
消除旧工具链依赖:最终生成的Go编译器不依赖于旧版本Go,而是完全自包含的。
构建阶段详解
// 阶段1:使用系统Go(go1.21)构建dist工具
// dist是Go构建系统的核心协调器
Building Go cmd/dist using /home/jauhararifin/.gvm/gos/go1.21
// 阶段2:使用系统Go构建第一代工具链
Building Go toolchain1 using /home/jauhararifin/.gvm/gos/go1.21
// 阶段3:使用toolchain1构建引导版go(go_bootstrap)
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1
// 阶段4-5:逐步改进工具链
Building Go toolchain2 using go_bootstrap and Go toolchain1
Building Go toolchain3 using go_bootstrap and Go toolchain2
加速开发构建的方法
如果你只是想学习编译器代码,不需要完整的多阶段构建,可以使用开发构建:
# 1. 使用现有Go工具链直接构建(跳过自举)
cd src
./make.bash # 或 ./make.bash --no-clean
# 2. 只构建特定组件
cd src/cmd/compile
go build -o compiler # 直接构建编译器
# 3. 使用增量构建
cd src
./make.bash --no-clean # 保留中间文件,加速后续构建
# 4. 针对特定架构(减少构建目标)
GOOS=linux GOARCH=amd64 ./make.bash
调试和学习编译器的建议
// 在编译器代码中添加调试输出
// 文件:src/cmd/compile/internal/gc/main.go
func Main() {
// 添加调试信息
fmt.Fprintf(os.Stderr, "=== 开始编译 ===\n")
fmt.Fprintf(os.Stderr, "编译文件: %v\n", flag.Args())
// 原有的编译器逻辑...
// 在关键路径添加更多调试信息
if debug {
dumpAST() // 自定义的AST导出函数
}
}
// 快速测试修改
func TestQuickBuild() {
// 构建最小化的测试编译器
cmd := exec.Command("go", "build", "-o", "test_compiler", "./cmd/compile")
cmd.Dir = "/path/to/go/src"
cmd.Run()
}
使用现有构建进行实验
# 使用现有Go安装来构建和测试编译器修改
export GOROOT_BOOTSTRAP=/home/jauhararifin/.gvm/gos/go1.21
cd /path/to/go/src
# 快速构建(可能不完整但更快)
./all.bash # 完整测试套件
# 或
./make.bash # 仅构建,不运行测试
# 只构建编译器进行测试
cd cmd/compile
go build -gcflags="-e" # 构建并允许所有错误(用于调试)
构建过程的复杂性确实会影响开发体验,但这是确保Go编译器质量和自包含性的必要设计。对于学习目的,使用增量构建和针对性构建可以显著减少等待时间。

