Skip to content

Commit

Permalink
Merge branch 'main' of github.com:floatshadow/accipit
Browse files Browse the repository at this point in the history
  • Loading branch information
chiakicage committed Mar 4, 2024
2 parents c266596 + 729aa39 commit 4b401cc
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 143 deletions.
40 changes: 20 additions & 20 deletions docs/appendix/accipit-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ lit ::= <int_lit> | <none_lit> | <unit_lit>

除了可以用上述具名或匿名的标识符来引用某个值,Accipit IR 还有常数值。

`int_lit` 定义了 64 位有符号整数字面量,我们只考虑普通的十进制整数的文本形式。
`int_lit` 定义了 32 位有符号整数字面量,我们只考虑普通的十进制整数的文本形式。

`none_lit` 是两个特殊的符号,用于 offset 指令。

Expand All @@ -87,23 +87,23 @@ value ::= <symbol> | lit<>
Accipit IR 中的众多实体按类型区分,类型关乎到程序的合法性、执行的过程。

```
type ::= 'i64'
type ::= 'i32'
| '()'
| <type> '*'
| 'fn' '(' separated_list(<type>, ',') ')' '->' <type>
```

i64,64 位带符号整数。
i32,32 位带符号整数。

单值类型 (),读作 unit,可以理解为空类型 void。

指针类型,由被指的类型 (pointee type) 加上后缀 * 表示。

函数类型,类似于函数声明,例如:
- 加法 add,两个 i64 参数,一个 i64 返回值 `fn(i64, i64) -> i64`
- 读入,无参数,一个 i64 返回值 `fn() -> i64`
- 输出,一个 i64 参数,无返回值 `fn(i64) -> ()`
- `fn(i64*) -> i64*`,接受一个 i64* 参数,返回一个 i64* 类型的返回值。
- 加法 add,两个 i32 参数,一个 i32 返回值 `fn(i32, i32) -> i32`
- 读入,无参数,一个 i32 返回值 `fn() -> i32`
- 输出,一个 i32 参数,无返回值 `fn(i32) -> ()`
- `fn(i32*) -> i32*`,接受一个 i32* 参数,返回一个 i32* 类型的返回值。

### Instructions

Expand Down Expand Up @@ -136,15 +136,15 @@ binexpr ::= <binop> <value> ',' <value>

数值计算指令操作符中不包含单目运算符,例如 lnot (logic not) 和 neg (numeric negation),因为他们是多余的:

- 按位取反,`not %src` 等价于 `xor %src: i64, -1`.
- 按位取反,`not %src` 等价于 `xor %src: i32, -1`.
- 逻辑取反,`lnot %src` 等价于 `eq %src, 0`.
- 取负数,`neg %src` 等价于 `sub 0, %src`.

这种转换很容易在前端生成中间代码时实现,并且使得这些计算有一个统一的表示形式 (canonical form),这将有利于后端代码生成.

##### 类型规则

接受两个 i64 类型操作数,返回一个 i64 类型的值.
接受两个 i32 类型操作数,返回一个 i32 类型的值.


#### Pointer Instructions
Expand All @@ -164,11 +164,11 @@ offset 指令有一个类型标注,用来表明数组中元素类型;
一共有 `2n + 1` 个参数,其中第一个参数是一个指针,表示基地址;
`2n` 个参数每两个一组, 每一组的形式为 `[index < size]` 其中 index 表示该维度上的偏移量,size 表示该维度的大小.

例如 C 语言中声明数组 `int g[3][2][5]`,访问元素 `g[x][y][z]` 时,对应的 offset 指令为 `offset i64, %g.addr: i64*, [x < 3], [y < 2], [z < 5]`
例如 C 语言中声明数组 `int g[3][2][5]`,访问元素 `g[x][y][z]` 时,对应的 offset 指令为 `offset i32, %g.addr: i32*, [x < 3], [y < 2], [z < 5]`

当然,可能会出现高位数组有一维不知道大小或者单个指针偏移的情况,在这种情况下,对应的维度使用 none 标记:
- 二维数组 `int g[][5]` 访问 `g[x][y]``offset i64, %g.addr: i64*, [x < none], [y < 5]`.
- 单个指针 `int *p` 访问 `p + 10``offset i64, %g.addr: i64*, [10 < none]`.
- 二维数组 `int g[][5]` 访问 `g[x][y]``offset i32, %g.addr: i32*, [x < none], [y < 5]`.
- 单个指针 `int *p` 访问 `p + 10``offset i32, %g.addr: i32*, [10 < none]`.

为什么要有 size 这个参数作为一个下标的上界?
为了你方便处理,我们在类型中舍弃了高维数组,因为数组类型在后端代码生成时处理相对比较麻烦,但是在前端处理这些信息相对容易。
Expand Down Expand Up @@ -212,7 +212,7 @@ store ::= 'store' <value> ',' <symbol>

alloca 指令的作用是为局部变量开辟栈空间,并获得一个指向 `<type>` 类型,长度为 `<int_lit>` 的指针。
可以理解为,在栈上定义一个数组 `<type>[<int_lit>]`,并获取数组首元素的地址。
或者类比 C 代码 `int *a = (int *)malloc(100 * sizeof(int))`, 对应 `let %a: i64* = alloc i64, 100`.
或者类比 C 代码 `int *a = (int *)malloc(100 * sizeof(int))`, 对应 `let %a: i32* = alloc i32, 100`.

load 指令接受一个指针类型 T* 的符号,返回一个 T 类型的值.

Expand All @@ -228,7 +228,7 @@ ret ::= 'ret' <value>

##### 说明

br 进行条件跳转,接受的 `<value>` 应当是 i64 类型.
br 进行条件跳转,接受的 `<value>` 应当是 i32 类型.
若为 true,跳转到第一个 `<label>` 标记的基本块起始处执行;
若为 false,跳转到第二个 `<label>` 标记的基本块起始处执行.

Expand All @@ -247,7 +247,7 @@ fun ::= 'fn' <ident> '(' <plist> ')' '->' <type> {';' | <body>}
```

函数包含函数头以及可选的函数体。
如果没有函数体,则以一个分号 `;` 结尾,例如 `fn getchar() -> i64;`
如果没有函数体,则以一个分号 `;` 结尾,例如 `fn getchar() -> i32;`

函数头以关键字 `fn` 开头,包含函数命令和参数列表和返回值,参数列表必须显式地标出每个参数的类型。

Expand All @@ -261,13 +261,13 @@ bb ::= <label> <instr>* <terminator>

下面是一个阶乘函数的例子:
```
fn %factorial(%n: i64) -> i64 {
fn %factorial(%n: i32) -> i32 {
%Lentry:
/* Create a stack slot of i64 type as the space of the return value.
/* Create a stack slot of i32 type as the space of the return value.
* if n equals 1, store `1` to this address, i.e. `return 1`,
* otherwise, do recursive call, i.e. return n * factorial(n - 1).
*/
let %ret.addr = alloca i64, 1
let %ret.addr = alloca i32, 1
let %cmp = eq %n, 0
br %cmp, label %Ltrue, label %Lfalse
%Ltrue:
Expand Down Expand Up @@ -302,10 +302,10 @@ global ::= <symbol> ':' 'region' <type> <int_lit>
例如:

```
%a : region i64, 2
%a : region i32, 2
```

`%a``i64*` 类型,所指向的地址能存放 2 个 i64
`%a``i32*` 类型,所指向的地址能存放 2 个 i32

## Execution

Expand Down
38 changes: 19 additions & 19 deletions docs/appendix/sysy-accipit-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ let %result = add %lhs, %rhs
取这个名字,是因为他们不像顶层变量有一个新的符号,他们只有局部变量所对应的地址,只能通过 `alloca` 指令创建:

```rust
let %result.var.addr = alloca i64, 1
let %lhs.var.addr = alloca i64, 1
let %rhs.var.addr = alloca i64, 1
let %result.var.addr = alloca i32, 1
let %lhs.var.addr = alloca i32, 1
let %rhs.var.addr = alloca i32, 1
```

在这里我们通过 `alloca` 创建三个局部变量,由于这三个局部变量都是 `i64` 类型,因此得到的结果 `%result.var.addr` `%lhs.var.addr``%rhs.var.addr` 这三个虚拟寄存器的值都是 `i64*` 类型,代表这三个局部变量的地址。
在这里我们通过 `alloca` 创建三个局部变量,由于这三个局部变量都是 `i32` 类型,因此得到的结果 `%result.var.addr` `%lhs.var.addr``%rhs.var.addr` 这三个虚拟寄存器的值都是 `i32*` 类型,代表这三个局部变量的地址。
我们并不知道这三个局部变量叫什么名字,也不关心这三个局部变量叫什么名字,我们只需要知道这三个局部变量的地址是什么:`%result.var.addr` `%lhs.var.addr``%rhs.var.addr`
通过这些地址,我们就能够对这些局部变量读写:

Expand Down Expand Up @@ -75,9 +75,9 @@ result = result + 1;
首先为局部变量分配栈空间:

```rust
let %lhs.addr = alloca i64, 1
let %rhs.addr = alloca i64, 1
let %result.addr = alloca i64, 1
let %lhs.addr = alloca i32, 1
let %rhs.addr = alloca i32, 1
let %result.addr = alloca i32, 1
```

然后使用 `store` 指令完成这些局部变量的初始化:
Expand Down Expand Up @@ -133,9 +133,9 @@ let %9 = store %8, %result.addr

```rust
// allocate
let %lhs.addr = alloca i64, 1
let %rhs.addr = alloca i64, 1
let %result.addr = alloca i64, 1
let %lhs.addr = alloca i32, 1
let %rhs.addr = alloca i32, 1
let %result.addr = alloca i32, 1
// initialize
let %0 = store 1, %lhs.addr
let %1 = store 2, %rhs.addr
Expand All @@ -156,9 +156,9 @@ let %9 = store %8, %result.addr

```rust
// allocate
let %0 = alloca i64, 1
let %1 = alloca i64, 1
let %2 = alloca i64, 1
let %0 = alloca i32, 1
let %1 = alloca i32, 1
let %2 = alloca i32, 1
// initialize
let %3 = store 1, %0
let %4 = store 2, %1
Expand Down Expand Up @@ -206,13 +206,13 @@ int bar(int value);
变成:
```rust
fn %bar(%value: i64) -> i64;
fn %bar(%value: i32) -> i32;
```

由于没有函数体,函数参数的名字无关紧要,因此你也可以略去参数名,只保留参数类型:

```rust
fn %bar(i64) -> i64;
fn %bar(i32) -> i32;
```

由于 SysY 的运行时库无需声明即可使用,你可以选择在读入的 SysY 源代码前先“拼接”上运行时库函数的声明,让你的编译器前端帮你完成从运行时库函数声明到 Accipit IR 函数声明的过程;
Expand Down Expand Up @@ -253,13 +253,13 @@ jmp label %dest
```

```rust
fn max(%a: i64, %b: i64) -> i64 {
fn max(%a: i32, %b: i32) -> i32 {
%entry:
let %a.addr = alloca i64, 1
let %b.addr = alloca i64, 1
let %a.addr = alloca i32, 1
let %b.addr = alloca i32, 1
let %0 = store %a, %a.addr
let %1 = store %b, %b.addr
let %retval = alloca i64, 1
let %retval = alloca i32, 1
let %2 = load %a.addr
let %3 = load %b.addr
let %4 = gt %2, %3
Expand Down
33 changes: 27 additions & 6 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
我们推荐你使用 Linux 系统完成实验. Windows 系统用户可以考虑 [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) 或者虚拟机. Mac 用户努力自行解决吧.

为了避免不必要的问题,推荐使用 Ubuntu 22.04 LTS 或者 Debian 12 (助教的环境). 当然其他发行版/系统也是可以的, 只要你能正常安装和使用相关工具.
以下的工具版本均为 Debian 12 上的版本 (其他版本也可以).
以下的工具版本均为 Debian 12 上的版本 (原则上其他版本也可以工作).

1. 基础编译器
+ gcc 10.2.1
Expand All @@ -23,11 +23,32 @@

### Bison 和 Flex 安装
`bison``flex` 是 Lab 1 要用到的工具. 你可以用 `accipit/examples/toy-calculator` 下的文件来简单测试.
里面使用 flex 和 bison 编写了一个 C API 的计算器,并使用 gcc 编译:

当然本实验并不限制你用什么语言和工具来实现 lexer/parser. 你可以用 Rust, Python, Java, C++ 甚至 OCaml 等语言, 也可以不用 parser generator 而是用 parser generator (Accipit IR 的 parser 就是这样写的) 甚至手写递归下降 parser.
```bash
examples/toy-calculator $ make
examples/toy-calculator $ ./calculator < input.txt
```

你能看到输出形如:

```
ans = -4.000000
ans = 3.000000
ans = 12.000000
ans = 18.000000
ans = 120.000000
ans = 1.666667
ans = 5.400000
ans = 1.000000
ans = 1.414214
```

当然本实验并不限制你用什么语言和工具来实现 lexer/parser. 你可以用 Rust, Python, Java, C++ 甚至 OCaml 等语言, 也可以不用 parser generator 而是用 parser combinator (Accipit IR 的 parser 就是这样写的) 甚至手写递归下降 parser.

### Rust 安装
由于 Accipit IR 相关工具是用 Rust 编写的, 所以需要安装 Rust (你也可以等到 Lab 3 的时候安装). 我们推荐使用[浙大源](https://mirrors.zju.edu.cn/docs/rustup/)安装 [rustup](https://rustup.rs/).
### Rust 安装(可选)
由于 Accipit IR 相关工具是用 Rust 编写的, 所以需要安装 Rust (你也可以等到 Lab 3 的时候安装) 从源码编译相关工具,或者使用助教帮你编译好的 binary。
我们推荐使用[浙大源](https://mirrors.zju.edu.cn/docs/rustup/)安装 [rustup](https://rustup.rs/).
```bash
export RUSTUP_DIST_SERVER=https://mirrors.zju.edu.cn/rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Expand Down Expand Up @@ -71,7 +92,7 @@ Timer: 0H-0M-12S-124900us
TOTAL: 0H-0M-12S-124900us
```

> 如果你对编译选项感兴趣, 这里是一个简单的解释: `NO_LIBC=1` 表示不使用系统的 C 标准库 (也就是 `libc`), `ADD_CFLAGS="-target riscv64-unknown-linux-elf -march=rv64im -mabi=lp64"` 表示添加交叉编译选项. 这里 `riscv64-unknown-linux-elf` 是交叉编译器的 target triple, `rv64im` 是 riscv 的指令集, `lp64` 是 riscv 的 ABI. 注意在编译你的程序的时候也许还需要添加一个选项 `-fuse-ld=lld` 来使用 `lld` 作为 linker, 否则默认的 `/usr/bin/ld` 是无法交叉编译的, 这点已经在 Makefile 中处理了.
> 如果你对编译选项感兴趣, 这里是一个简单的解释: `NO_LIBC=1` 表示不使用系统的 C 标准库 (也就是 `libc`), `ADD_CFLAGS="-target riscv64-unknown-linux-elf -march=rv64im -mabi=lp64"` 表示添加交叉编译选项. 这里 `riscv64-unknown-linux-elf` 是交叉编译器的 target triple, `rv64im` 是 riscv 的指令集, `lp64` 是 riscv 的 ABI. 注意在编译你的程序的时候也许还需要添加一个选项 `-fuse-ld=lld` 来使用 `lld` 作为 linker, 否则默认的 `/usr/bin/ld` 只能处理主机 x86_64 或者 arm 指令集的 elf 文件,无法链接交叉编译的产物, 这点已经在 Makefile 中处理了.
<br><br>
关于为什么不用 `libc`, `qemu-user-static` 是一个用户态的 riscv 虚拟机, 就像你在 OS 实验中用 `qemu-system-riscv64` 运行的系统一样, 它是不带标准库的. 如果你想输入输出, 你需要手动调用 `syscall`. 这个工作在之前是由 `libc` 来帮你完成的, 你只需要调用它提供的库函数 (比如 `printf`), 我们提供的 runtime 也是这样实现的.
<br><br>
Expand All @@ -85,4 +106,4 @@ TOTAL: 0H-0M-12S-124900us


## 实验提交
没有提交内容. Just have fun.
没有提交内容. Just have fun.
34 changes: 17 additions & 17 deletions docs/middle-ir-gen.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ int factorial(int n) {
参考中间代码:
```rust
fn %factorial(#n: i64) -> i64 {
fn %factorial(#n: i32) -> i32 {
%Lentry:
// create a stack slot of i64 type as the space of the return value.
// create a stack slot of i32 type as the space of the return value.
// if n equals 1, store `1` to this address, i.e. `return 1`,
// otherwise, do recursive call, i.e. return n * factorial(n - 1).
let %ret.addr: i64* = alloca i64, 1
let %ret.addr: i32* = alloca i32, 1
// store function parameter on the stack.
let %n.addr: i64* = alloca i64, 1
let %n.addr: i32* = alloca i32, 1
let %4: () = store #n, %n.addr
// create a slot for local variable ans, uninitialized.
let %ans.addr: i64* = alloca i64, 1
let %ans.addr: i32* = alloca i32, 1
// when we need #n, you just read it from %n.addr.
let %6: i64 = load %n.addr
let %6: i32 = load %n.addr
// comparison produce an `i8` value.
let %cmp: i1 = eq %6, 0
br i1 %cmp, label %Ltrue, label %Lfalse
Expand All @@ -68,26 +68,26 @@ fn %factorial(#n: i64) -> i64 {
jmp label %Lret
%Lfalse:
// n - 1
let %13: i64 = load %n.addr
let %14: i64 = sub %13, 1
let %13: i32 = load %n.addr
let %14: i32 = sub %13, 1
// factorial(n - 1)
let %res: i64 = call fn %factorial, %14
let %res: i32 = call fn %factorial, %14
// n
let %16 = load %n.addr
// n * factorial(n - 1)
let %17: i64 = mul %16, %res
let %17: i32 = mul %16, %res
// write local variable `ans`
let %18: () = store %17, %ans.addr
// now we meets `return ans`, which means we
// should first read value from `%ans.addr` and then
// write it to `%ret.addr`.
let %19: i64 = load %ans.addr
let %19: i32 = load %ans.addr
let %20: () = store %19, %ret.addr
jmp label %Lret
%Lret:
// load return value from %ret.addr
let %ret.val: i64 = load %ret.addr: i64*
ret %ret.val: i64
let %ret.val: i32 = load %ret.addr: i32*
ret %ret.val: i32
}
```

Expand Down Expand Up @@ -140,7 +140,7 @@ $ dot -Tpng -o test.png .test.dot

```c
enum value_kind {
kind_constant_int64,
kind_constant_int32,
Kind_constant_unit,
// ...
kind_constant_binary_expression,
Expand All @@ -154,7 +154,7 @@ $ dot -Tpng -o test.png .test.dot
enum value_kind kind,
struct type *ty;
union {
struct { int number; } constant_int64;
struct { int number; } constant_int32;
struct { enum binary_op op, struct value *lhs, *rhs; } binary_expr;
struct { struct function *callee, struct vector args } function_call;
struct { struct value* src_addr } load;
Expand Down Expand Up @@ -253,7 +253,7 @@ let %2 = add %0, %1
<tr class="odd">
<td><code>INT</code></td>
<td><div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>number <span class="op">=</span> get_number<span class="op">(</span>INT<span class="op">);</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> create_constant_int64<span class="op">(</span>number<span class="op">);</span></span></code></pre></div></td>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> create_constant_int32<span class="op">(</span>number<span class="op">);</span></span></code></pre></div></td>
</tr>
<tr class="even">
<td><code>ID</code></td>
Expand All @@ -269,7 +269,7 @@ let %2 = add %0, %1
</tr>
<tr class="even">
<td><code>MINUS Expr1</code></td>
<td><div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>zero_value <span class="op">=</span> create_constant_int64<span class="op">(</span><span class="dv">0</span><span class="op">);</span></span>
<td><div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>zero_value <span class="op">=</span> create_constant_int32<span class="op">(</span><span class="dv">0</span><span class="op">);</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>expr1_value <span class="op">=</span> translate_expr<span class="op">(</span>Expr1<span class="op">,</span> sym_table<span class="op">);</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> create_binary<span class="op">(</span>subop<span class="op">,</span> zero_value<span class="op">,</span> expr1_value<span class="op">,</span> basic_block<span class="op">);</span></span></code></pre></div></td>
</tr>
Expand Down
Loading

0 comments on commit 4b401cc

Please sign in to comment.