编译实践学习 Part 9
License: CC BY-NC-SA 4.0
lv 9.1 & lv 9.2
内容差不多,就一起写了。
上来就是报了 \(5\) 个 shift/reduce conflict
,很快啊!我全都没防出去(
但是对于怎么报错的完全没思路,开了 -Wcex
照样看不懂。
怎么会逝呢?
对着代码肉眼查错了 1h 才发现 ConstInitValList ',' ConstInitVal
中的 ','
忘加了。
我真傻,真的。
对于一个多维数组,用一个 std::list<int>
记录它的每一维大小。如果不是数组则列表为空。
读到此处, 我觉得你最关心的问题应该是: 到底应该如何理解 SysY 的初始化列表, 然后把它转换成一个填好 0 的形式呢? 其实, 处理 SysY (或 C 语言) 中的初始化列表时, 可以遵循这几个原则:
- 记录待处理的 \(n\) 维数组各维度的总长 \(\text{len}_1,\text{len}_2,\cdots,\text{len}_n\). 比如
int[2][3][4]
各维度的长度分别为 \(2\), \(3\) 和 \(4\).- 依次处理初始化列表内的元素, 元素的形式无非就两种可能: 整数, 或者另一个初始化列表.
- 遇到整数时, 从当前待处理的维度中的最后一维 (第 \(n\) 维) 开始填充数据.
- 遇到初始化列表时:
- 当前已经填充完毕的元素的个数必须是 \(\text{len}_n\) 的整数倍, 否则这个初始化列表没有对齐数组维度的边界, 你可以认为这种情况属于语义错误.
- 检查当前对齐到了哪一个边界, 然后将当前初始化列表视作这个边界所对应的最长维度的数组的初始化列表, 并递归处理. 比如:
- 对于
int[2][3][4]
和初始化列表{1, 2, 3, 4, {5}}
, 内层的初始化列表{5}
对应的数组是int[4]
.- 对于
int[2][3][4]
和初始化列表{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, {5}}
, 内层的初始化列表{5}
对应的数组是int[3][4]
.- 对于
int[2][3][4]
和初始化列表{{5}}
, 内层的初始化列表{5}
之前没出现任何整数元素, 这种情况其对应的数组是int[3][4]
.
先写 const
。
ConstDef:
IDENT ArrayDimension '=' ConstInitVal {
// ...
}
对于局部数组变量,可以一个一个赋值;对于全局数组变量(例如 int a[2] = {b, c};
)怎么办?
对于全局数组变量, 语义规定它的初始值也必须是常量表达式, 你也需要对其求值.
原来是我想多了。
注意有一个坑点:std::list
为空时 std::list::size()
不一定为 \(0\). 我也不知道为什么。
计算类型大小
int get_array_size(const koopa_raw_type_t arr) {
if(arr->tag == KOOPA_RTT_ARRAY) {
return arr->data.array.len * get_array_size(arr->data.array.base);
} else if(arr->tag == KOOPA_RTT_POINTER) {
return get_array_size(arr->data.pointer.base);
} else {
return 4;
}
}
int get_array_size(const koopa_raw_value_t arr) {
return get_array_size(arr->ty);
}
于是就行了。
附一个建议:在 git 里合并(rebase -i
)两个版本时用 squash
比用 drop
更方便。
用(局部/全局)(常量/变量)的组合能测出不少 bug.
lv 9.3
回忆一下 C 语言的相关内容: 函数形式参数中的
int arr[]
,int arr[][10]
等, 实际上表示的是指针, 也就是int *arr
和int (*arr)[10]
, 而不是数组. SysY 中的情况与之类似.
由于之前我设计 Koopa_val
类型时为省事而对数组和变量不作区分,现在部分解引用的数组应该用 getelemptr
而不是 load
。
但是不想再加新的类型了,干脆就在原来的类型上修修补补,最后它确实能跑。
然后是数组大小要在编译期求值,我选择在 sysy.y
里求,但符号表不允许我这么做。
一个大力出奇迹的方法:添加操作符号表的函数。
但是 undefined reference to ...
。
另一个大力出奇迹的方法:包装成一个类。
然后就神奇地又过了两个样例……突然就懂了屎山是如何形成的。
然后我一天后良心发现并把它重构了。
生成汇编时发现不能出现 2048(sp)
类似物.
imm12
: 12-bit 有符号立即数, 范围为 \([−2048,2047]\)
有一个小坑点:计算数组下一维大小(比如对 int[2][3]
取下标时的偏移量)时指针和数组的处理方式不同。
表达能力不行,给出一组数据:
void foo(int x[][10]){
x[1][1] = 114;
}
int main(){
int x[10][10];
foo(x);
return x[1][1];
}
于是改了改顺利 AC。
别看说起来很简单,做的时候是真的折磨啊……
一些感想
完结撒花!
lv 9+ 的内容可能后面还会咕咕咕地更新。
为什么学编译?
见 Part 1.
本来想给 awesome-sysy
做点贡献的,但发现自己看不懂 sysy 的语法,干脆就从头开始做编译器了。
杂
代码在这。码风可能不太工程(