Самым серьезным нарушением является то, что func
ожидает, что $a1
переживет вызов функции до foo
- что неверно. По соглашению о вызовах $a1
является регистром аргументов и не сохраняется при вызове функции. $a1
также не является возвращаемым значением, поэтому его следует считать неинициализированным после вызова функции, т. Е. Оно содержит ненужный мусор.
func: addi $sp,$sp,-4
sw $ra, 0($sp)
addi $a0,$a0,1
addi $a1,$a1,2
jal foo # this call effectively wipes out argument registers (ok)
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0 (ok)
add $v0,$a0,$a1 # but here it uses uninitliazed $a1 (not ok)
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
Правильный способ сохранить аргумент func
s $a1
сохранить его в памяти и загрузить позже, после вызова функции; для этого потребуется дополнительное слово в кадре стека.
func: addiu $sp,$sp,-8
sw $ra, 4($sp)
addi $a0,$a0,1
addi $a1,$a1,2
sw $a1, 0($sp) # save a1 in stack before call
jal foo # this call effectively wipes out argument registers
lw $a1, 0($sp) # restore a1 from stack after call
addi $a0,$v0,4 # here the function re-initializes $a0 from return value $v0
add $v0,$a0,$a1 # now using re-initialized $a1 (ok)
lw $ra,4($sp)
addiu $sp,$sp,8
jr $ra
В приведенном выше примере я сохранил обновленный регистр $a1
, но, разумеется, из простого чтения кода не ясно действительно ли желателен исходный, без приращения $a1
или обновленный $a1
. Похоже, что $a1
передается в качестве параметра в foo, поэтому может быть, что foo
хочет увеличить значение, но позже func
не делает.
В качестве альтернативы можно сохранить $a1
в регистре $s
, поскольку они гарантированно сохраняются в вызове функции соглашением о вызовах - однако, согласно этому самому определению, $s
сами регистры, если они используются вызываемым объектом, должны быть сохранены и восстановлено, поэтому все еще потребуется дополнительное слово в кадре стека.
Другие нарушения соглашения о вызовах для MIPS заключаются в следующем:
MIPS CC требует, чтобы стековые кадры были выровнены на 8 байт, поэтому даже если вам нужно только одно слово, мы должны округлить размер стекового кадра до кратного 8 байт. Многие программисты на ассемблере игнорируют это, однако, без серьезных последствий.
MIPS CC также диктует место для 4 регистров аргументов, которые должны быть сохранены в стеке, и что это пространство обеспечивается вызывающий, чтобы вызываемый мог использовать его (не требуя какого-либо выделения стека вызывающим). Это почти никогда не делается программистами на ассемблере в простых заданиях, хотя это технически необходимо. Если вызываемый пользуется этими 4 словами, которые должны быть там, когда их нет, произойдут плохие вещи. (Я не учел это в приведенном выше решении.) Я бы минимально следовал этому требованию при написании функции, которая вызывает функцию varargs , например printf, sprintf, scanf и т. Д. c. Эта область сохранения 4 регистра аргументов соприкасаются с памятью для 5-го, 6-го и т. д. c .. аргументов, что делает весь блок параметров непрерывным в памяти, что полезно при реализации функций varargs.
https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf
Кроме того, addi
, используемый для выделения и освобождения стекового пространства, лучше, чем addiu
- поскольку это арифметика указателей c (адреса не подписаны), целочисленное переполнение со знаком в лучшем случае неинтересно, а в худшем - вредно (вызывая неуместное исключение переполнения).