Back

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 测试点,遂检查了一下代码,发现是不能仅仅只在 loadstore 指令中重置寄存器计数,对于 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 测试点,先 AEWA

首先是 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 差一个点,那么可能是存在长跳转的问题,即跳转范围超过了 bnezbeqz 的跳转范围,所以需要使用 jump 指令。

只需要修改一下这两条的指令的实现,在 bnezbeqz 旁边添加新的标号,然后将原先的短跳转转为长跳转 jump 指令即可。

性能测试

发现是没有在 main.cpp 中允许 -perf 的模式(其实就是和 -riscv 一样),添加一下就行。

编译原理大作业的奇妙测试点们
https://arthals.ink/blog/compile-principle-testcases
Author Arthals
Published at January 14, 2025
Comment seems to stuck. Try to refresh?✨