【MIPS】经典指令块集锦

Directives声明变量值存储

容易将数据段地址和地址上的内容搞混

.data
fibs: .space 48	# allocate 12 * 4 = 48 Byte memory, store first address in label "fibs"
size: .word 12	# allocate 1 Byte(a word) for 12, store address in label "size"

.text	# want to load the value of "size" (12) to $t5
la $t5, size	# 1. load the address of "size" to $t5, for we could only use the label
lw $t5, 0($t5)	# 2. load the value of "size" to $t5

需厘清:寄存器编号(地址);寄存器值;内存地址;内存段值
程序操纵寄存器,寄存器操纵内存地址
表示地址:1.立即数(+标签) 2.寄存器取值

二维数组使用

标准

Arr: .space 800

.macro getindex(%ans, %i, %j, %rank)  # for Arr[][rank]
	mult %i, %rank 
	mflo %ans
	add %ans, %ans, %j
	sll %ans, %ans, 2
.end_macro

.text
li $t0, 4
li $t1, 0
li $t2, 8
getindex($t3, $t0, $t1, $t2)
lw $s0, Arr($t3)		# load Arr[4][0] to $s0
sw $s0, Arr($t3)		# store $s0 to Arr[4][0]

一些存储带来的简化

.data
Arr: .space 800			# Arr[25][8]: 25 * 8 * 4Byte
				# cols最好用2的幂,方便sll操作,不用mult
.macro getindex_8(%ans, %i, %j)	# calculate (i*8+j)*4
	sll %ans, %i, 3		# i << 3
	add %ans, %ans, %j	
	sll %ansm %ans, 2	
.end_macro
# never manipulate the value of %i, %j , which prevent the risk of mischanging other registers when using the macro
# thus "getindex($t0, $t1, $t1)" is safe to both the result and $t1
# 模块化维护某些规范,便无需在其他地方多虑。寄存器使用惯例、函数栈的意义在此。

这里将标签的首地址立即数直接作为offset,将感觉上的偏移作为base,效果不错。
不过是不是还得小心位数,address:32bit 而 offset:16bit ?是只传了低 16bit 吗

循环

for循环 for(int i=0;i<n;++i)

li $t0, 0                    # int i=0 ($s0 store the value of n)
for_i_begin:
    slt $t1, $t0, $s0        # i < n ? 1 : 0  can be changed to other comparing instruction
    beqz $t1, for_i_end      # ATTENTION: please match the comparing instruction , easy to mix up

    # other instruction

    addi $t0, $t0, 1         # ++i
    j for_i_begin            # continue loop
for_i_end:

函数调用

约定,类似$t0$t1等寄存器记作$t

所谓$t由父函数维护,$s由子函数维护。
$s为正当的“存储寄存器”,因而当前父函数中不应操心,由其内部子函数维护该寄存器存储的效果;$t为正当的“临时寄存器”,本不应该存储数据,因而子函数不应操心可以随便用,若想要存储则应由当前父函数自己操心)

  • 预处理
    1. .data栈空间申请
    2. $sp指向栈顶
  • 父函数调用子函数
    1. 若有需要维护的变量,应事先在函数头处分为该父函数分配栈帧
    2. 传出参数: 在$a0$a1等寄存器(称为$a,后同)处加载需传出的数据
    3. 维护变量-前: 考虑该父函数中需要保留的$t$ra等寄存器,逐条载入栈帧中
    4. 调用函数jal function
    5. 维护变量-后: 将之前于栈帧中存好的数据逐条载回$ra$t等寄存器
    6. 接收返回值: 从$v中取回传回的数据
  • 子函数被调用
    1. 根据函数所需,为该子函数分配栈帧
    2. 维护变量-前: 考虑需要用到的$s寄存器,逐条载入栈帧中
    3. 取出传入参数: 将$a中传入的参数存到自己需要的地方
    4. 子函数内容
    5. 传回返回值: 将返回值载入$v
    6. 维护变量-后: 将之前于栈帧中存好的数据逐条载回$t寄存器

示例:调用int son_function(int i,int j){}

.data
stack: .space 300

.text
la $sp, stack
addiu $sp, $sp, 300

# ......

father_function:
addiu $sp, $sp, -12

    # using registers for operation
    # registers $s0, $s1 are used to store partial variables
    # registers $t0, $t1 are still not used up

    move $a0, $t2
    move $a1, $t3

    sw $t1, 8($sp)
    sw $t0, 4($sp)
    sw $ra, 0($sp)  # not necessary, depending on son_function
    jal son_funtion
    lw $ra, 0($sp)
    lw $t0, 4($sp)
    lw $t1, 8($sp)

addiu $sp, $sp, 12
jr $ra

# in son_function, 
son_function:
addiu $sp, $sp, -8
sw $s1, 4
sw $s0, 0
move $t0, $a0
move $t1, $a1
    
    # using registers for operation
    # finally get return_value in $s1
    # suppose no other function calling -- leaf function. 
    # (needn't have saved $ra in father function, just lazy to delete)

move $v0, $s1
lw $s0, 0
lw $s1, 4
addiu $sp, $sp, 8
jr $ra

跳转条件判断

比较跳转指令

bgt $t0, $t1, label/bgt $t0, 100, label均可,第二个可为reg、16位imm、32位imm

指令 跳转条件 拆分
bgt > slt+bne
bge >= slt+beq
blt <
ble <=
beq == /
bne != /

指令后加z便是将$t1变为0

跳转条件书写

if(exp)

1.顺逻辑

slt sle sgt sge 判断exp真值;beqz bnez 根据真值是否为零决定跳转。

跳转伪指令(pseudo instruction)多半会按这种形式拆分成基础指令,标准的逻辑思路。注意sle sge一般会转化为slt+ori+subu之类的,指令效率低,建议换

2.反逻辑

直接使用bgt bge blt ble 判断!exp真值,真了就跳转。

清爽很多,虽然指令效率和上面一样。同样少用bgeble,通过反向来消除等号

复合条件

就是短路逻辑,感觉复杂了也挺繁琐的。
增添set_1: set_0:标签来完成短路跳转好像还不错。
具体规律遇到再说。

常见Pseudo Instruction分解

posted @ 2021-10-07 21:48  Xlucidator  阅读(221)  评论(0编辑  收藏  举报