跳转至

CS61C Lecture09: RISC-V 位操作与函数调用


一、位操作指令详解

1. 位掩码操作

andi x5, x6, 0xFF        # 取最低字节(x5 = x6 & 0x000000FF)
andi x7, x8, 0xFF000000  # 取最高字节(x7 = x8 & 0xFF000000)
掩码应用场景: - 提取颜色通道(ARGB格式) - 网络协议头解析 - 字符编码处理

2. 逻辑运算技巧

操作 实现方式 示例
按位取反 XOR全1掩码 xori x5, x6, 0xFFFFFFFF
清除特定位 AND取反掩码 andi x5, x6, 0xFFFFF0FF(清除第8-11位)
设置特定位 OR置位掩码 ori x5, x6, 0x00000F00

3. 移位指令对比

指令 格式 行为 用途
sll sll rd, rs1, rs2 逻辑左移(补0) 快速乘法(2^n倍)
slli slli rd, rs1, imm 立即数逻辑左移
srl srl rd, rs1, rs2 逻辑右移(补0) 无符号数除法
sra sra rd, rs1, rs2 算术右移(符号扩展) 有符号数除法
srai srai rd, rs1, imm 立即数算术右移

示例

slli x5, x6, 3    # x5 = x6 * 8
srai x7, x8, 2    # x7 = x8 / 4(有符号数)

二、程序控制核心机制

1. 程序计数器(PC)

  • 32位寄存器,存储下一条指令的字节地址
  • 默认行为:PC += 4(顺序执行)
  • 跳转时:PC = target_address

2. 寄存器别名表

物理寄存器 符号名称 用途
x0 zero 常数0
x1 ra 返回地址
x2 sp 栈指针
x10-x17 a0-a7 参数/返回值
x5-x7, x28-x31 t0-t6 临时寄存器

三、伪指令解析

伪指令 实际指令 功能
mv rd, rs addi rd, rs, 0 寄存器复制
li rd, imm 组合指令(可能包含lui+addi) 加载立即数
nop addi x0, x0, 0 空操作(用于流水线控制)
j label jal x0, label 无条件跳转
ret jalr x0, 0(x1) 函数返回

nop的三大作用: 1. 填充流水线气泡 2. 对齐指令地址 3. 软件延时(不推荐)


四、函数调用机制

1. 函数调用六步曲

  1. 参数传递:a0-a7寄存器(前8个参数)
  2. 控制转移:jal指令(保存返回地址到ra)
  3. 栈帧分配addi sp, sp, -framesize
  4. 局部存储:保存被调用者保存寄存器(s0-s11)
  5. 返回值设置:a0/a1寄存器
  6. 资源释放addi sp, sp, framesize + ret

2. 关键跳转指令对比

指令 格式 行为 用途
j jal x0, offset PC = PC + offset 短距离跳转
jal jal rd, offset rd=PC+4; PC=PC+offset 函数调用
jalr jalr rd, offset(rs1) rd=PC+4; PC=rs1+offset 间接跳转
jr jalr x0, 0(rs1) PC = rs1 寄存器跳转

调用示例

# 调用函数func
    addi a0, x0, 5     # 设置参数
    jal ra, func        # 跳转并保存返回地址
    ...                 # 后续代码

func:
    addi sp, sp, -16    # 分配栈空间
    sw ra, 12(sp)       # 保存返回地址
    ...                 # 函数体
    lw ra, 12(sp)      # 恢复返回地址
    addi sp, sp, 16     # 释放栈空间
    ret                 # 返回

五、函数调用规范

1. 寄存器保存规则

寄存器类型 保存责任 寄存器列表
调用者保存 调用者负责保存 t0-t6, a0-a7
被调用者保存 被调用者负责保存 s0-s11, ra

2. 栈帧结构示例

High Address
|----------------|
| 保存的s1       | <- sp + 24
|----------------|
| 保存的s0       | <- sp + 20
|----------------|
| 保存的ra       | <- sp + 16
|----------------|
| 局部变量2      | <- sp + 12
|----------------|
| 局部变量1      | <- sp + 8
|----------------|
| 参数溢出区     | <- sp + 4
|----------------|
| 当前栈帧       | <- sp
Low Address

六、高级编程技巧

1. 尾调用优化

# 普通递归调用
factorial:
    addi sp, sp, -8
    sw ra, 4(sp)
    ...
    jal factorial       # 产生新栈帧

# 尾递归优化版
factorial_tail:
    ...
    mv a0, t0          # 直接修改参数
    j factorial_tail    # 复用当前栈帧

2. 多精度运算

# 64位加法(x5:x4 = x5:x4 + x7:x6)
add x4, x4, x6         # 低32位相加
sltu t0, x4, x6        # 检测进位
add x5, x5, x7         # 高32位相加
add x5, x5, t0         # 加上进位

七、常见错误分析

  1. 栈不平衡

    # 错误示例
    func:
        addi sp, sp, -12
        ... 
        ret  # 忘记恢复sp!
    
    后果:后续函数调用栈损坏
  2. 未保存调用者保存寄存器

    # 错误示例
    caller:
        mv t0, a0
        jal func
        add a0, t0, a0  # t0可能被func修改
    
    正确做法:调用前保存t0到栈中
  3. 错误使用ra寄存器

    # 错误示例
    func:
        jal helper    # 覆盖ra!
        ret           # 返回地址丢失
    
    正确做法:嵌套调用前保存ra到栈中