diff options
Diffstat (limited to 'zh-cn/mips-cn.html.markdown')
| -rw-r--r-- | zh-cn/mips-cn.html.markdown | 334 | 
1 files changed, 334 insertions, 0 deletions
| diff --git a/zh-cn/mips-cn.html.markdown b/zh-cn/mips-cn.html.markdown new file mode 100644 index 00000000..83888338 --- /dev/null +++ b/zh-cn/mips-cn.html.markdown @@ -0,0 +1,334 @@ +--- +language: "MIPS Assembly" +filename: MIPS-cn.asm +contributors: +    - ["Stanley Lim", "https://github.com/Spiderpig86"] +translators: +    - ["Liu Yihua", "https://github.com/yihuajack"] +lang: zh-cn +--- + +MIPS(Microprocessor 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, $s1                           # 否则 [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" + +``` | 
