Compare commits
11 Commits
b8108acb2e
...
4dc291b27e
Author | SHA1 | Date | |
---|---|---|---|
4dc291b27e | |||
e18bf2c2d3 | |||
8114d04fb9 | |||
50969c6bb9 | |||
4893f0cd68 | |||
66505c1389 | |||
967a203414 | |||
57837abbba | |||
44051c7985 | |||
2097d9fd34 | |||
40eb2ce96f |
73
README.md
73
README.md
@ -1,6 +1,6 @@
|
||||
# RVBTCC
|
||||
|
||||
- 约 1500 行的轻量级自举编译器。
|
||||
- 约 1900 行的轻量级自举编译器。
|
||||
- 编译器和自举编译器行为一致。
|
||||
- 语法类似 C,输出 RISC-V 汇编。
|
||||
- 依赖几个 libc 函数用于输入输出。
|
||||
@ -12,7 +12,6 @@
|
||||
|
||||
### 真机运行
|
||||
|
||||
|
||||
编译运行程序,src 为本语言源代码。可以编译 demo 文件夹下的实例。
|
||||
|
||||
```sh
|
||||
@ -25,19 +24,6 @@ $ sh run-native.sh <src>
|
||||
$ sh boot-native.sh
|
||||
```
|
||||
|
||||
输出六个文件:
|
||||
|
||||
| 源代码 | 编译器 | 汇编 | 可执行 | 命名 |
|
||||
| ----------------- | --------- | ------- | --------- | ---------------------- |
|
||||
| boot.c boot-lib.c | gcc | | gcc.out | 自制编译器 |
|
||||
| boot.c boot-lib.h | gcc.out | boot1.s | boot1.out | 自举自制编译器 |
|
||||
| boot.c boot-lib.h | boot1.out | boot2.s | boot2.out | 自举自举自制编译器 |
|
||||
| boot.c boot-lib.h | boot2.out | boot3.s | | 验证自举自举自制编译器 |
|
||||
|
||||
后三次编译时,boot-lib.h 的内容被手动导入 boot.c 开头进行编译,boot-lib.c 提供的库通过链接引入。
|
||||
|
||||
自举的目标为 boot1.s == boot2.s == boot3.s
|
||||
|
||||
### 模拟运行
|
||||
|
||||
安装以下依赖
|
||||
@ -58,13 +44,31 @@ $ sh run.sh <src>
|
||||
$ sh boot.sh
|
||||
```
|
||||
|
||||
### 自举过程
|
||||
|
||||
|
||||
自举会输出六个文件,三个汇编文件和三个可执行文件:
|
||||
|
||||
| 源代码 | 编译器 | 汇编 | 可执行 | 代号 | 命名 |
|
||||
| ----------------- | --------- | ------- | --------- | ---- | ---------------------- |
|
||||
| boot.c boot-lib.c | gcc | | gcc.out | G | 自制编译器 |
|
||||
| boot.c boot-lib.h | gcc.out | boot1.s | boot1.out | B1 | 自举自制编译器 |
|
||||
| boot.c boot-lib.h | boot1.out | boot2.s | boot2.out | B2 | 自举自举自制编译器 |
|
||||
| boot.c boot-lib.h | boot2.out | boot3.s | | B3 | 验证自举自举自制编译器 |
|
||||
|
||||
后三次编译时,boot-lib.h 的内容被手动导入 boot.c 开头进行编译,boot-lib.c 提供的库通过链接引入。
|
||||
|
||||
整个自举及其验证的过程如下图所示:
|
||||
|
||||
![](bootstrapping.png)
|
||||
|
||||
自举的目标为 G、B1、B2 的可执行文件行为一致,也就是说 B1、B2、B3 的汇编代码一致。
|
||||
|
||||
## 语言文档
|
||||
|
||||
### 关键字
|
||||
### 注释
|
||||
|
||||
本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const`。
|
||||
|
||||
`const` 关键字可以在类型中使用,但会被直接忽略。支持它是为了更好兼容 C 程序。
|
||||
支持多行 `/* ... */` 和单行 `//` 两种注释
|
||||
|
||||
### 支持六个基本类型
|
||||
|
||||
@ -75,13 +79,11 @@ $ sh boot.sh
|
||||
| `int` | `int*` |
|
||||
|
||||
- 注意指针类型不是复合得来的,而是被视作整体。因此也不存在二重指针。
|
||||
|
||||
- 函数和数组不是类型系统的一部分。
|
||||
- 可以认为数组的类型就是其元素对应的指针类型。
|
||||
- 函数的参数类型和个数不会检查,返回值会参与类型检查。
|
||||
- 函数名只能被用于调用,函数调用被视为初等表达式。
|
||||
- 数组只支持一维数组,且数组的元素不能是指针类型。
|
||||
- 全局变量不能是指针类型。
|
||||
- 整数和字符字面量的类型是 `int`,字符串字面量的类型是 `char*`
|
||||
|
||||
### 支持的流程控制
|
||||
@ -91,6 +93,25 @@ $ sh boot.sh
|
||||
- `break` `continue`
|
||||
- `return`
|
||||
|
||||
### 关键字
|
||||
|
||||
本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const`。
|
||||
|
||||
#### `const` 关键字
|
||||
|
||||
`const` 关键字可以在类型中使用,在大部分情况下会被直接忽略。支持它是为了更好兼容 C 程序。
|
||||
|
||||
但是当在出现
|
||||
|
||||
- 全局,标量(即不是数组)
|
||||
- 类型为 `const int` 或 `const int const`
|
||||
- 带有初始化
|
||||
|
||||
的声明时,将会被解析为整数常量。
|
||||
|
||||
整数常量在使用的时候会被直接替换为对应的右值,失去作为全局变量左值的性质。
|
||||
|
||||
使用 `int const` 或 `int` 可以避免这样的特殊处理。
|
||||
|
||||
### 支持以下运算符
|
||||
|
||||
@ -109,15 +130,15 @@ $ sh boot.sh
|
||||
| <code>|</code> | 按位或 | 从左到右 |
|
||||
| `&&` | 逻辑与 | 从左到右 |
|
||||
| <code>||</code> | 逻辑或 | 从左到右 |
|
||||
| `=` | 赋值 | 从右到左 |
|
||||
| `?:` | 条件 | 从右到左 |
|
||||
| `=` `+=` `-=` `*=` `/=` `%=` `<<=` `>>=` `&=` `^=` <code>|=</code> | 赋值 | 从右到左 |
|
||||
| `,` | 逗号 | 从左到右 |
|
||||
|
||||
- 同级表达式的求值顺序与结合性一致。
|
||||
- 加减号支持整数之间,指针与整数,指针之间的运算。
|
||||
- 算术运算的结果总是被提升为 `int` 类型。布尔值用 `int` 类型表示。
|
||||
- 由于空指针就是 `0`,因此指针和整数之间的比较运算没有禁止。
|
||||
- 逻辑与和逻辑或支持短路求值。
|
||||
- 表达式没有左值和右值之分。可以认为右值总是存在一个临时的变量中。
|
||||
- 赋值不检查类型。强制类型转换可以用赋值给特定类型的变量实现。
|
||||
|
||||
### 其它支持与不支持
|
||||
|
||||
@ -125,7 +146,9 @@ $ sh boot.sh
|
||||
- 不支持局部变量之间的遮挡,重名的局部变量为同一变量。
|
||||
- 支持函数声明,可以通过函数声明来调用 C 语言库。不支持变量声明。
|
||||
- 函数只支持最多八个参数。函数声明中支持可变参数,仅用于兼容 C 语言库。
|
||||
- 类型检查可能有遗漏,若 C 编译器报错,而本语言编译通过,就可以认为是 UB。
|
||||
- 类型检查有遗漏,若 C 编译器报错,而本语言编译通过,就可以认为是 UB。
|
||||
- 例如函数调用的参数和 `return` 语句不会检查类型。
|
||||
|
||||
|
||||
## 限制
|
||||
|
||||
|
2
boot.sh
2
boot.sh
@ -5,7 +5,7 @@ gcc ../boot.c ../boot-lib.c -o gcc.out &&
|
||||
riscv64-linux-gnu-gcc-12 -static boot1.s ../boot-lib.c -o boot1.out &&
|
||||
qemu-riscv64 boot1.out < boot-all.c > boot2.s &&
|
||||
riscv64-linux-gnu-gcc-12 -static boot2.s ../boot-lib.c -o boot2.out &&
|
||||
qemu-riscv64 boot2.out < boot-all.c > boot3.s &&
|
||||
qemu-riscv64 boot2.out < boot-all.c > boot3.s
|
||||
cmp --silent boot1.s boot2.s && echo "boot1.s == boot2.s" || echo "boot1.s != boot2.s"
|
||||
cmp --silent boot2.s boot3.s && echo "boot2.s == boot3.s" || echo "boot2.s != boot3.s"
|
||||
cmp --silent boot1.s boot3.s && echo "boot1.s == boot3.s" || echo "boot1.s != boot3.s"
|
||||
|
BIN
bootstrapping.png
Normal file
BIN
bootstrapping.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
17
demo/add.c
Normal file
17
demo/add.c
Normal file
@ -0,0 +1,17 @@
|
||||
int printf(const char format[], ...);
|
||||
int scanf(const char format[], ...);
|
||||
int putchar(int ch);
|
||||
|
||||
int* p;
|
||||
int f1() {
|
||||
int a = 1;
|
||||
return *(a+(a+(a+(a+(a+(a+(a+(a+(a+(a+(p))))))))))); // a[10]
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
int a[15];
|
||||
p = a;
|
||||
for (int i = 0; i < 15; a[i] = i, ++i);
|
||||
return f1();
|
||||
}
|
@ -2,5 +2,4 @@ int printf(const char* format, ...);
|
||||
|
||||
int main() {
|
||||
printf("hello world %d\n", 42);
|
||||
return 0;
|
||||
}
|
46
demo/lut.c
Normal file
46
demo/lut.c
Normal file
@ -0,0 +1,46 @@
|
||||
int printf(const char format[], ...);
|
||||
int getchar();
|
||||
|
||||
char string_table[65536];
|
||||
int string_offset;
|
||||
int string_lut[4096];
|
||||
int string_lut_size;
|
||||
|
||||
int parse_string() {
|
||||
int offset = string_offset;
|
||||
int ch;
|
||||
while ((ch = getchar()) != '"') {
|
||||
if (ch == -1 || ch == '\n') {
|
||||
printf("expecting '\"'\n");
|
||||
return 1;
|
||||
}
|
||||
string_table[string_offset++] = ch;
|
||||
}
|
||||
string_table[string_offset++] = 0;
|
||||
string_lut[string_lut_size] = offset;
|
||||
return string_lut_size++;
|
||||
}
|
||||
|
||||
|
||||
int streq(const char* s1, const char* s2) {
|
||||
while (*s1 && *s2 && *s1 == *s2) {
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
return *s1 == *s2;
|
||||
}
|
||||
|
||||
void dump_string_table() {
|
||||
printf(".data\n");
|
||||
for (int i = 0; i < string_lut_size; ++i) {
|
||||
char* id = string_table + string_lut[i];
|
||||
printf(".LC%d: .string \"%s\", const: %d\n",
|
||||
i, id, streq(id, "const"));
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
char ch;
|
||||
while ((ch = getchar()) == '"') parse_string();
|
||||
dump_string_table();
|
||||
}
|
19
demo/parse.c
Normal file
19
demo/parse.c
Normal file
@ -0,0 +1,19 @@
|
||||
int getchar();
|
||||
|
||||
|
||||
int is_digit(int ch) {
|
||||
return '0' <= ch && ch <= '9';
|
||||
}
|
||||
|
||||
int parse_int(int ch) {
|
||||
int num = ch - '0';
|
||||
while (is_digit(ch = getchar())) {
|
||||
num = num * 10;
|
||||
num = num + ch - '0';
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
int main() {
|
||||
return parse_int(getchar());
|
||||
}
|
@ -43,5 +43,4 @@ void queen(int x) {
|
||||
int main() {
|
||||
queen(1);
|
||||
printf("solutions: %d\n", a[0]);
|
||||
return 0;
|
||||
}
|
@ -27,5 +27,4 @@ int main() {
|
||||
printf("%d ", a[i]);
|
||||
}
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
16
demo/strcmp.c
Normal file
16
demo/strcmp.c
Normal file
@ -0,0 +1,16 @@
|
||||
int printf(const char* format, ...);
|
||||
|
||||
int strcmp(const char* s1, const char* s2) {
|
||||
while (*s1 && *s2 && *s1 == *s2) {
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
return *s1 - *s2;
|
||||
}
|
||||
|
||||
int main() {
|
||||
const char* s1 = "helloworld";
|
||||
const char* s2 = "world";
|
||||
printf("%d\n", strcmp(s1, s2));
|
||||
printf("%d\n", strcmp(s1 + 5, s2));
|
||||
}
|
Loading…
Reference in New Issue
Block a user