# RVBTCC - 约 1900 行的轻量级自举编译器。 - 编译器和自举编译器行为一致。 - 语法类似 C,输出 RISC-V 汇编。 - 依赖几个 libc 函数用于输入输出。 - 不使用动态内存分配,嵌入式友好。 ## 用法 如果你有 RISC-V 真机,可以采用真机运行,否则可以考虑模拟运行。两者行为应当是一致的。 ### 真机运行 编译运行程序,src 为本语言源代码。可以编译 demo 文件夹下的实例。 ```sh $ sh run-native.sh ``` 自举编译器,输出的文件位于 build 文件夹中。 ```sh $ sh boot-native.sh ``` ### 模拟运行 安装以下依赖 ```sh sudo apt install gcc-12-riscv64-linux-gnu qemu-user qemu-system-misc ``` 编译运行程序,src 为本语言源代码。可以编译 demo 文件夹下的实例。 ```sh $ sh run.sh ``` 自举编译器,输出的文件位于 build 文件夹中。 ```sh $ sh boot.sh ``` ### 自举过程 自举会输出六个文件,三个汇编文件和三个可执行文件: | 源代码 | 编译器 | 汇编 | 可执行 | 代号 | 命名 | | ------ | --------- | ------- | --------- | ---- | ---------------------- | | boot.c | gcc | | gcc.out | G | 自制编译器 | | boot.c | gcc.out | boot1.s | boot1.out | B1 | 自举自制编译器 | | boot.c | boot1.out | boot2.s | boot2.out | B2 | 自举自举自制编译器 | | boot.c | boot2.out | boot3.s | | B3 | 验证自举自举自制编译器 | 除了第一次编译全程由 gcc 完成之外,另外三次编译从源码到汇编由本编译器完成,从汇编到可执行文件由 gcc 完成。从汇编到可执行文件时需要将 glibc 链接进去,这对于 gcc 来说是默认的行为。 整个自举及其验证的过程如下图所示: ![](bootstrapping.png) 自举的目标为 G、B1、B2 的可执行文件行为一致,也就是说 B1、B2、B3 的汇编代码一致。 ## 语言文档 ### 注释 支持多行 `/* ... */` 和单行 `//` 两种注释 ### 支持六个基本类型 | 标量类型 | 指针类型 | | -------- | -------- | | `void` | `void*` | | `char` | `char*` | | `int` | `int*` | - 注意指针类型不是复合得来的,而是被视作整体。因此也不存在二重指针。 - 函数和数组不是类型系统的一部分。 - 可以认为数组的类型就是其元素对应的指针类型。 - 函数的参数类型和个数不会检查,返回值会参与类型检查。 - 函数名只能被用于调用,函数调用被视为初等表达式。 - 数组只支持一维数组,且数组的元素不能是指针类型。 - 整数和字符字面量的类型是 `int`,字符串字面量的类型是 `char*` ### 支持的流程控制 - `if` `else` - `while` `for` `do` - `break` `continue` - `return` ### 关键字 本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const` 和 `extern`。 #### `const` 关键字 `const` 关键字可以在类型中使用,在大部分情况下会被直接忽略。支持它是为了更好兼容 C 程序。 但是当在出现 - 全局,标量(即不是数组)。 - 类型为 `const int` 或 `const int const`。 - 带有初始化。 - 不是 `extern` 的。 的声明时,将会被解析为整数常量。 整数常量在使用的时候会被直接替换为对应的右值,失去作为全局变量左值的性质。 使用 `int const` 或 `int` 形式或添加 `extern` 可以避免这样的特殊处理。 ### `extern` 关键字 `extern` 在全局函数和变量的声明的开头中可以使用。 全局函数的声明和定义都会直接忽略这个关键字。全局函数的声明和定义由是否提供函数体决定,与该关键字无关。 全局变量如果使用了这个关键字,则有以下特性和限制: - 变量仅被声明,而没有被定义。 - 如果需要使用这样的变量,需要稍后提供定义,或在外部已经定义。 - 不可以初始化。 - 不可是数组。 ### 支持以下运算符 | 运算符 | 含义 | 结合性 | | --------------------------------- | ----------------------------------------------- | -------- | | `()` | 初等表达式(字面量、标识符、函数调用、括号) | | | `++` `--` `[]` | 后缀自增自减 数组下标 | 从左到右 | | `++` `--` `+` `-` `*` `&` `!` `~` | 前缀自增自减 正负号 取地址 解引用 逻辑非 按位非 | 从右到左 | | `*` `/` `%` | 乘除余 | 从左到右 | | `+` `-` | 加减 | 从左到右 | | `<<` `>>` | 左移和算术右移 | 从左到右 | | `<` `<=` `>` `>=` | 关系比较 | 从左到右 | | `==` `!=` | 相等比较 | 从左到右 | | `&` | 按位与 | 从左到右 | | `^` | 按位异或 | 从左到右 | | | | 按位或 | 从左到右 | | `&&` | 逻辑与 | 从左到右 | | || | 逻辑或 | 从左到右 | | `?:` | 条件 | 从右到左 | | `=` `+=` `-=` `*=` `/=` `%=` `<<=` `>>=` `&=` `^=` |= | 赋值 | 从右到左 | | `,` | 逗号 | 从左到右 | - 同级表达式的求值顺序与结合性一致。 - 加减号支持整数之间,指针与整数,指针之间的运算。 - 算术运算的结果总是被提升为 `int` 类型。布尔值用 `int` 类型表示。 - 由于空指针就是 `0`,因此指针和整数之间的比较运算没有禁止。 - 逻辑与和逻辑或支持短路求值。 ### 其它支持与不支持 - 支持全局变量和局部变量,局部变量遮挡全局变量。 - 不支持局部变量之间的遮挡,重名的局部变量为同一变量。 - 函数只支持最多八个参数。函数声明中支持可变参数,仅用于兼容 C 语言库。 - 类型检查有遗漏,若 C 编译器报错,而本语言编译通过,就可以认为是 UB。 - 例如函数调用的参数和 `return` 语句不会检查类型。 ## 限制 编译过程中涉及的以下参数: - 符号表总长度、字符串表总长度 - 符号数、字符串数、全局变量数、局部变量数 不能超过源代码中指定的常数。如果有必要这些常数可以适度加大。 目前源代码中的常数能够保证自举。 如果愿意,完全可以把程序中的各类表改为 `malloc` 和 `free` 动态管理,本语言是完全支持的。 ## 依赖 直接依赖下面这些 C 语言库函数和变量,在本语言中提供声明后调用。 - `printf` - `getchar` - `exit` - `ungetc` 和 `stdin`(理论上非必须,可以在本语言中手动模拟) - `fprintf` 和 `stderr`(理论上非必须,仅用于输出错误信息)