共计 2679 个字符,预计需要花费 7 分钟才能阅读完成。
1.1 调试源代码
1.1.1 编译源码
-
下载 Go1.4 并装置到
~/go1.4
门路下(Go 从 1.5 之版本开始实现自举,编译须要用到 1.4 版本。~/go1.4
是编译时默认的读取门路,能够通过设置环境变量$GOROOT_BOOTSTRAP
来指定):官网:Downloads – The Go Programming Language
国内镜像:Go 下载 – Go 语言中文网 – Golang 中文社区
-
批改源码,比方在
fmt/print.go
的Println
函数打印时多加一句:/usr/local/go/src/fmt/print.go:
... 273 func Println(a ...interface{}) (n int, err error) {274 println("hisoka") 275 return Fprintln(os.Stdout, a...) 276 } ...
-
切换到 go 的装置门路里的 src 目录(不可省略,否则报错),运行
make.bash
脚本:% cd /usr/local/Cellar/go/1.17.5/libexec/src % sudo ./make.bash Building Go cmd/dist using /Users/hisoka/go1.4. (go1.14 darwin/amd64) Building Go toolchain1 using /Users/hisoka/go1.4. 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 darwin/amd64. --- Installed Go for darwin/amd64 in /usr/local/Cellar/go/1.17.5/libexec Installed commands in /usr/local/Cellar/go/1.17.5/libexec/bin
-
写个 Hello World 程序验证下成果:
% go run hello.go hisoka Hello World!
1.1.2 中间代码
-
获取汇编代码:
% go build -gcflags -S hello.go # command-line-arguments "".main STEXT size=103 args=0x0 locals=0x40 funcid=0x0 0x0000 00000 (/Users/hisoka/Downloads/hello.go:5) TEXT "".main(SB), ABIInternal, $64-0 ...
-
获取编译指令优化过程:
% GOSSAFUNC=main go build hello.go # runtime dumped SSA to /Users/hisoka/Downloads/ssa.html # command-line-arguments dumped SSA to ./ssa.html
2.1 编译过程
2.1.1 准备常识
- 形象语法树(Abstract Syntax Tree、AST),是源代码语法的构造的一种形象示意,它用树状的形式示意编程语言的语法结构。
- 动态单赋值(Static Single Assignment、SSA)是中间代码的个性,如果中间代码具备动态单赋值的个性,那么每个变量就只会被赋值一次。
- 指令集。
2.1.2 编译原理
-
词法与语法分析:
所有的编译过程其实都是从解析代码的源文件开始的,词法剖析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,不便前面的解决和解析,咱们个别会把执行词法剖析的程序称为词法解析器(lexer)。
而语法分析的输出是词法分析器输入的 Token 序列,语法分析器会依照程序解析 Token 序列,该过程会将词法剖析生成的 Token 依照编程语言定义好的文法(Grammar)自下而上或者自上而下的规约,每一个 Go 的源代码文件最终会被演绎成一个 SourceFile 构造。
-
类型查看:
当拿到一组文件的形象语法树之后,Go 语言的编译器会对语法树中定义和应用的类型进行查看,类型查看会依照以下的程序别离验证和解决不同类型的节点:
- 常量、类型和函数名及类型;
- 变量的赋值和初始化;
- 函数和闭包的主体;
- 哈希键值对的类型;
- 导入函数体;
- 内部的申明;
-
两头代码生成:
当咱们将源文件转换成了形象语法树、对整棵树的语法进行解析并进行类型查看之后,就能够认为以后文件中的代码不存在语法错误和类型谬误的问题了,Go 语言的编译器就会将输出的形象语法树转换成中间代码。
-
机器码生成:
Go 语言源代码的
src/cmd/compile/internal
目录中蕴含了很多机器码生成相干的包,不同类型的 CPU 别离应用了不同的包生成机器码,其中包含 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,其中比拟乏味的就是 WebAssembly(Wasm)了。
2.1.3 编译器入口
Go 语言的编译器入口在 src/cmd/compile/internal/gc/main.go
文件中,其中 600 多行的 cmd/compile/internal/gc.Main
就是 Go 语言编译器的主程序。
2.2 词法与语法分析
略
2.3 类型查看
2.3.1 强弱类型
-
个别论断:
- 强类型的编程语言在编译期间会有更严格的类型限度,也就是编译器会在编译期间发现变量赋值、返回值和函数调用时的类型谬误;
- 弱类型的编程语言在呈现类型谬误时可能会在运行时进行隐式的类型转换,在类型转换时可能会造成运行谬误。
-
开发者更值得关注的问题:
- 类型的转换是显式的还是隐式的?
- 编译器会帮忙咱们推断变量的类型么?
2.3.2 动态类型与动静类型
-
动态类型查看:
动态类型查看是基于对源代码的剖析来确定运行程序类型平安的过程,如果咱们的代码可能通过动态类型查看,那么以后程序在肯定水平上能够满足类型平安的要求,它可能缩小程序在运行时的类型查看,也能够被看作是一种代码优化的形式。
-
动静类型查看:
动静类型查看是在运行时确定程序类型平安的过程,它须要编程语言在编译时为所有的对象退出类型标签等信息,运行时能够应用这些存储的类型信息来实现动静派发、向下转型、反射以及其余个性。动静类型查看能为工程师提供更多的操作空间,让咱们能在运行时获取一些类型相干的上下文并依据对象的类型实现一些动静操作。
2.4 两头代码生成 ~ 2.5 机器码生成
略