这是一篇针对计组P6的自动化测试学习

写在开头

由于P6是P5的迭代开发,因此本次工作将主要体现在数据生成上

命令行学习

无需,前面的完全够用

Python实现(数据执行器)

无需,照搬P5即可,具体见我的前几篇博客

数据生成器迭代思路

P6指令集

{add,sub,and,or,slt,sltu,addi,andi,ori,lb,lh,lw,sb,sh,sw,mult,multu,div,divu,mfhi,mflo,mthi,mtlo,beq,bne,lui,jal,jr,nop}

迭代思路

整体思路

等同与不等同

找到同类,直接归类 没有同类,单独添加

我举个例子,or,and,slt,sltu都是cal_r类型,可以直接和P5数据生成器中的add,sub等同处理。
lb,lh可以等同lw处理。
sb,sh可以等同sw处理。
等同的意思是,代码可以复用,规则可以复用。
对于mult,div则不可以,需要新规则的限制。
做完这些,大部分我们就归类完毕了,之后就是对于新规则的修修补补,维持程序正确

危险指令与安全指令

要这样考虑是为了逻辑上的简便,和作为指令生成时随机范围的依据,例如add,sub,and,or这些,就是安全指令,因为他们无论如何都不会引起异常,所以他们可以在任何位置生成(开头,代码块,延迟槽)……
而lw,sw,div这些,则是危险指令,对他们的操作稍有不慎,就会导致Mars报错,div不能除0,lw,sw要求地址合理,要求字对齐,而这些规则的处理,在随机生成中是需要我们提前用一些指令规避这些问题的,所以某些规则下生成的他们就不能出现在延迟槽中,因为不止一条指令。

具体细节

div除0的处理

div和divu是不能除0的,这在数学上本就不被允许,在Mars中是一种UB(未定义)行为,所以我们需要添加规则避免除0现象的产生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int rss= rs;
int rtt = rt;
while(rtt==0) #避免0号寄存器对后面的影响
{
rtt = Reg;
}
while(rss==0)
{
rss = Reg;
}
ori(rtt,rss,I+4,0);
ori(rtt,rss,I+4,0); #ori两个不同的立即数保证除数不为0
int delay = Delayinstr; #抽取随机数决定抽取指令池中的哪条指令
while(delay == 8||delay == 9) # 89为div和divu,我们这里不允许嵌套,所以要重新抽取
{
delay = Delayinstr;
}
Choose(delay, 0, rss, rtt, rs, rt, 0); #生成抽取好的指令,后面的参数是生成时寄存器不会选择rss,rtt,rs,rt(概率不生成)
delay = Delayinstr;
while(delay == 8||delay == 9)
{
delay = Delayinstr;
}
Choose(delay, 0, rss, rtt, rs, rt, 0); #同理(概率不生成)
printf("divu $%d,$%d\n", rtt, rss); 核心人物

以上代码便是对div,divu规则的具体表现,具体体现为,避免0寄存器的影响;在div这条指令的前1-2,2-3或3-4均为ori,保证除数不为0,至于为什么中间可能会插入两条指令,就是为了我P5时所说的那样,实现不同的转发级别。增强测试能力。

指令类的生成

细细察看我的代码生成,会发现我在尽力保持一种原则,及时是固定3条指令,我也会尽可能将这3条指令变为指令类,进行生成,这样既保证了正确性,也能提高随机性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
printf("sw $0,0($0)\n");
int imm = Ib;
sub(rt, rt, rt);
ori(rt, rt, 12, 0);
int random = rand() % 3;
if(random == 0)
{
printf("sw $%d,0($0)\n", rt);
}
else if(random == 1)
{
printf("sh $%d,0($0)\n", rt);
}
else if(random == 2)
{
printf("sb $%d,0($0)\n", rt);
}
random = rand() % 3;
if(random == 0)
{
printf("lw $%d,0($0)\n", rs);
}
else if(random == 1)
{
printf("lh $%d,0($0)\n", rs);
}
else if(random == 2)
{
printf("lb $%d,0($0)\n", rs);
}
random = rand() % 3;
if(random == 0)
{
printf("lw $%d,%d($%d)\n", rt, imm, rs);
}
else if(random == 1)
{
printf("lh $%d,%d($%d)\n", rt, imm, rs);
}
else if(random == 2)
{
printf("lb $%d,%d($%d)\n", rt, imm, rs);
}
random = rand() % 3;
if(random == 0)
{
printf("sw $%d,%d($0)\n", rt, Ib);
}
else if(random == 1)
{
printf("sh $%d,%d($0)\n", rt, Ib);
}
else if(random == 2)
{
printf("sb $%d,%d($0)\n", rt, Ib);
}

这一段代码是我为了解决load和store指令冲突的规则之一,会发现,我在生成这些指令时,看着复杂,实际上就是指令类的随机生成,store-load-load-store,这样既能测试转发和阻塞,又能保证正确性。

TIPS:由于Lb只能存1个字节,所以随机数Ib的生成范围应该在0-255之间,否则可能会出现sw了一个400,lb取出来就不是400,导致后面出错。

代码复用

刚才我在总体思路里说明了,对于类似指令下的已有规则,我们可以直接复用,不需创造新规则,这样稳定又高效,展示一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void sw_sh_sb(int rs, int rt) #原来是sw的生成函数的一部分,直接改为sw_sh_sb的生成函数
{
int flag = Reg % 2;
if (flag == 0)
{
int imm = Ib;
sub(rs, rs, rs);
if ((Reg % 3) == 0)
{
printf("sw $%d,%d($%d)\n", rt, imm, rs);
}
else if ((Reg % 3) == 1)
{
printf("sh $%d,%d($%d)\n", rt, imm, rs);
}
else
{
printf("sb $%d,%d($%d)\n", rt, imm, rs);
}
}
else
{
if (rs == 0)
{
rs++;
}
int imm1 = Ib;
int imm2 = Ib;
if (imm1 < imm2)
{
int temp = imm1;
imm1 = imm2;
imm2 = temp;
}
sub(rs, rs, rs);
ori(rs, rs, imm1, 0);
int imm3 = 0 - imm2;
if ((Reg % 3) == 0)
{
printf("sw $%d,%d($%d)\n", rt, imm3, rs);
}
else if ((Reg % 3) == 1)
{
printf("sh $%d,%d($%d)\n", rt, imm3, rs);
}
else
{
printf("sb $%d,%d($%d)\n", rt, imm3, rs);
}
}
}

如上,这个函数在P5中是sw生成规则的一部分,具体是为了加入负数检验EXT模块的正确性,在P6它便可以摇身一变,成为sw_sh_sb生成规则的一部分,我们要做的,只是添加一个随机的功能,抽取一个Store类即可。

jr和jal的处理

这个话题是老生常谈的,也是数据生成中最大的问题,稍微动错一点,就会导致跳转错误。所以我们对这两个的转发和阻塞实现可以从两个方面下手:
(1)让他们作为转发人,作为阻塞源:

1
2
3
4
5
#前面是jal,刚对$31进行了写入
if(Reg%3==0) printf("mult $31,$%d\n",Reg);
else if(Reg%3==1) printf("multu $31,$%d\n",Reg);
else if(Reg%3==2) printf("divu $%d,$31\n",Reg);
else printf("div $%d,$31\n",Reg);

这是我生成规则中,对于jal和乘除指令的一个特殊生成规则,有个小细节就是,在div中,保证$31作为除数,因为它绝对不为0,可以省去考虑div为0的步骤。
(2)作为被转发人,作为被阻塞源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int g = Reg % 5;
if (g == 0)
{
sub(rs, rs, rs);
add(rs, 31, rd);
sub(31, 31, 31); // rd $31 rs=0
add(rd, 0, 31);
}
else if (g == 1)
{
sub(rs, rs, rs);
add(rs, 31, rd);
sub(31, 31, 31); // rd $31 rs=0 $31=0
ori(rd, 31, 0, 0);
}
else if (g == 2)
{
int immi = I;
sub(rt, rt, rt);
printf("sw $%d,%d($%d)\n", 31, immi, rt);
sub(31, 31, 31);
printf("lw $%d,%d($%d)\n", 31, immi, rt);
}
else if(g == 3)
{
sub(rs, rs, rs);
add(rs, 31, rd);
add(31, 31, 31);
sub(rd, 31, 31); // rd $31 rs=0
}
else if(g==4)
{
mtlo(31);
mthi(31);
sub(31, 31, 31);
if(Reg%2) mflo(31);
else mfhi(31);
}

总体思路就是:把$31的值挪走,再挪回来。

有一个很遗憾的一点是,在这里对于$31的处理要小心加小心,因此难以实现指令类的随机,导致了随机性的有所下降,这个也只能通过不断地添加特殊指令序列去增强随机,笔者由于时间原因也只写了5条,这可能就是随机生成法的一个缺陷吧. 所以笔者寒假也在尝试一些新的数据生成方式,有所作为的话一定会再次开贴更新,先画个饼先~

效果展示

1
2
3
4
5
"grade": {
"forward": {
"average": 95.06304176516942,
"stall": {
"average": 95.52380952380952,

数据生成器

太长了所以放链接:https://github.com/ForeverYolo/2022-BUAA-CO/tree/main/P6

如果使用了本数据生成器,笔者亲测可以通过2022年P6课上强测!

P6数据生成器代码行数:823行