learnxinyminutes-docs/zh-cn/mips-cn.md
Boris Verkhovskiy 434f15cd10 .markdown -> .md
2024-12-08 20:03:01 -07:00

334 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
language: "MIPS Assembly"
filename: MIPS-cn.asm
contributors:
- ["Stanley Lim", "https://github.com/Spiderpig86"]
translators:
- ["Liu Yihua", "https://github.com/yihuajack"]
lang: zh-cn
---
MIPSMicroprocessor without Interlocked Pipeline Stages汇编语言是为了配合约翰·雷洛伊·亨尼西于1981年设计的 MIPS 微处理器范式而设计的,这些 RISC 处理器用于嵌入式系统,例如网关和路由器。
[阅读更多](https://en.wikipedia.org/wiki/MIPS_architecture)
```asm
# 注释用一个 '#' 表示
# 一行中 '#' 之后的所有文本都会被汇编器的词法分析器忽略
# 程序通常包含 .data 和 .text 部分
.data # 数据存储在内存中在RAM中分配
# 类似于高级语言中的变量
# 声明遵循( 标签: .类型 值 )的声明形式
hello_world: .asciiz "Hello World\n" # 声明一个 null 结束的字符串
num1: .word 42 # 整数被视为字
# 32位值
arr1: .word 1, 2, 3, 4, 5 # 字数组
arr2: .byte 'a', 'b' # 字符数组每个1字节
buffer: .space 60 # 在 RAM 中分配空间
# 不清除为0
# 数据类型的大小
_byte: .byte 'a' # 1字节
_halfword: .half 53 # 2字节
_word: .word 3 # 4字节
_float: .float 3.14 # 4字节
_double: .double 7.0 # 8字节
.align 2 # 数据的内存对齐
# 其中数字应是2的幂表示几字节对齐
# .align 2 表示字对齐(因为 2^2 = 4 字节)
.text # 这部分包括指令和程序逻辑
.globl _main # 声明一个全局指令标签
# 其他文件都可以访问
_main: # MIPS 程序按顺序执行指令
# 这条标签下的代码将首先执行
# 打印 "hello world"
la $a0, hello_world # 加载存储在内存中的字符串地址
li $v0, 4 # 加载 syscall 的值
# (数字代表要执行哪个 syscall
syscall # 使用给定的参数($a0执行指定的 syscall
# 寄存器(用于在程序执行期间保存数据)
# $t0 - $t9 # 临时寄存器,用于过程内部的中间计算
# (过程调用时不保存)
# $s0 - $s7 # 保留寄存器(被保留的寄存器,过程调用时保存)
# 通常保存在栈中
# $a0 - $a3 # 参数寄存器,用于传递过程的参数
# $v0 - $v1 # 返回寄存器,用于向调用过程返回值
# 存取指令
la $t0, label # 将内存中由 label 指定的值的地址复制到寄存器 $t0 中
lw $t0, label # 从内存中复制一个字
lw $t1, 4($s0) # 从寄存器中存储的地址复制一个字
# 偏移量为4字节地址 + 4
lb $t2, label # 把一个字节复制到寄存器 $t2 的低阶部分
lb $t2, 0($s0) # 从 $s0 的源地址复制一个字节
# 偏移量为0
# 同理也适用于 'lh' (取半字)
sw $t0, label # 将字存储到由 label 映射的内存地址中
sw $t0, 8($s0) # 将字存储到 $s0 指定的地址中
# 偏移量为8字节
# 同理也适用于 'sb' (存字)和 'sh' (存半字)。'sa'不存在
### 数学 ###
_math:
# 记住要将值加载到寄存器中
lw $t0, num # 从数据部分
li $t0, 5 # 或者从一个立即数(常数)
li $t1, 6
add $t2, $t0, $t1 # $t2 = $t0 + $t1
sub $t2, $t0, $t1 # $t2 = $t0 - $t1
mul $t2, $t0, $t1 # $t2 = $t0 * $t1
div $t2, $t0, $t1 # $t2 = $t0 / $t1
# MARS 的某些版本可能不支持)
div $t0, $t1 # 执行 $t0 / $t1。
# 用 'mflo' 得商,用 'mfhi' 得余数
# 移位
sll $t0, $t0, 2 # 按位左移立即数常数值2
sllv $t0, $t1, $t2 # 根据一个寄存器中的变量值左移相应位
srl $t0, $t0, 5 # 按位右移
# 不保留符号用0符号扩展
srlv $t0, $t1, $t2 # 根据一个寄存器中的变量值右移相应位
sra $t0, $t0, 7 # 按算术位右移(保留符号)
srav $t0, $t1, $t2 # 根据一个寄存器中的变量值右移相应算数位
# 按位运算符
and $t0, $t1, $t2 # 按位与
andi $t0, $t1, 0xFFF # 用立即数按位与
or $t0, $t1, $t2 # 按位或
ori $t0, $t1, 0xFFF # 用立即数按位或
xor $t0, $t1, $t2 # 按位异或
xori $t0, $t1, 0xFFF # 用立即数按位异或
nor $t0, $t1, $t2 # 按位或非
## 分支 ##
_branching:
# 分支指令的基本格式通常遵循 <指令> <寄存器1> <寄存器2> <标签>
# 如果给定的条件求值为真,则跳转到标签
# 有时向后编写条件逻辑更容易,如下面的简单的 if 语句示例所示
beq $t0, $t1, reg_eq # 如果 $t0 == $t1则跳转到 reg_eq
# 否则执行下一行
bne $t0, $t1, reg_neq # 当 $t0 != $t1 时跳转
b branch_target # 非条件分支,总会执行
beqz $t0, req_eq_zero # 当 $t0 == 0 时跳转
bnez $t0, req_neq_zero # 当 $t0 != 0 时跳转
bgt $t0, $t1, t0_gt_t1 # 当 $t0 > $t1 时跳转
bge $t0, $t1, t0_gte_t1 # 当 $t0 >= $t1 时跳转
bgtz $t0, t0_gt0 # 当 $t0 > 0 时跳转
blt $t0, $t1, t0_gt_t1 # 当 $t0 < $t1 时跳转
ble $t0, $t1, t0_gte_t1 # 当 $t0 <= $t1 时跳转
bltz $t0, t0_lt0 # 当 $t0 < 0 时跳转
slt $s0, $t0, $t1 # 当 $t0 < $t1 时结果为 $s0 1为真
# 简单的 if 语句
# if (i == j)
# f = g + h;
# f = f - i;
# 让 $s0 = f, $s1 = g, $s2 = h, $s3 = i, $s4 = j
bne $s3, $s4, L1 # if (i !=j)
add $s0, $s1, $s2 # f = g + h
L1:
sub $s0, $s0, $s3 # f = f - i
# 下面是一个求3个数的最大值的例子
# 从 Java 到 MIPS 逻辑的直接翻译:
# if (a > b)
# if (a > c)
# max = a;
# else
# max = c;
# else
# max = b;
# else
# max = c;
# 让 $s0 = a, $s1 = b, $s2 = c, $v0 = 返回寄存器
ble $s0, $s1, a_LTE_b # 如果 (a <= b) 跳转到 (a_LTE_b)
ble $s0, $s2, max_C # 如果 (a > b && a <= c) 跳转到 (max_C)
move $v0, $s0 # 否则 [a > b && a > c] max = a
j done # 跳转到程序结束
a_LTE_b: # 当 a <= b 时的标签
ble $s1, $s2, max_C # 如果 (a <= b && b <= c) 跳转到 (max_C)
move $v0, $s1 # 如果 (a <= b && b > c) max = b
j done # 跳转到 done
max_C:
move $v0, $s2 # max = c
done: # 程序结束
## 循环 ##
_loops:
# 循环的基本结构是一个退出条件和一个继续执行的跳转指令
li $t0, 0
while:
bgt $t0, 10, end_while # 当 $t0 小于 10不停迭代
addi $t0, $t0, 1 # 累加值
j while # 跳转回循环开始
end_while:
# 二维矩阵遍历
# 假设 $a0 存储整数 3 × 3 矩阵的地址
li $t0, 0 # 计数器 i
li $t1, 0 # 计数器 j
matrix_row:
bgt $t0, 3, matrix_row_end
matrix_col:
bgt $t1, 3, matrix_col_end
# 执行一些东西
addi $t1, $t1, 1 # 累加列计数器
matrix_col_end:
# 执行一些东西
addi $t0, $t0, 1
matrix_row_end:
## 函数 ##
_functions:
# 函数是可调用的过程,可以接受参数并返回所有用标签表示的值,如前所示
main: # 程序以 main 函数开始
jal return_1 # jal 会把当前程序计数器PC存储在 $ra
# 并跳转到 return_1
# 如果我们想传入参数呢?
# 首先,我们必须将形参传递给参数寄存器
li $a0, 1
li $a1, 2
jal sum # 现在我们可以调用函数了
# 递归怎么样?
# 这需要更多的工作
# 由于 jal 会自动覆盖每次调用,我们需要确保在 $ra 中保存并恢复之前的程序计数器
li $a0, 3
jal fact
li $v0, 10
syscall
# 这个函数返回1
return_1:
li $v0, 1 # 将值取到返回寄存器 $v0 中
jr $ra # 跳转回原先的程序计数器继续执行
# 有2个参数的函数
sum:
add $v0, $a0, $a1
jr $ra # 返回
# 求阶乘的递归函数
fact:
addi $sp, $sp, -8 # 在栈中分配空间
sw $s0, ($sp) # 存储保存当前数字的寄存器
sw $ra, 4($sp) # 存储先前的程序计数器
li $v0, 1 # 初始化返回值
beq $a0, 0, fact_done # 如果参数为0则完成
# 否则继续递归
move $s0, $a0 # 复制 $a0 到 $s0
sub $a0, $a0, 1
jal fact
mul $v0, $s0, $v0 # 做乘法
fact_done:
lw $s0, ($sp)
lw $ra, ($sp) # 恢复程序计数器
addi $sp, $sp, 8
jr $ra
## 宏 ##
_macros:
# 宏可以实现用单个标签替换重复的代码块,这可以增强程序的可读性
# 它们绝不是函数的替代品
# 它们必须在使用之前声明
# 用于打印换行符的宏(这可以被多次重用)
.macro println()
la $a0, newline # 存储在这里的新行字符串
li $v0, 4
syscall
.end_macro
println() # 汇编器会在运行前复制此代码块
# 参数可以通过宏传入。
# 它们由 '%' 符号表示,可以选择起任意名字
.macro print_int(%num)
li $v0, 1
lw $a0, %num
syscall
.end_macro
li $t0, 1
print_int($t0)
# 我们也可以给宏传递立即数
.macro immediates(%a, %b)
add $t0, %a, %b
.end_macro
immediates(3, 5)
# 以及标签
.macro print(%string)
la $a0, %string
li $v0, 4
syscall
.end_macro
print(hello_world)
## 数组 ##
.data
list: .word 3, 0, 1, 2, 6 # 这是一个字数组
char_arr: .asciiz "hello" # 这是一个字符数组
buffer: .space 128 # 在内存中分配块,不会自动清除
# 这些内存块彼此对齐
.text
la $s0, list # 取 list 的地址
li $t0, 0 # 计数器
li $t1, 5 # list 的长度
loop:
bgt $t0, $t1, end_loop
lw $a0, ($s0)
li $v0, 1
syscall # 打印数字
addi $s0, $s0, 4 # 字的大小为4字节
addi $t0, $t0, 1 # 累加
j loop
end_loop:
## INCLUDE ##
# 使用 include 语句可以将外部文件导入到程序中
# (它只是将文件中的代码放入 include 语句的位置)
.include "somefile.asm"
```