This program is meant to replace all the lowercase letters in a string with the character *
.
The problem I am having is in the nested call of subroutines. I.e. some same $t
and $a
registers are being used in different subroutines. So, when a subroutine is called in another subroutine, the caller subroutine's registers gets messed up.
.data
str: .asciiz "WindOnTheHill"
.text
la $a0, str # start of the string
li $a1, '*'
jal ReplaceAllLower
#la $a0, str # start of the string
jal PrintStr
jal Exit
ReplaceAllLower:
# backup return address
addi $sp, $sp, -12 # create space for 3 words
# (3*4=12 bytes) on the stack
# (push) for $ra
sw $ra, 0($sp) # backup return address $ra
# protect arguments from change
sw $a0, 4($sp) # backup string address
sw $a1, 8($sp) # backup char
# get string length
jal StrLen # obtain string length
move $t0, $v0 # backup string length
# retrieve argument values
lw $a1, 8($sp) # restore char
lw $a0, 4($sp) # restore string address
move $t1, $a0 # obtain string address
move $t2, $a1 # obtain char
li $t3, 0 # loop counter
while:
bgt $t3, $t0, end_while
jal IsLower
beq $t0, 1, lower_case
j not_lower_case
lower_case:
sb $t2, ($a0)
not_lower_case:
addi $a0, $a0, 1 # increment address
addi $t3, $t3, 1 # increment loop counter
j while
end_while:
move $a0, $t1
# restore stack
lw $ra, 0($sp) # restore $ra
addi $sp, $sp, 16 # return the space on the stack(pop)
# return
jr $ra
IsLower:
lb $t0, ($a0) # obtain the character
li $t1, 97 # 'a' - character
li $t2, 122 # 'z' - character
bge $t0, $t1, con1_fulfilled #bigger tha or equal to 0
j con1_not_fulfilled
con1_fulfilled:
ble $t0, $t2, con2_fullfilled #less than or equal to 9
j con2_not_fulfilled
con2_fullfilled:
li $v0, 1
j return
con1_not_fulfilled:
con2_not_fulfilled:
li $v0, 0
return:
# return
jr $ra
StrLen:
move $a1, $a0 # start of string
# run a loop
li $t0, '\0' # null character
li $t1, 0 # prepare the counter
start_loop:
lb $v0, ($a0) # obtain the 1st character
beq $v0, $t0, end_loop # exit loop if '\0'-char found
addi $t1, $t1, 1 # increment counter
addi $a0, $a0, 1 # increment address
j start_loop # iterate again
end_loop:
move $a0, $a1 #restore string address
move $v0, $t1 # return value
# return
jr $ra
PrintStr:
li $v0, 4
syscall
# return
jr $ra
Exit:
# push $s0 on stack
addi $sp, $sp, -4 # create 4-bytes on the stack
sw $s0, ($sp) # cpy $s0 to stack
#terminate program
li $v0, 10
syscall
# free stack
addi $sp, $sp, 4
# return
jr $ra
Note: Let us not concentrate on the algorithm at this moment.
So, my question is,
What technique should I use to get rid of this issue, as it is very hard to know beforehand which subroutine is going to be called in which in future (a library can expand itself over time)?
Some conventions are required in such a way that if all subroutines respect them, there will not be any problem such as a register being clobbered by a called procedure.
For the MIPS, the generally accepted calling conventions are that:
* registers $t0-7
are "temporary" and can be used without precautions. If a procedure wants to preserve some of them across function calls, it is its responsibility to save them ("caller saved").
* registers $s0-7
("saved registers") cannot be used without precaution. If a procedure wants to use some of them, it must preserve them before use and restore their value upon return ("callee saved")
There are other important aspects in the calling conventions, such as passing the first arguments in registers $a0-$a3
, using $v0-$v1
for the return value, etc. They also precise the role of some registers such a the stack pointer (sp
) or the frame pointer (fp
). this document is a very good summary, but you can easily find extra details on internet.
Preserving registers is done with a call stack. It is a data structure that holds all preserved information.
At the start of the function, some space must be reserved in the stack for all information that need to be preserved. Then registers s0-s7
that are going to be used are saved in the stack. If the function is non terminal (ie calls another function), the returned address is also saved.
Before calling a function, temporary or argument registers ($t0-7
or $a0-3
) that need to be saved are written to the stack. Arguments are written to registers $a0-3
or stacked if required. And the function is called.
After the called function return, preserved temporary registers are restored.
And before the function returns, one needs to restore saved $s0-7
registers and the return address register ($ra
), stack space is freed and one calls jr $ra
.
If all procedures respect these calling conventions, there will not be any problem. Compilers respect these conventions, but they are dependent on OS and architectures.