CS61C Lecture09: RISC-V 位操作与函数调用
一、位操作指令详解
1. 位掩码操作
andi x5, x6, 0xFF # 取最低字节(x5 = x6 & 0x000000FF)
andi x7, x8, 0xFF000000 # 取最高字节(x7 = x8 & 0xFF000000)
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 | 立即数算术右移 |
示例:
二、程序控制核心机制
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. 函数调用六步曲
- 参数传递:a0-a7寄存器(前8个参数)
- 控制转移:jal指令(保存返回地址到ra)
- 栈帧分配:
addi sp, sp, -framesize - 局部存储:保存被调用者保存寄存器(s0-s11)
- 返回值设置:a0/a1寄存器
- 资源释放:
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 # 加上进位
七、常见错误分析
-
栈不平衡:
后果:后续函数调用栈损坏 -
未保存调用者保存寄存器:
正确做法:调用前保存t0到栈中 -
错误使用ra寄存器:
正确做法:嵌套调用前保存ra到栈中