Go编译器定制简介

chai2010

自我介绍(chai2010)

2

主题大纲

3

主题大纲

4

最简编译器

5

编译器是什么

6

人肉编译器

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

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

int main() {
    return 42;
}

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

.intel_syntax noprefix
.globl _main

_main:
    mov rax, 42 ;
    ret

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

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

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 $?

8

挑战: 继续前进一小步

9

编译器后门八卦

10

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

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

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

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

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

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

更多的自重写代码

可能是最短的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 = `
14

Go和Rust的选择

15

Go 开发的编译器





16

Rust 开发的编译器



17

Go和Rust的选择



18

Go衍生的语言

19

Go衍生的语言


20

TinyGo的编译器架构

21

Go语法树

22

FileSet 文件集模型

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

全部的 Token 类型

24

表达式的语法树

1+2*3
25

语法解析器接口

parser.ParseDir, parser.ParseFile
26

方法声明的语法树结构

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

Go类型系统

28

Scope 词法域 - 01

builtin -> package -> file -> func
29

Scope 词法域 - 02

func -> args -> block
30

Scope 词法域 - 03

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

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
}
`
32

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
}
33

SSA和LLVM

34

SSA 基本结构

35

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)
}
36

Go AST 到 Go SSA - 02

const src = `
package main

var s = "hello ssa"

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

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
38

Go SSA 的执行和再编译

39

µGo案例

40

为什么是 Go ?



41

µ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)
        }
    }
}
42

µGo 架构

43

参考链接

44

参考链接


45

感谢 Ian 推荐序

46

感谢 Ian 推荐序

47

Q&A

48

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.)