Exercise 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.data
.word 2, 4, 6, 8
n: .word 9

.text
main:
add t0, x0, x0
addi t1, x0, 1
la t3, n
lw t3, 0(t3)
fib:
beq t3, x0, finish
add t2, t1, t0
mv t0, t1
mv t1, t2
addi t3, t3, -1
j fib
finish:
addi a0, x0, 1
addi a1, t0, 0
ecall # print integer ecall
addi a0, x0, 10
ecall # terminate ecall

Q: .data.word, .text指令的作用是什么?

A: 汇编语言编程中,像.data.word.text这样的指令用于指定代码的不同部分,并定义数据和指令。

  • .data:该指令标志着数据声明的开始部分。通常后面会跟着变量或常量的声明。在你的示例中:
1
2
3
.data
.word 2, 4, 6, 8
n: .word 9

这个部分声明了一个整数数组,包含值2、4、6和8,并且一个变量n被初始化为值9。

  • .word:该指令用于在内存中分配存储单词大小的数据空间(通常是32位或4字节)。在示例中,它用于初始化数组和变量n

  • .text:该指令标志着代码段的开始部分,其中包含了指令。在示例中:

1
2
3
4
5
6
7
.text
main:
···
fib:
···
finish:
···

Exercise 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int source[] = {3, 1, 4, 1, 5, 9, 0};
int dest[10];

int fun(int x) {
return -x * (x + 1);
}

int main() {
int k;
int sum = 0;
for (k = 0; source[k] != 0; k++) {
dest[k] = fun(source[k]);
sum += dest[k];
}
return sum;
}
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
.globl main

.data
# 初始化数组
source:
.word 3
.word 1
.word 4
.word 1
.word 5
.word 9
.word 0
dest:
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0

.text
fun:
addi t0, a0, 1
sub t1, x0, a0
mul a0, t0, t1
jr ra

main:
# BEGIN PROLOGUE
addi sp, sp, -20
sw s0, 0(sp) # SUM
sw s1, 4(sp) # SOURCE指针
sw s2, 8(sp) # DEST指针
sw s3, 12(sp)
sw ra, 16(sp)
# END PROLOGUE
addi t0, x0, 0 # K
addi s0, x0, 0
la s1, source
la s2, dest
loop:
slli s3, t0, 2 # 偏移量
add t1, s1, s3 # source偏移后的地址
lw t2, 0(t1) # source[k]的值
# CONDITION
beq t2, x0, exit
add a0, x0, t2 # a0存储source[k]的值,以便后面传参
# JUMP FUN
addi sp, sp, -8
sw t0, 0(sp)
sw t2, 4(sp)
jal fun # a0存储着fun返回的值
lw t0, 0(sp)
lw t2, 4(sp)
addi sp, sp, 8
# BACK
add t2, x0, a0 # t2代a0
add t3, s2, s3 # dest偏移后的地址
sw t2, 0(t3) # 将结果存入dest
add s0, s0, t2 # sum += t2

addi t0, t0, 1 # k++
jal x0, loop
exit:
add a0, x0, s0 # return sum
# BEGIN EPILOGUE
lw s0, 0(sp)
lw s1, 4(sp)
lw s2, 8(sp)
lw s3, 12(sp)
lw ra, 16(sp)
addi sp, sp, 20
# END EPILOGUE
jr ra

Exercise 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
.globl factorial

.data
n: .word 8

.text
main:
la t0, n
lw a0, 0(t0)
jal ra, factorial

addi a1, a0, 0
addi a0, x0, 1
ecall # Print Result

addi a1, x0, '\n'
addi a0, x0, 11
ecall # Print newline

addi a0, x0, 10
ecall # Exit

factorial:
# YOUR CODE HERE
addi sp, sp, -8
sw t1, 0(sp)
sw t2, 4(sp)
addi t1, x0, 1
add t2, a0, x0
Loop:
beq t2, t1, Done
sub t2, t2, t1
mul a0, a0, t2
j Loop
Done:
lw t2, 4(sp)
lw t1, 0(sp)
addi sp, sp, 8
jr ra

Exercise 4

  1. simple_fnnaive_powinc_arr 函数中,调用约定错误是由于未正确保存和恢复被调用的寄存器所致。在 simple_fn 中,没有保存和恢复 ra 寄存器;在 naive_pow 中,没有保存和恢复 s0 寄存器;在 inc_arr 中,没有保存和恢复 ra 寄存器。
  2. 在 RISC-V 中,调用函数是通过跳转实现的,并将返回地址存储在 ra 寄存器中。然而,调用约定仅适用于实际的函数调用,而不适用于跳转到标签(如 naive_pow_loopnaive_pow_end)。因此,在这种情况下,调用约定不适用于这些标签。
  3. 我们需要在 inc_arr 函数中保存和恢复 ra 寄存器,因为在函数内部调用了另一个函数 helper_fn。根据调用约定,被调用的函数必须保存并恢复 ra 寄存器。而其他函数没有调用其他函数,因此不需要在其中保存和恢复 ra 寄存器。
  4. 虽然 helper_fn 函数也违反了调用约定(未保存和恢复被调用的寄存器),但 Venus CC 检查器可能没有报告这个错误。这是因为检查器主要关注寄存器的直接使用情况,而不会分析通过指针间接访问的内存位置的修改。