22.CS61C Lab3

Lab 3

Exercise 1: Familiarizing yourself with Venus

  1. 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)的开始。在这个段中,程序存放实际执行的机器指令(如函数、主程序等)。

  1. Run the program to completion. What number did the program output? What does this number represent?

输出了 34,这个数字是 斐波那契数列的第 9

  1. At what address is n stored in memory? Hint: Look at the contents of the registers.

通过观察寄存器 x28 我们可以得到 n 的地址是 0x10000010

  1. 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.

答案是 233

Exercise 2: Translating from C to RISC-V

t0
s0
la s1, source
la s2, dest
for (k = 0; source[k] != 0; k++) {
    dest[k] = fun(source[k]);
    sum += dest[k];
}

在汇编代码中,先通过 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

Exercise 4: Calling Convention Checker

这个练习需要你修复一个汇编代码,我们使用

$ java -jar tools/venus.jar -cc lab03/cc_test.s

命令能获得当前代码的一些问题

.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

simple_fn 中,t0 是调用者保存的,然而在函数中调用需要赋一个初值

naive_pow 中,使用了 s0(保存寄存器),但未在函数开头保存它

inc_arr 中,使用了 s0s1(保存寄存器),但未保存它们。调用 helper_fn 前未保存临时寄存器 t0(虽然 t0 是临时寄存器,但跨函数调用时需手动保存)。

不实用,ra 属于函数调用,而 naive_pow_loopnaive_pow_end 属于标签跳转

因为接下来需要调用 helper_fn 函数,必须在在开始的时候保存 ra

不知道,不会,大佬教教

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

# ... (其余代码保持不变)