MIPS
Amb el nom de MIPS (Microprocessor without Interlocked Pipeline Stages) es coneix a tota una família de microprocessadors d'arquitectura RISC desenvolupats per MIPS Technologies.
Els dissenys del MIPS són utilitzats en la línia de productes informàtics de SGI, en molts sistemes encastats, en dispositius per a Windows CE, routers Cisco, i videoconsoles com la Nintendo 64 o les Sony PlayStation, PlayStation 2 i PlayStation Portable.
Les primeres arquitectures MIPS van ser implementades en 32 bits (generalment rutes de dades i registres de 32 bits d'ample), si bé versions posteriors van ser implementades en 64 bits. Existeixen cinc revisions compatibles cap enrere del conjunt d'instruccions del MIPS, anomenades MIPS I, MIPS II, MIPS III, MIPS IV i MIPS 32/64. En l'última d'elles, la MIPS 32/64 Release 2, es defineix a majors un conjunt de control de registres. Així mateix estan disponibles diverses "extensions", com ara la MIPS-3D, consistent en un simple conjunt d'instruccions SIMD en coma flotant dedicades a tasques 3D comuns, la MDMX (MaDMaX) composta per un conjunt més extens d'instruccions SIMD senceres que utilitzen els registres de coma flotant de 64 bits, la MIPS16 que afegeix compressió al flux d'instruccions per fer que els programes ocupin menys espai (presumptament com a resposta a la tecnologia de compressió Thumb de l'arquitectura ARM) o la recent MIPS MT que afegeix funcionalitats multithreading similars a la tecnologia HyperThreading dels processadors Intel Pentium 4.
A causa que els dissenyadors van crear un conjunt d'instruccions tan clar, els cursos sobre arquitectura de computadors en universitats i escoles tècniques sovint es basen en l'arquitectura MIPS. El disseny de la família de CPU MIPS influiria de manera important en altres arquitectures RISC posteriors com els DEC Alpha.
Sumario
Descripció
El processador MIPS va ser desenvolupat per a tindre un gran rendiment gràcies a la segmentació. La segmentació consisteix en dividir una instrucció en etapes i poder fer les primeres etape
Format d'instruccions
Type | -31- format (bits) -0- | |||||
---|---|---|---|---|---|---|
R | opcode (6) | rs (5) | rt (5) | rd (5) | shamt (5) | funct (6) |
I | opcode (6) | rs (5) | rt (5) | immediate (16) | ||
J | opcode (6) | address (26) |
Llenguatge
Category | Name | Instruction syntax | Meaning | Format/opcode/funct | Notes/Encoding | ||
---|---|---|---|---|---|---|---|
Arithmetic | Add | add $d,$s,$t | $d = $s + $t | R | 0 | 2016 | adds two registers, executes a trap on overflow000000ss sssttttt ddddd--- --100000 |
Add unsigned | addu $d,$s,$t | $d = $s + $t | R | 0 | 2116 | as above but ignores an overflow000000ss sssttttt ddddd--- --100001 | |
Subtract | sub $d,$s,$t | $d = $s - $t | R | 0 | 2216 | subtracts two registers, executes a trap on overflow000000ss sssttttt ddddd--- --100010 | |
Subtract unsigned | subu $d,$s,$t | $d = $s - $t | R | 0 | 2316 | as above but ignores an overflow000000ss sssttttt ddddd000 00100011 | |
Add immediate | addi $t,$s,C | $t = $s + C (signed) | I | 816 | - | Used to add sign-extended constants (and also to copy one register to another: addi $1, $2, 0), executes a trap on overflow001000ss sssttttt CCCCCCCC CCCCCCCC | |
Add immediate unsigned | addiu $t,$s,C | $t = $s + C (signed) | I | 916 | - | as above but ignores an overflow001001ss sssttttt CCCCCCCC CCCCCCCC | |
Multiply | mult $s,$t | LO = (($s * $t) << 32) >> 32; HI = ($s * $t) >> 32; |
R | 0 | 1816 | Multiplies two registers and puts the 64-bit result in two special memory spots - LO and HI. Alternatively, one could say the result of this operation is: (int HI,int LO) = (64-bit) $s * $t . See mfhi and mflo for accessing LO and HI regs. | |
Multiply unsigned | multu $s,$t | LO = (($s * $t) << 32) >> 32; HI = ($s * $t) >> 32; |
R | 0 | 1916 | Multiplies two registers and puts the 64-bit result in two special memory spots - LO and HI. Alternatively, one could say the result of this operation is: (int HI,int LO) = (64-bit) $s * $t . See mfhi and mflo for accessing LO and HI regs. | |
Divide | div $s, $t | LO = $s / $t HI = $s % $t | R | 0 | 1A16 | Divides two registers and puts the 32-bit integer result in LO and the remainder in HI.<ref name="uidaho"/> | |
Divide unsigned | divu $s, $t | LO = $s / $t HI = $s % $t | R | 0 | 1B16 | Divides two registers and puts the 32-bit integer result in LO and the remainder in HI. | |
Data Transfer | |||||||
Load word | lw $t,C($s) | $t = Memory[$s + C] | I | 2316 | - | loads the word stored from: MEM[$s+C] and the following 3 bytes. | |
Load halfword | lh $t,C($s) | $t = Memory[$s + C] (signed) | I | 2116 | - | loads the halfword stored from: MEM[$s+C] and the following byte. Sign is extended to width of register. | |
Load halfword unsigned | lhu $t,C($s) | $t = Memory[$s + C] (unsigned) | I | 2516 | - | As above without sign extension. | |
Load byte | lb $t,C($s) | $t = Memory[$s + C] (signed) | I | 2016 | - | loads the byte stored from: MEM[$s+C]. | |
Load byte unsigned | lbu $t,C($s) | $t = Memory[$s + C] (unsigned) | I | 2416 | - | As above without sign extension. | |
Store word | sw $t,C($s) | Memory[$s + C] = $t | I | 2B16 | - | stores a word into: MEM[$s+C] and the following 3 bytes. The order of the operands is a large source of confusion. | |
Store half | sh $t,C($s) | Memory[$s + C] = $t | I | 2916 | - | stores the least-significant 16-bit of a register (a halfword) into: MEM[$s+C]. | |
Store byte | sb $t,C($s) | Memory[$s + C] = $t | I | 2816 | - | stores the least-significant 8-bit of a register (a byte) into: MEM[$s+C]. | |
Load upper immediate | lui $t,C | $t = C << 16 | I | F16 | - | loads a 16-bit immediate operand into the upper 16-bits of the register specified. Maximum value of constant is 216-1 | |
Move from high | mfhi $d | $d = HI | R | 0 | 1016 | Moves a value from HI to a register. Do not use a multiply or a divide instruction within two instructions of mfhi (that action is undefined because of the MIPS pipeline). | |
Move from low | mflo $d | $d = LO | R | 0 | 1216 | Moves a value from LO to a register. Do not use a multiply or a divide instruction within two instructions of mflo (that action is undefined because of the MIPS pipeline). | |
Move from Control Register | mfcZ $t, $d | $t = Coprocessor[Z].ControlRegister[$d] | R | 0 | Moves a 4 byte value from Coprocessor Z Control register to a general purpose register. Sign extension. | ||
Move to Control Register | mtcZ $t, $d | Coprocessor[Z].ControlRegister[$d] = $t | R | 0 | Moves a 4 byte value from a general purpose register to a Coprocessor Z Control register. Sign extension. | ||
Logical | And | and $d,$s,$t | $d = $s & $t | R | 0 | 2416 | Bitwise and000000ss sssttttt ddddd--- --100100 |
And immediate | andi $t,$s,C | $t = $s & C | I | C16 | - | Leftmost 16 bits are padded with 0's001100ss sssttttt CCCCCCCC CCCCCCCC | |
Or | or $d,$s,$t | $d = $s | $t | R | 0 | 2516 | Bitwise or | |
Or immediate | ori $t,$s,C | $t = $s | C | I | D16 | - | Leftmost 16 bits are padded with 0's | |
Exclusive or | xor $d,$s,$t | $d = $s ^ $t | R | 0 | 2616 | ||
Nor | nor $d,$s,$t | $d = ~ ($s | $t) | R | 0 | 2716 | Bitwise nor | |
Set on less than | slt $d,$s,$t | $d = ($s < $t) | R | 0 | 2A16 | Tests if one register is less than another. | |
Set on less than immediate | slti $t,$s,C | $t = ($s < C) | I | A16 | - | Tests if one register is less than a constant. | |
Bitwise Shift | Shift left logical | sll $d,$t,shamt | $d = $t << shamt | R | 0 | 0 | shifts shamt number of bits to the left (multiplies by <math>2^{shamt} </math>) |
Shift right logical | srl $d,$t,shamt | $d = $t >> shamt | R | 0 | 216 | shifts shamt number of bits to the right - zeros are shifted in (divides by <math>2^{shamt} </math>). Note that this instruction only works as division of a two's complement number if the value is positive. | |
Shift right arithmetic | sra $d,$t,shamt | <math>\scriptstyle $d = $t >> shamt + \left(\left(\sum_{n=1}^{\text{shamt}}2^{32-n}\right)\cdot \left($t>>31\right)\right) </math> | R | 0 | 316 | shifts shamt number of bits - the sign bit is shifted in (divides 2's complement number by <math>2^{shamt} </math>) | |
Conditional branch | Branch on equal | beq $s,$t,C | if ($s == $t) go to PC+4+4*C | I | 416 | - | Goes to the instruction at the specified address if two registers are equal.000100ss sssttttt CCCCCCCC CCCCCCCC |
Branch on not equal | bne $s,$t,C | if ($s != $t) go to PC+4+4*C | I | 516 | - | Goes to the instruction at the specified address if two registers are not equal. | |
Unconditional jump | Jump | j C | PC = PC+4[31:28] . C*4 | J | 216 | - | Unconditionally jumps to the instruction at the specified address. |
Jump register | jr $s | goto address $s | R | 0 | 816 | Jumps to the address contained in the specified register | |
Jump and link | jal C | $31 = PC + 4; PC = PC+4[31:28] . C*4 | J | 316 | - | For procedure call - used to call a subroutine, $31 holds the return address; returning from a subroutine is done by: jr $31. Return address is PC + 8, not PC + 4 due to the use of a branch delay slot which forces the instruction after the jump to be executed |
Note: In MIPS assembly code, the offset for branching instructions can be represented by a label elsewhere in the code.
Note: There is no corresponding load lower immediate instruction; this can be done by using addi (add immediate, see below) or ori (or immediate) with the register $0 (whose value is always zero). For example, both addi $1, $0, 100
and ori $1, $0, 100
load the decimal value 100 into register $1.
Note: Subtracting an immediate can be done with adding the negation of that value as the immediate.
MIPS has 32 floating-point registers. Two registers are paired for double precision numbers. Odd numbered registers cannot be used for arithmetic or branching, just as part of a double precision register pair.
Category | Name | Instruction syntax | Meaning | Format/opcode/funct | Notes/Encoding | ||
---|---|---|---|---|---|---|---|
Arithmetic | FP add single | add.s $x,$y,$z | $x = $y + $z | Floating-Point add (single precision) | |||
FP subtract single | sub.s $x,$y,$z | $x = $y - $z | Floating-Point subtract (single precision) | ||||
FP multiply single | mul.s $x,$y,$z | $x = $y * $z | Floating-Point multiply (single precision) | ||||
FP divide single | div.s $x,$y,$z | $x = $y / $z | Floating-Point divide (single precision) | ||||
FP add double | add.d $x,$y,$z | $x = $y + $z | Floating-Point add (double precision) | ||||
FP subtract double | sub.d $x,$y,$z | $x = $y - $z | Floating-Point subtract (double precision) | ||||
FP multiply double | mul.d $x,$y,$z | $x = $y * $z | Floating-Point multiply (double precision) | ||||
FP divide double | div.d $x,$y,$z | $x = $y / $z | Floating-Point divide (double precision) | ||||
Data Transfer | Load word coprocessor | lwcZ $x,CONST ($y) | Coprocessor[Z].DataRegister[$x] = Memory[$y + CONST] | I | Loads the 4 byte word stored from: MEM[$y+CONST] into a Coprocessor data register. Sign extension. | ||
Store word coprocessor | swcZ $x,CONST ($y) | Memory[$y + CONST] = Coprocessor[Z].DataRegister[$x] | I | Stores the 4 byte word held by a Coprocessor data register into: MEM[$y+CONST]. Sign extension. | |||
Logical | FP compare single (eq,ne,lt,le,gt,ge) | c.lt.s $f2,$f4 | if ($f2 < $f4) cond=1; else cond=0 | Floating-point compare less than single precision | |||
FP compare double (eq,ne,lt,le,gt,ge) | c.lt.d $f2,$f4 | if ($f2 < $f4) cond=1; else cond=0 | Floating-point compare less than double precision | ||||
Branch | branch on FP true | bc1t 100 | if (cond == 1) go to PC+4+100 | PC relative branch if FP condition | |||
branch on FP false | bc1f 100 | if (cond == 0) go to PC+4+100 | PC relative branch if not condition |
These instructions are accepted by the MIPS assembler, although they are not real instructions within the MIPS instruction set. Instead, the assembler translates them into sequences of real instructions.
Name | instruction syntax | Real instruction translation | meaning |
---|---|---|---|
Move | move $rt,$rs | addi $rt,$rs,0 | R[rt]=R[rs] |
Clear | clear $rt | add $rt,$zero,$zero | R[rt]=0 |
Not | not $rt, $rs | nor $rt, $rs, $zero | R[rt]=~R[rs] |
Load Address | la $rd, LabelAddr | lui $rd, LabelAddr[31:16]; ori $rd,$rd, LabelAddr[15:0] | $rd = Label Address |
Load Immediate | li $rd, IMMED[31:0] | lui $rd, IMMED[31:16]; ori $rd,$rd, IMMED[15:0] | $rd = 32 bit Immediate value |
Branch unconditionally | b Label | beq $zero,$zero,Label | if(R[rs]==R[rt]) PC=Label |
Branch and link | bal $rs,Label | bgezal $zero,Label | if(R[rs]>=0) PC=Label |
Branch if greater than | bgt $rs,$rt,Label | slt $at,$rt,$rs; bne $at,$zero,Label | if(R[rs]>R[rt]) PC=Label |
Branch if less than | blt $rs,$rt,Label | slt $at,$rs,$rt; bne $at,$zero,Label | if(R[rs]<R[rt]) PC=Label |
Branch if greater than or equal | bge $rs,$rt,Label | slt $at,$rs,$rt; beq $at,$zero,Label | if(R[rs]>=R[rt]) PC=Label |
Branch if less than or equal | ble $rs,$rt,Label | slt $at,$rt,$rs; beq $at,$zero,Label | if(R[rs]<=R[rt]) PC=Label |
Branch if greater than unsigned | bgtu $rs,$rt,Label | if(R[rs]>R[rt]) PC=Label | |
Branch if greater than zero | bgtz $rs,Label | if(R[rs]>0) PC=Label | |
Branch if equal to zero | beqz $rs,Label | if(R[rs]==0) PC=Label | |
Multiplies and returns only first 32 bits | mul $d, $s, $t | mult $s, $t; mflo $d | $d = $s * $t |
Divides and returns quotient | div $d, $s, $t | div $s, $t; mflo $d | $d = $s / $t |
Divides and returns remainder | rem $d, $s, $t | div $s, $t; mfhi $d | $d = $s % $t |
The hardware architecture specifies that:
- General purpose register $0 always returns a value of 0.
- General purpose register $31 is used as the link register for jump and link instructions.
- HI and LO are used to access the multiplier/divider results, accessed by the mfhi (move from high) and mflo commands.
These are the only hardware restrictions on the usage of the general purpose registers.
The various MIPS tool-chains implement specific calling conventions that further restrict how the registers are used. These calling conventions are totally maintained by the tool-chain software and are not required by the hardware.
Name | Number | Use | Callee must preserve? |
---|---|---|---|
$zero | $0 | constant 0 | Plantilla:N/A |
$at | $1 | assembler temporary | Plantilla:No |
$v0–$v1 | $2–$3 | values for function returns and expression evaluation | Plantilla:No |
$a0–$a3 | $4–$7 | function arguments | Plantilla:No |
$t0–$t7 | $8–$15 | temporaries | Plantilla:No |
$s0–$s7 | $16–$23 | saved temporaries | Plantilla:Yes |
$t8–$t9 | $24–$25 | temporaries | Plantilla:No |
$k0–$k1 | $26–$27 | reserved for OS kernel | Plantilla:N/A |
$gp | $28 | global pointer | Plantilla:Yes |
$sp | $29 | stack pointer | Plantilla:Yes |
$fp | $30 | frame pointer | Plantilla:Yes |
$ra | $31 | return address | Plantilla:N/A |
Name | Number | Use | Callee must preserve? |
---|---|---|---|
$zero | $0 | constant 0 | Plantilla:N/A |
$at | $1 | assembler temporary | Plantilla:No |
$v0–$v1 | $2–$3 | values for function returns and expression evaluation | Plantilla:No |
$a0–$a7 | $4–$11 | function arguments | Plantilla:No |
$t4–$t7 | $12–$15 | temporaries | Plantilla:No |
$s0–$s7 | $16–$23 | saved temporaries | Plantilla:Yes |
$t8–$t9 | $24–$25 | temporaries | Plantilla:No |
$k0–$k1 | $26–$27 | reserved for OS kernel | Plantilla:N/A |
$gp | $28 | global pointer | Plantilla:Yes |
$sp | $29 | stack pointer | Plantilla:Yes |
$s8 | $30 | frame pointer | Plantilla:Yes |
$ra | $31 | return address | Plantilla:N/A |
Registers that are preserved across a call are registers that (by convention) will not be changed by a system call or procedure (function) call. For example, $s-registers must be saved to the stack by a procedure that needs to use them, and $sp and $fp are always incremented by constants, and decremented back after the procedure is done with them (and the memory they point to). By contrast, $ra is changed automatically by any normal function call (ones that use jal), and $t-registers must be saved by the program before any procedure call (if the program needs the values inside them after the call).
Exemples d'assemblador per a MIPS
Aquests exemples estan fets per a funcionar en el simulador Simula3ms. És possible que en altres simuladors no siga exàctament aquesta sintaxi.
Hola Mundo:
.data hola: .asciiz "hola mundo" .text .globl main main: la $a0, hola #$a0=dirección de memoria donde empieza "cadena" li $v0, 4 #Cargamos el código de operación de impresión #de cadenas de texto syscall #print_string
Traure la mitjana aritmètica de tres números en registres:
.text .globl main main: addi $t1,$0,20 addi $t2,$0,30 addi $t3,$0,40 add $t4,$0,$t1 add $t4,$t4,$t2 add $t4,$t4,$t3 addi $t5,$t5,3 div $t4,$t5 mflo $t6
El bucles
Sumar els elements d'un array de memòria:
.data array: .word 10,20,30,40,50 count: .word 5 .text .globl main main: la $t0,array la $t1, count lw $t2, 0($t1) addi $t5,$0,0 loop: addi $t2,$t2,-1 lw $t3, 0($t0) add $t5,$t5,$t3 addi $t0,$t0,4 bne $t2,$0,loop
L'etiqueta loop: serveix per a indicar la direcció de memòria de l'instrucció següent per a que l'assemblador la canvie en l'ordre bne. Aquest bucle va restant 1 al registre $t2. A continuació carrega de memòria en el registre 3 el contingut de la direcció indicada en $t0. Fa la suma del número obtés, incrementa en 4 la direcció de memòria (4 bytes 32 bits, l'ample de paraula). L'ordre bne mira si $t2 ja és 0, en cas contrari torna a loop:.
En el cas anterior es tracta d'un bucle amb un comptador, molt paregut a un bluce for. Un altre tipus de bucle, el While, es pot implementar de la següent manera:
.data array: .word 10,20,30,40,50,0 .text .globl main main: la $t0,array addi $t5,$0,0 loop: lw $t3, 0($t0) add $t5,$t5,$t3 addi $t0,$t0,4 bne $t3,$0,loop
Observem que necessitem una condició de final. En aquest cas és arribar al 0.
Les subrutines
Per a fer una funció, cal guardar la direcció a la que s'ha de tornar. Com que la noció de funció és de llenguatge d'alt nivell, cal dir subrutina.
- Una subrutina a de poder ser cridada de qualsevol part.
- Després ha de saber tornar al flux del programa.
En els lleguatges d'alt nivell, les funcions tenen les seues variables locals i són independents d'altres cridades a la mateixa funció. No obstant, els processadors tenen registres que actuen com a variables globals.
MIPS designa els registres $s0-$s7 com a registres save. El que crida a la funció a de cuidar aquests registres. No ho fa automàticament el processador, és sols una convenció entre programadors de MIPS. Qualsevol funció pot cridar a altres de manera recursiva. Això fa que no siga segur confiar en que aquests registres no van a ser sobreescrits. Per tant, sempre que es crida a una funció cal:
- Determinar els registres protegits que vols utilitzar.
- Posar (Push) els registres en la pila.
- Utilitzar els registres protegits en la subrutina.
- Extraure (Pop) els registres protegits de la pila.
- Retornar a la subrutina que l'ha cridat.
La pila
Com que els registres són un recurs compartit, les funcions han de guardar les seues dades a un lloc no accessible per les subrutines. Aquest és la pila. Hem d'assumir que cada subrutina utilitza una part de la pila i no ha d'accedir a la resta.
La pila se guarda en memòria principal junt en les instruccions i altres dades estàtiques o el montícul (heap).
+---------------+ | Stack | | | | | v | +---------------+ : : +---------------+ | ^ | | | | | Heap | +---------------+ | Data | +---------------+ | Code | +---------------+
La pila és una estructura de dades especial que té una direcció de memòria que apunta a l'últim element. Podem pensar en la pila com en una pila de plats en el que l'últim en amuntonar-se és el primer en ser agafat de nou.
En MIPS, la pila creix en direccions de memòria descendents.
+-------------+ | | +-------------+ | | +-------------+ | | +-------------+ $sp ------------> | | +-------------+ | *********** | +-------------+ | *********** | +-------------+
Per a posar una paraula en la pila (Push):
sub $sp,$sp,4 li $s0,0x12345678 sw $s0,4($sp)
És a dir, reduir en 4 (32 bits) la direccions de la pila, guardar una paraula literal en un registre i guardar-lo en la pila.
Per a llevar una paraula de la pila (Pop):
lw $s0,4($sp) add $sp,$sp,4
Llegir la direcció de la pila més 4 i augmentar en 4 la direcció. D'aquesta manera, la pila es redueix en una paraula (recordem que les direccions van de més a menys.)
No tots els processadors tenen les mateixes convencions per a programar les subrutines. Així es fa en MIPS:
- El procés fica els arguments de la funció en els registres $a0 a $a3. Sols 4 arguments passats com a valor o referència.
- El retorn de la funció es guarda en els registre $v0 i $v1.
- La subrutina reserva espai en la pila per a guardar els seus registres per si crida a una alta subrutina.
- Quant la subrutina acaba, fica el punter de la pila a la posició en la que estava abans.
- El resultat està en $v0, si el procés ha de cridar a un altra subrutina ha de guardar el resultat en la seua pila.
La subrutina sols accedeix a la seua part de la pila. Poden haver excepcions com quant es passen arguments per referència.
El que passa quant cridem a una subrutina
Imaginem que tenim aquest fragment de codi:
.... | # Set up arguments 1000 | j QUICKSORT # Call QUICKSORT .... | .... .... | .... 2000 | QUICKSORT: # Code for quicksort
Després de guardar els arguments es crida en una instrucció j a la subrutina que està en la direcció 2000. S'executa la rubrutina i ha de tornar. Però no sap tornar perquè no coneix la direcció des de la qual s'ha cridat.
Per a solucionar això tenim la instrucció jal. Que significa "jump-and-link". Aquesta bota a la direcció de la subrutina però guarda la direcció de tornada al registre $r31 o $ra que és la direcció de retorn.
.... | # Set up arguments 1000 | jal QUICKSORT # Call QUICKSORT .... | .... .... | .... 2000 | QUICKSORT: # Code for quicksort
La direcció guardada en $r31 serà 1004, ja que és la següent.
Per a tornar, s'utilitzarà l'instrucció jr:
2000 | QUICKSORT: # Code for quicksort .... | ..... .... | ..... 20ff | jr $ra # Return back
Per determinar què guardar en la pila, podem seguir aquest raonament:
- Si la subrutina no té ninguna instrucció jal. És a dir, no crida a altres, no cal guardar res en la pila, sols els $s0-$s7.
- Si té un jal, has de pensar quins registres salvar en la pila, al menys el $r31.
- Una vegada vas a eixir de la subrutina, has de restaurar el registre de retorn i la posició de la pila.
Observa en el següent exemple la necessitat de guardar el registre de retorn:
FOO: .... .... jal BAR move $t0, $v0 # Save return value to t0 .... jal CAT add $v0, $v0, $t0 # Uh oh! Error! $t0 may have changed!
Si llegim açò com un programador, pareix que tot està bé: Cridem a la subrutina BAR i guardem el resultat ($v0) en un registre normal, per exemple $t0. A continuació cridem a CAT i després utilitzem el $t0 i el resultat de CAT en $v0 per a fer una suma. Tot correcte. Però ningú en garanteix que $t0 serà el mateix, ja que la subrutina CAT pot modificar el registres. La única solució és guardar-lo en la pila.
Solució:
FOO: .... .... jal BAR sw $v0, 4($sp) # Save return value to stack .... jal CAT sw $t0, 4($sp) # Get old return value from stack add $v0, $v0, $t0 # Fixed problem!
Al guardar en la pila, el valor queda salvat. Amés, les subrutines s'han encarregat de modificar la pila adequadament per a que al finalitzar apunte al mateix punt.
Exemples de funcions:
Exemple molt simple:
.text main: li $t0, 1 jal procedure # call procedure li $v0, 10 syscall procedure: li $t0, 3 jr $ra # return
Instruccions:
jal displ r31 <- return address; pc <- target address jalr rs,rd rd <- return address (rd defaults to r31); pc <- contents of register rs jr rs pc <- contents of register rs
Estructura de subrutina:
subr: ! prolog: subu $sp,$sp,32 ! adjust stack frame sw $ra,20($sp) ! save return address sw $fp,16($sp) ! save caller's fp addu $fp,$sp,28 ! set up new fp sw $a0,0($fp) ! save first parameter in stack ... body of subroutine ... ! epilog: lw $ra,20($sp) ! restore return address lw $fp,16($sp) ! restore caller's fp addu $sp,$sp,32 ! restore caller's sp jr $ra ! return
Exemple de subrutina guardant els registres i restaurant-los:
jumbo: sub $sp,$sp,4 # push ra sw $ra,4($sp) sub $sp,$sp,4 # save $s1 sw $s1,4($sp) sub $sp,$sp,4 # save $s2 sw $s2,4($sp) ... # do work lw $s2,4($sp) # restore $s2 add $sp,$sp,4 lw $s1,4($sp) # restore $s1 add $sp,$sp,4 lw $ra,($sp) # pop ra add $sp,$sp,4 jr $ra
Exemple de la mateixa subrutina més eficient:
jumbo: sub $sp,$sp,12 sw $ra,12($sp) # push ra sw $s1,8($sp) # save s1 sw $s2,4($sp) # save s2 ... lw $s2,4($sp) # restore $s2 lw $s1,8($sp) # restore $s1 lw $ra,12($sp) # pop ra add $sp,$sp,12 jr $ra
Per passar paràmetres de manera simple:
# $a0-$a3 és per passar paràmetres # $v0 i $v1 és per a retornar. average: add $t0,$a0,$a1 div $v0,$t0,2 jr $ra
Per passar paràmetres de manera general:
Si volem passar més de 4 registres, necessitem utilitzar la pila:
# average the values in $s0 and $s1, put the # result in $s2 sub $sp,$sp,12 # space for rslt. sw $s0,8($sp) # push 1st param sw $s1,12($sp) # push 2nd param jal average lw $s2,4($sp) # get result add $sp,$sp,12 done average: sub $sp,$sp,4 sw $ra,4($sp) lw $t0,12($sp) # load 1st param lw $t1,16($sp) # load 2nd param add $t0,$t0,$t1 div $t0,$t0,2 sw $t0,8($sp) # store result lw $ra,4($sp) # pop ra add $sp,$sp,4 jr $ra # return
Per passar paràmetres per referència:
# t0: pointer incremented thru array # t1: address of end of array # t2: value fill: sub $sp,$sp,4 # push ra sw $ra,4($sp) lw $t0,16($sp) # get start array lw $t1,12($sp) # get len. (elts) mul $t1,$t1,4 # len. in bytes add $t1,$t1,$t0 # end addr. lw $t2,8($sp) # get value loop: bgt $t0,$t1,done sw $t2,($t0) # asgn val to elt add $t0,$t0,4 # incr. pointer b loop done: lw $ra,4($sp) add $sp,$sp,4 jr $ra