跟着曹大学Golang第二回 – Go 语法背后的秘密

Coding Alan 3年前 (2022-01-23) 1761次浏览 0个评论 扫描二维码

安装多版本的 Go:https://golang.org/doc/manage-install

  • 编译原理基础
  • 编译与反编译⼯具
  • 使⽤调试⼯具
  • 语法实现分析
  • Parser 应⽤场景示例
  • 函数调⽤规约

编译原理基础

编译过程

跟着曹大学Golang第二回 - Go 语法背后的秘密

词法分析(Lexical Analysis)

跟着曹大学Golang第二回 - Go 语法背后的秘密

语法分析(Syntax Analysis)

跟着曹大学Golang第二回 - Go 语法背后的秘密

https://astexplorer.net/

语义分析(Semantic Analysis)

在抽象语法树 AST 上做类型检查

跟着曹大学Golang第二回 - Go 语法背后的秘密

中间代码(SSA)⽣成与优化

https://golang.design/gossa,或者GOSSAFUNC=funcname go build x.go

跟着曹大学Golang第二回 - Go 语法背后的秘密

SSA(Single Static Assignment)的两大要点是:

  • Static: 每个变量只能赋值一次(因此应该叫常量更合适);
  • Single: 每个表达式只能做一个简单运算,对于复杂的表达式a*b+c*d要拆分成: t0=a*b; t1=c*d; t2=t0+t1; 三个简单表达式;

机器码⽣成

编译的最后一个阶段是机器码:

跟着曹大学Golang第二回 - Go 语法背后的秘密

https://godbolt.org/

链接过程

最重要的就是进行虚拟地址重定位(Relocation)。编译后所有函数地址都是从 0 开始,每条指令是相对函数第一条指令的偏移。而链接后所有指令都有了全局唯一的地址:

跟着曹大学Golang第二回 - Go 语法背后的秘密

编译与反编译⼯具

编译

该命令会生成 .o目标文件,并把目标的汇编内容输出:

跟着曹大学Golang第二回 - Go 语法背后的秘密

例如要对比以下两段代码的执行效率,一种方法就是使用go tool compile来进行测试,本例会发现两者在框出部分输出的汇编是一致的:

跟着曹大学Golang第二回 - Go 语法背后的秘密

反编译

go tool objdump 查找 make 的实现 (https://go.dev/ref/spec,官⽅ spec),make.go 内容如下:

执行命令go build make.go && go tool objdump ./make | grep -E "make.go:6|make.go:10|make.go:14",得到的结果如下:

接下来可进一步查看runtime.makesliceruntime.makechan以及runtime.makemap来进行分析。

使⽤调试⼯具

https://github.com/go-delve/delve/tree/master/Documentation/cli

  • 调试汇编时 使用 si 到 JMP 目标位置
  • 使用 c(continue) 从一个断点到 下一个断点
  • disass 反汇编

语法实现分析

go func

通过go build hello.go && go tool objdump ./hello | grep "hello.go:6"命令可得到

可查看其中的 runtime 函数

channel send && recv

跟着曹大学Golang第二回 - Go 语法背后的秘密

关键点就是 chansend1 和 chanrecv1 函数

非阻塞 recv

使用go tool compile -S nonblock_recv.go | grep nonblock_recv.go:6得到

可进一步查看runtime.selectnbrecv

Parser 应⽤场景示例

内置 AST 工具-简单的规则引擎

假设有一个简单的需求,初中高级会员的规则如下(字段posts表示发帖数,invest表示充值消费):

  • 初级会员,发帖数 > 10
  • 中级会员,充值 > 1000 RMB
  • 高级会员,发帖数 > 100,充值 > 10000 RMB

跟着曹大学Golang第二回 - Go 语法背后的秘密

社区 Parser-SQL 审计

  • Vitess
  • PingCAP

SQL Parser 的例子:https://github.com/cch123/elasticsql

如何在交付二进制的同时使我们的 Go 模块具备一定的扩展性?

  • RPC,但存在性能问题
  • go-plugin,不同版本编译不兼容
  • REPL,需要编译原理知识或使用社区 Parser
  • WASM,Go 版本不完善

函数调⽤规约

函数栈

为什么 Go 可以一个函数多个返回值?

局部变量只要不逃逸,都在栈上分配空间,从低地址向高地址不断压入栈中

跟着曹大学Golang第二回 - Go 语法背后的秘密

在我们调用其它函数时,参数和返回值都 caller 提供调用空间的,因此可以有多个返回值:

跟着曹大学Golang第二回 - Go 语法背后的秘密

函数调用规约
• The order in which atomic (scalar) parameters, or individual parts of a complex parameter, are allocated
• How parameters are passed (pushed on the stack, placed in registers, or a mix of both)
• Which registers the called function must preserve for the caller (also known as: callee-saved registers or non-volatile registers)
• How the task of preparing the stack for, and restoring after, a function call is divided between the caller and the callee

参考资料

补充

编译原理相关书籍:

  • 入门
  • “龙书”、“虎书”,新手易劝退

 

喜欢 (1)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址