#assembly #mips #subroutine #mars-simulator
#сборка #mips #подпрограмма #mars-симулятор
Вопрос:
Эта программа предназначена для замены всех строчных букв в строке символом *
.
Проблема, с которой я сталкиваюсь, заключается во вложенном вызове подпрограмм. Т.Е. некоторые одинаковые регистры $t
и $a
используются в разных подпрограммах. Итак, когда подпрограмма вызывается в другой подпрограмме, регистры вызывающей подпрограммы перепутываются.
.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, '' # 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 ''-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
Примечание: Давайте не будем концентрироваться на алгоритме в данный момент.
Итак, мой вопрос в том,
какую технику мне следует использовать, чтобы избавиться от этой проблемы, поскольку очень сложно заранее знать, какая подпрограмма будет вызываться в какой в будущем (библиотека может расширяться со временем)?
Комментарии:
1. Как правило, платформа (ОС/оборудования) будет определять в приложение Б inary я минимальная подписка, которая включает в себя указание о том, какие регистры сохраняются вызовы подпрограмм, и какие регистры являются бесплатными для подпрограмм изменения без восстановления. Обычно регистры, которые необходимо сохранить, копируются в стек , который увеличивается / сжимается в порядке FIFO, откуда их можно восстановить. Важно отметить, что регистр указателя стека сохраняется вызываемым пользователем (сохраняется во всех вызовах).
Ответ №1:
Некоторые соглашения требуются таким образом, что если все подпрограммы их соблюдают, не возникнет никаких проблем, таких как переполнение регистра вызываемой процедурой.
Для MIPS общепринятыми соглашениями о вызовах являются следующие:
* регистры $t0-7
являются «временными» и могут использоваться без мер предосторожности. Если процедура хочет сохранить некоторые из них при вызовах функций, она несет ответственность за их сохранение («сохраненный вызывающий»).
* регистры $s0-7
(«сохраненные регистры») нельзя использовать без предосторожности. Если процедура хочет использовать некоторые из них, она должна сохранить их перед использованием и восстановить их значение по возвращении («вызываемый сохранен»)
В соглашениях о вызовах есть другие важные аспекты, такие как передача первых аргументов в регистрах $a0-$a3
, использование $v0-$v1
для возвращаемого значения и т.д. Они также уточняют роль некоторых регистров, таких как указатель стека ( sp
) или указатель фрейма ( fp
). этот документ представляет собой очень хорошее резюме, но вы можете легко найти дополнительные подробности в Интернете.
Сохранение регистров выполняется с помощью стека вызовов. Это структура данных, которая содержит всю сохраненную информацию.
При запуске функции в стеке должно быть зарезервировано некоторое пространство для всей информации, которую необходимо сохранить. Затем регистры s0-s7
, которые будут использоваться, сохраняются в стеке. Если функция не является терминальной (т.Е. вызывает другую функцию), возвращаемый адрес также сохраняется.
Перед вызовом функции в стек записываются временные регистры или регистры аргументов ( $t0-7
или $a0-3
), которые необходимо сохранить. Аргументы записываются в регистры $a0-3
или складываются, если требуется. И функция вызывается.
После возврата вызванной функции сохраненные временные регистры восстанавливаются.
И прежде чем функция вернется, нужно восстановить сохраненные $s0-7
регистры и регистр обратного адреса ( $ra
), пространство стека освобождается и вызывается jr $ra
.
Если все процедуры соблюдают эти соглашения о вызовах, проблем не возникнет. Компиляторы соблюдают эти соглашения, но они зависят от ОС и архитектур.