CS61C Lab3
# Lab 3
# Exercise 1: Familiarizing yourself with Venus
- What do the
.data
,.word
,.text
directives mean (i.e. what do you use them for)? Hint: think about the 4 sections of memory.
这里提示我们了内存的四个部分
.data
表示内存数据上数据段的开始,
.word 2, 4, 6, 8
表示在内存中连续存储4个32位整数(2、4、6、8)
n: .word 9
定义了一个名为 n
的标签,指向一个值为9的32位整数,也就是 n=9
.text
表示代码段(text section)的开始。在这个段中,程序存放实际执行的机器指令(如函数、主程序等)。
- Run the program to completion. What number did the program output? What does this number represent?
输出了 ,这个数字是 斐波那契数列的第 个
- At what address is
n
stored in memory? Hint: Look at the contents of the registers.
通过观察寄存器 x28 我们可以得到 n 的地址是 0x10000010
- Without actually editing the code (i.e. without going into the “Editor” tab), have the program calculate the 13th fib number (0-indexed) by manually modifying the value of a register. You may find it helpful to first step through the code. If you prefer to look at decimal values, change the “Display Settings” option at the bottom.
答案是
# Exercise 2: Translating from C to RISC-V
- The register representing the variable
k
.
t0
1
- The register representing the variable
sum
.
s0
1
- The registers acting as pointers to the
source
anddest
arrays.
la s1, source
la s2, dest
1
2
2
- The assembly code for the loop found in the C code.
for (k = 0; source[k] != 0; k++) {
dest[k] = fun(source[k]);
sum += dest[k];
}
1
2
3
4
2
3
4
- How the pointers are manipulated in the assembly code.
在汇编代码中,先通过 la
命令获取数组的首地址,然后通过 add t1, s1, s3
来获取到 source[i]
的值
# Exercise 3: Factorial
在这个 Exercise 中,需要实现一个函数计算阶乘
.globl factorial
.data
n: .word 10
.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:
# 需要实现一个函数计算阶乘
addi t0, x0, 1 # t0 = 1
addi t1, x0, 1 # t1 = 1
addi t2, x0, 1
beq a0, t2, end # if a0 == t2 end
beq a0, x0, end
loop:
mul t0, t0, t1 # t0 = t0 * t1
addi t1, t1, 1 # t1 += 1
bne t1, a0, loop # if t1 <= n loop
end:
mv a0, t0 # a0 = t0
ret
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
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
# Exercise 4: Calling Convention Checker
这个练习需要你修复一个汇编代码,我们使用
$ java -jar tools/venus.jar -cc lab03/cc_test.s
1
命令能获得当前代码的一些问题
.globl simple_fn naive_pow inc_arr
.data
failure_message: .asciiz "Test failed for some reason.\n"
success_message: .asciiz "Sanity checks passed! Make sure there are no CC violations.\n"
array:
.word 1 2 3 4 5
exp_inc_array_result:
.word 2 3 4 5 6
.text
main:
# We test our program by loading a bunch of random values
# into a few saved registers - if any of these are modified
# after these functions return, then we know calling
# convention was broken by one of these functions
li s0, 2623
li s1, 2910
# ... skipping middle registers so the file isn't too long
# If we wanted to be rigorous, we would add checks for
# s2-s20 as well
li s11, 134
# Now, we call some functions
# simple_fn: should return 1
jal simple_fn # Shorthand for "jal ra, simple_fn"
li t0, 1
bne a0, t0, failure
# naive_pow: should return 2 ** 7 = 128
li a0, 2
li a1, 7
jal naive_pow
li t0, 128
bne a0, t0, failure
# inc_arr: increments "array" in place
la a0, array
li a1, 5
jal inc_arr
jal check_arr # Verifies inc_arr and jumps to "failure" on failure
# Check the values in the saved registers for sanity
li t0, 2623
li t1, 2910
li t2, 134
bne s0, t0, failure
bne s1, t1, failure
bne s11, t2, failure
# If none of those branches were hit, print a message and exit normally
li a0, 4
la a1, success_message
ecall
li a0, 10
ecall
# Just a simple function. Returns 1.
#
# FIXME Fix the reported error in this function (you can delete lines
# if necessary, as long as the function still returns 1 in a0).
simple_fn:
# mv a0, t0
li a0, 1
ret
# Computes a0 to the power of a1.
# This is analogous to the following C pseudocode:
#
# uint32_t naive_pow(uint32_t a0, uint32_t a1) {
# uint32_t s0 = 1;
# while (a1 != 0) {
# s0 *= a0;
# a1 -= 1;
# }
# return s0;
# }
#
# FIXME There's a CC error with this function!
# The big all-caps comments should give you a hint about what's
# missing. Another hint: what does the "s" in "s0" stand for?
naive_pow:
# BEGIN PROLOGUE
# END PROLOGUE
addi sp, sp, -8
sw ra, 4(sp)
sw s0, 0(sp)
li s0, 1
naive_pow_loop:
beq a1, zero, naive_pow_end
mul s0, s0, a0
addi a1, a1, -1
j naive_pow_loop
naive_pow_end:
mv a0, s0
lw s0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
ret
# Increments the elements of an array in-place.
# a0 holds the address of the start of the array, and a1 holds
# the number of elements it contains.
#
# This function calls the "helper_fn" function, which takes in an
# address as argument and increments the 32-bit value stored there.
inc_arr:
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
sw s1, 4(sp)
mv s0, a0 # Copy start of array to saved register
mv s1, a1 # Copy length of array to saved register
li t0, 0 # Initialize counter to 0
inc_arr_loop:
beq t0, s1, inc_arr_end
slli t1, t0, 2 # Convert array index to byte offset
add a0, s0, t1 # Add offset to start of exp_inc_array_result
sw t0, 0(sp)
jal helper_fn
lw t0, 0(sp)
addi t0, t0, 1 # Increment counter
j inc_arr_loop
inc_arr_end:
lw s1, 4(sp)
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
# This helper function adds 1 to the value at the memory address in a0.
# It doesn't return anything.
# C pseudocode for what it does: "*a0 = *a0 + 1"
#
# FIXME This function also violates calling convention, but it might not
# be reported by the Venus CC checker (try and figure out why).
# You should fix the bug anyway by filling in the prologue and epilogue
# as appropriate.
helper_fn:
addi sp, sp, -8
sw ra, 4(sp)
sw s0, 0(sp)
lw t1, 0(a0)
addi s0, t1, 1
sw s0, 0(a0)
lw s0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
ret
# YOU CAN IGNORE EVERYTHING BELOW THIS COMMENT
# Checks the result of inc_arr, which should contain 2 3 4 5 6 after
# one call.
# You can safely ignore this function; it has no errors.
check_arr:
la t0, exp_inc_array_result
la t1, array
addi t2, t1, 20 # Last element is 5*4 bytes off
check_arr_loop:
beq t1, t2, check_arr_end
lw t3, 0(t0)
lw t4, 0(t1)
bne t3, t4, failure
addi t0, t0, 4
addi t1, t1, 4
j check_arr_loop
check_arr_end:
ret
# This isn't really a function - it just prints a message, then
# terminates the program on failure. Think of it like an exception.
failure:
li a0, 4 # String print ecall
la a1, failure_message
ecall
li a0, 10 # Exit ecall
ecall
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
- What caused the errors in
simple_fn
,naive_pow
, andinc_arr
that were reported by the Venus CC checker?
在 simple_fn
中, 是调用者保存的,然而在函数中调用需要赋一个初值
在 naive_pow
中,使用了 s0
(保存寄存器),但未在函数开头保存它
在 inc_arr
中,使用了 s0
和 s1
(保存寄存器),但未保存它们。调用 helper_fn
前未保存临时寄存器 t0
(虽然 t0
是临时寄存器,但跨函数调用时需手动保存)。
- In RISC-V, we call functions by jumping to them and storing the return address in the
ra
register. Does calling convention apply to the jumps to thenaive_pow_loop
ornaive_pow_end
labels? 在 RISC-V 中,我们通过跳转到函数并将返回地址存储在ra
寄存器中来调用函数。调用约定是否适用于跳转到naive_pow_loop
函数? 或naive_pow_end
标签?
不实用,ra
属于函数调用,而 naive_pow_loop
和 naive_pow_end
属于标签跳转
- Why do we need to store
ra
in the prologue forinc_arr
, but not in any other function? 为什么我们需要将ra
存储在inc_arr
的序言中,而不是存储在任何其他函数中?
因为接下来需要调用 helper_fn
函数,必须在在开始的时候保存 ra
- Why wasn’t the calling convention error in
helper_fn
reported by the CC checker? (Hint: it’s mentioned above in the exercise instructions.)
不知道,不会,大佬教教
# Exercise 5: RISC-V function calling with map
.globl map
.text
main:
jal ra, create_default_list
add s0, a0, x0 # a0 = s0 is head of node list
#print the list
add a0, s0, x0
jal ra, print_list
# print a newline
jal ra, print_newline
# load your args
add a0, s0, x0 # load the address of the first node into a0
# load the address of the function in question into a1 (check out la on the green sheet)
la a1, square # 加载 square 函数的地址到 a1
# issue the call to map
jal ra, map
# print the list
add a0, s0, x0
jal ra, print_list
# print another newline
jal ra, print_newline
addi a0, x0, 10
ecall #Terminate the program
map:
# Prologue: Make space on the stack and back-up registers
addi sp, sp, -12 # 分配栈空间(保存 ra, s0, s1)
sw ra, 8(sp) # 保存返回地址
sw s0, 4(sp) # 保存 s0
sw s1, 0(sp) # 保存 s1
beq a0, x0, done # If we were given a null pointer (address 0), we're done.
add s0, a0, x0 # Save address of this node in s0
add s1, a1, x0 # Save address of function in s1
# load the value of the current node into a0
lw a0, 0(s0) # 加载当前节点的值到 a0(作为函数参数)
# Call the function in question on that value. DO NOT use a label (be prepared to answer why).
jalr ra, s1, 0 # 调用函数(地址在 s1 中),结果存储在 a0
# store the returned value back into the node
sw a0, 0(s0) # 将返回值存回当前节点的 value
# Load the address of the next node into a0
lw a0, 4(s0) # 加载下一个节点的地址到 a0
# Put the address of the function back into a1 to prepare for the recursion
add a1, s1, x0 # 将函数地址从 s1 恢复到 a1
# recurse
jal ra, map # 递归调用 map
done:
# Epilogue: Restore register values and free space from the stack
lw s1, 0(sp) # 恢复 s1
lw s0, 4(sp) # 恢复 s0
lw ra, 8(sp) # 恢复 ra
addi sp, sp, 12 # 释放栈空间
jr ra # Return to caller
square:
mul a0, a0, a0
jr ra
# ... (其余代码保持不变)
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
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