Go编译器定制简介

chai2010

自我介绍(chai2010)

2

我的图书(原创)

3

我的图书(翻译)

4

主题大纲

5

主题大纲

6

最简编译器

7

编译器是什么

8

人肉编译器

将一个整数编译为一个程序, 程序的返回值返回这个整数值

比如 42 翻译为以下的 C 语言程序:

int main() {
    return 42;
}

也可以翻译为以下的 X86 汇编程序:

.intel_syntax noprefix
.globl _main

_main:
    mov rax, 42 ;
    ret

也可以翻译为 LLVM 跨平台的汇编程序:

define i32 @main() {
    ret i32 42 ;
}
9

Go程序替代人肉编译器

Go语言重新实现如下人肉编译器:

func main() {
    code, _ := io.ReadAll(os.Stdin)
    compile(string(code))
}

func compile(code string) {
    output := fmt.Sprintf(tmpl, code)
    os.WriteFile("a.out.ll", []byte(output), 0666)
    exec.Command("clang", "-o", "a.out", "a.out.ll").Run()
}

const tmpl = `
define i32 @main() {
    ret i32 %v
}
`

编译 echo 123 | go run main.go, 执行 ./a.out, 看结果 echo $?

10

挑战: 继续前进一小步

11

编译器后门八卦

12

Reflections on Trusting Trust - 01 - 自重写程序

s 是从最后一个 0 到结尾的内容, 第1个 printf 打印 s 数组, 第2个打印后面部分
13

Reflections on Trusting Trust - 02 - 鸡和蛋问题

C编译器为了支持转义的自举过程
14

Reflections on Trusting Trust - 03 - 潜伏的后门

Ken 在C编译器植入后门后擦除记录, 同时在UNIX保留伪装的后门代码
15

更多的自重写代码

可能是最短的C自重写代码:

main(a){printf(a="main(a){printf(a=%c%s%c,34,a,34);}",34,a,34);}

rsc 给了Go的版本(Go已经自举, 大家可以放心用了):

package main

import "fmt"

func main() {
    fmt.Printf("%s%c%s%c\n", q, 0x60, q, 0x60)
}

var q = `package main

import "fmt"

func main() {
    fmt.Printf("%s%c%s%c\n", q, 0x60, q, 0x60)
}

var q = `
16

Go和Rust的选择

17

Go 开发的编译器





18

Rust 开发的编译器



19

Go和Rust的选择



20

Go衍生的语言

21

Go衍生的语言


22

TinyGo的编译器架构

23

Go语法树

24

FileSet 文件集模型

  1. 全部Go文件根据文件名排序
  2. 全部Go文件映射到连续的抽象内存, 每个文件对应一个区间
  3. token.Pos 表示一个指针, 0 对应 token.NoPos 空指针
  4. 这样设计的目的是为了压缩和性能, 通过一个 uint32 可以映射到文件位置
25

全部的 Token 类型

26

表达式的语法树

1+2*3
27

语法解析器接口

parser.ParseDir, parser.ParseFile
28

方法声明的语法树结构

func (p *xType) Hello(arg1, arg2 int) (bool, error)
29

Go类型系统

30

Scope 词法域 - 01

builtin -> package -> file -> func
31

Scope 词法域 - 02

func -> args -> block
32

Scope 词法域 - 03

特殊的地方: Pkg 内 File 之间的命名实体可见(但不包含 import)
33

Go类型检查

func main() {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
    if err != nil {
        log.Fatal(err)
    }
    pkg, err := new(types.Config).Check("hello.go", fset, []*ast.File{f}, nil)
    if err != nil {
        log.Fatal(err)
    }
    _ = pkg
}

const src = `package main
func main() {
    var _ = "a" + 1
}
`
34

Go类型信息

func main() {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
    if err != nil {
        log.Fatal(err)
    }
    info := &types.Info{
        Types:  make(map[ast.Expr]types.TypeAndValue),
        Defs:   make(map[*ast.Ident]types.Object),
        Uses:   make(map[*ast.Ident]types.Object),
        Scopes: make(map[ast.Node]*types.Scope),
    }
    conf := types.Config{Importer: nil}
    pkg, err := conf.Check("hello.go", fset, []*ast.File{f}, info)
    if err != nil {
        log.Fatal(err)
    }
    _ = pkg
}
35

SSA和LLVM

36

SSA 基本结构

37

Go AST 到 Go SSA - 01

func main() {
    fset := token.NewFileSet()
    f, _ := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)

    info := &types.Info{}
    conf := types.Config{Importer: nil}
    pkg, _ := conf.Check("hello.go", fset, []*ast.File{f}, info)

    var ssaProg = ssa.NewProgram(fset, ssa.SanityCheckFunctions)
    var ssaPkg = ssaProg.CreatePackage(pkg, []*ast.File{f}, info, true)

    ssaPkg.Build()

    ssaPkg.WriteTo(os.Stdout)
    ssaPkg.Func("init").WriteTo(os.Stdout)
    ssaPkg.Func("main").WriteTo(os.Stdout)
}
38

Go AST 到 Go SSA - 02

const src = `
package main

var s = "hello ssa"

func main() {
    for i := 0; i < 3; i++ {
        println(s)
    }
}
`
39

Go AST 到 Go SSA - 03

package hello.go:
  func  init       func()
  var   init$guard bool
  func  main       func()
# Name: hello.go.main
# Package: hello.go
# Location: hello.go:4:6
func main():
0:                                                                entry P:0 S:1
        jump 3
1:                                                             for.body P:1 S:1
        t0 = println("hello ssa -- chai...":string)                          ()
        t1 = t2 + 1:int                                                     int
        jump 3
2:                                                             for.done P:1 S:0
        return
3:                                                             for.loop P:2 S:2
        t2 = phi [0: 0:int, 1: t1] #i                                       int
        t3 = t2 < 3:int                                                    bool
        if t3 goto 1 else 2
40

Go SSA 的执行和再编译

41

µGo案例

42

为什么是 Go ?



43

µGo 例子

µGo 是 Go 语言的子集(不含标准库部分), 可以直接作为Go代码编译执行.

func main() {
    for n := 2; n <= 30; n = n + 1 {
        var isPrime int = 1
        for i := 2; i*i <= n; i = i + 1 {
            if x := n % i; x == 0 {
                isPrime = 0
            }
        }
        if isPrime != 0 {
            println(n)
        }
    }
}
44

µGo 架构

45

参考链接

46

参考链接


47

感谢 Ian 推荐序

48

感谢 Ian 推荐序

49

感谢 许大 (Go 和 Go+ 的全部工作)

50

Q&A

51

Thank you

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)