Lv3
Riscv
发现 27_complex_binary
寄存器超过使用限制了,得复用寄存器才行,不能每个中间结果都开一个新的。
但其实无所谓,Lv4 会将寄存器完全改为使用栈来存储,所以不用 care,写完 Lv4 自然就过了。
Lv4
Koopa
在测试点 18_multiple_returns2
中,存在多条 return
语句,形如:
int main(){
return 0; return 1;
}
cpp我们应当只处理到第一个 return
语句后,就停止处理(或者提前看后续 Lv6 的实现办法)
Riscv
发现过不去 21_decl_after_decl3
测试点,遂检查了一下代码,发现是不能仅仅只在 load
、 store
指令中重置寄存器计数,对于 binary
指令,也需要重置寄存器计数。
来自写完之后的补充:后来就是在每次
void visit(const koopa_raw_value_t& value)
时,都会先重置寄存器计数了。
Lv6
Koopa
惨不忍睹:
Lv5:07_empty_ block1
09_summary1
11_ret_in_block2
14_ret_in_block3
Lv6:13_branch2
这些点都是因为最后一条语句的处理问题。
需要注意的是,基本块的结尾必须是 br,jump 或 ret 指令其中之一 (并且,这些指令只能出现在基本块的结尾)。也就是说,即使两个基本块是相邻的,例如上述程序的 % else 基本块和 % end 基本块,如果你想表达执行完前者之后执行后者的语义,你也必须在前者基本块的结尾添加一条目标为后者的 jump 指令。这点和汇编语言中 label 的概念有所不同。
值得一提的是,如下 Koopa IR 是合法的:
%then_0:
ret 1
%return_end_0:
jump %end_0
asm所以,我们得到一个弱智但有效的做法:给所有 ret
语句后都添加一个新的标签,保证每个 print
函数的末尾不是一条 br
/ jump
/ ret
,就可以了。
另外,在同一函数体内出现多个同名 alloc
指令是不合法的。
Lv6:14_else_match2
,检查是否正确处理了 if else
的 label。
Riscv
这里发现始终过不去 11_logical1
测试点,先 AE
后 WA
。
首先是 AE
,发现是我在处理 12 位立即数偏置的时候,错误地使用了 reg(sp)
的形式。
实际上偏移量不是指做成 t1(sp)
,而是先做 t1 = bias; t1 = sp + t1
,然后再 lw t0, (t1)
。对 sw
指令同理。
接着是 WA
,发现是我在处理 12 位立即数的时候,寄存器分配出现了问题,我手动调整了 context.stack_used
的值为临界值 2040
后,发现是我原先对于 load
处的寄存器分配有问题,我使用了 cur_reg
而不是 new_reg
,这会导致如果 load
指令目标偏置超过 12 位立即数限制,那么在 riscv._lw(reg, "sp", context.stack_map[load.src]);
中,会隐式地发现偏置大于 2048
并再次分配 t0
来存储偏置,从而造成一句 lw t0, (t0)
的指令。修改为 new_reg
后,即可 AC。
Lv7
Koopa
关于短路求值的一个测试点:需要注意一下,对于逻辑表达式,其返回值一定是一个布尔类型,所以你需要考虑如下的测试点,其不能被用常量传播直接求出:
int main() {
int x = 2;
putint(0||x);
putch(10);
}
cpp这个点的输出应当是 1,而不是 2。这个测试点甚至在全部的本地/在线测试中都不存在类似的,导致我直到通过了所有测试开始逐行加注释改善代码质量的时候才发现。
Lv8
Koopa
发现在 16_summary1
测试点上 AE
了,仔细检查尝试,发现了问题,即在不同函数体内可能声明同样的变量:
int f() {
int a = 1;
}
int g() {
int a = 2;
}
c这意味着你需要在每次进入函数体时清空 is_symbol_allocated
,在两个函数体内各自生成一次 alloc
指令。
Lv9
Koopa
你需要考虑形如 {}
的初始化,这个东西只要出现,就至少会初始化掉一个步长。
如果你发现在 22_arr_init1
测试点 WA / AE,那么就很有可能是此原因导致的,你可以本地测试如下测试点:
const int buf[3][3][1] = { 1,{},2 };
c这个测试点的输出应当是
alloc [[[i32, 1], 3], 3], {{{1}, {0}, {2}}, {{0}, {0}, {0}}, {{0}, {0}, {0}}}
plaintext如果你仅仅在处理第二个 {}
的时候检查对齐,而不考虑其 init_values
为空,一上来就对齐导致完全没有补 0 进而被直接跳过,那么很容易得到:
alloc [[[i32, 1], 3], 3], {{{1}, {2}, {0}}, {{0}, {0}, {0}}, {{0}, {0}, {0}}}
plaintext另外一个测试点是:
const int buf[2][3] = {{}, 1};
c这个测试点输出应当是:
global @buf_0 = alloc [[i32, 3], 2], {{0, 0, 0}, {1, 0, 0}}
plaintext一个数组表达式的 LVal 出现的位置是不确定的,其既可以作为值,也可以作为指针参数去调用函数,我们必须判断输出它时究竟是哪种情况,进而输出不同的 Koopa IR。
而判断的方法,就是看我们调用他们所使用的维度个数,相对于我们初始化他们时的维度个数的关系。
- 若调用时使用的维度个数等于初始化时知道的维度个数,则其为值,我们最后补上的应当是一句
load
指令 - 若调用时使用的维度个数小于初始化时知道的维度个数,则其为指针,我们最后补上的应当是一句
getelemptr
指令
注:对于指针的情况,你要记录他的维度为表达式 + 1。
Riscv
一个测试点:
int main() {
int b[2][3] = { 1, 2, 3, 4 };
putint(b[0][1]);
putch(10);
return 0;
}
c测试输出是 4 还是 2?如果是 4,那么说明你对于 getelemptr
指令的翻译有问题。
这是因为,getelemptr %0, 1
指令的翻译并不一定是 +4,而是也需要像之前一样,使用 get_alloc_size
函数来计算 %0
的偏移步长。
如果你本地所有测试、远程的从 Koopa 也能过,但是 Riscv 差一个点,那么可能是存在长跳转的问题,即跳转范围超过了 bnez
和 beqz
的跳转范围,所以需要使用 jump
指令。
只需要修改一下这两条的指令的实现,在 bnez
和 beqz
旁边添加新的标号,然后将原先的短跳转转为长跳转 jump
指令即可。
性能测试
发现是没有在 main.cpp
中允许 -perf
的模式(其实就是和 -riscv
一样),添加一下就行。