Assembly MIPS中具有嵌套调用的子例程

Assembly MIPS中具有嵌套调用的子例程,assembly,mips,subroutine,mars-simulator,Assembly,Mips,Subroutine,Mars Simulator,此程序用于将字符串中的所有小写字母替换为字符* 我遇到的问题是子例程的嵌套调用。即,一些相同的$t和$a寄存器正在不同的子例程中使用。因此,当在另一个子例程中调用某个子例程时,调用方子例程的寄存器会出错 .data str: .asciiz "WindOnTheHill" .text la $a0, str # start of the string li $a1, '*' jal ReplaceAllLower #la $a0, str # sta

此程序用于将字符串中的所有小写字母替换为字符
*

我遇到的问题是子例程的嵌套调用。即,一些相同的
$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, '\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  
注意:现在我们不要把注意力集中在算法上

所以,我的问题是,

我应该使用什么技术来解决这个问题,因为事先很难知道将来调用哪个子例程(库可以随时间扩展自身)

某些约定的要求是,如果所有子例程都遵守这些约定,则不会出现任何问题,例如寄存器被调用的过程阻塞

对于MIPS,普遍接受的呼叫约定是:
*寄存器
$t0-7
是“临时”的,可以在没有预防措施的情况下使用。如果一个过程希望在函数调用中保留其中一些,则它有责任保存它们(“调用方已保存”)。
*寄存器
$s0-7
(“保存的寄存器”)在没有预防措施的情况下不能使用。如果一个过程想要使用其中的一些,它必须在使用前保留它们,并在返回时恢复它们的值(“被调用方已保存”)

调用约定中还有其他重要方面,例如在寄存器
$a0-$a3
中传递第一个参数,使用
$v0-$v1
作为返回值等。它们还精确地定义了一些寄存器的作用,例如堆栈指针(
sp
)或帧指针(
fp
)。这是一个很好的总结,但你们可以很容易地在互联网上找到额外的细节

保存寄存器是用一个函数完成的。它是一种保存所有保留信息的数据结构

在函数开始时,堆栈中必须为需要保留的所有信息保留一些空间。然后将要使用的寄存器
s0-s7
保存在堆栈中。如果该函数是非终端函数(即调用另一个函数),则返回的地址也会保存

调用函数之前,需要保存的临时寄存器或参数寄存器(
$t0-7
$a0-3
)会写入堆栈。参数被写入寄存器
$a0-3
,或者根据需要堆叠。然后调用该函数

被调用函数返回后,保留的临时寄存器将恢复

在函数返回之前,需要恢复保存的
$s0-7
寄存器和返回地址寄存器(
$ra
),释放堆栈空间,调用
jr$ra


如果所有过程都遵守这些调用约定,就不会有任何问题。编译器尊重这些约定,但它们依赖于操作系统和体系结构

某些约定的要求是,如果所有子例程都遵守这些约定,则不会出现任何问题,例如寄存器被调用的过程阻塞

对于MIPS,普遍接受的呼叫约定是:
*寄存器
$t0-7
是“临时”的,可以在没有预防措施的情况下使用。如果一个过程希望在函数调用中保留其中一些,则它有责任保存它们(“调用方已保存”)。
*寄存器
$s0-7
(“保存的寄存器”)在没有预防措施的情况下不能使用。如果一个过程想要使用其中的一些,它必须在使用前保留它们,并在返回时恢复它们的值(“被调用方已保存”)

调用约定中还有其他重要方面,例如在寄存器
$a0-$a3
中传递第一个参数,使用
$v0-$v1
作为返回值等。它们还精确地定义了一些寄存器的作用,例如堆栈指针(
sp
)或帧指针(
fp
)。这是一个很好的总结,但你们可以很容易地在互联网上找到额外的细节

保存寄存器是用一个函数完成的。它是一种保存所有保留信息的数据结构

在函数开始时,堆栈中必须为需要保留的所有信息保留一些空间。然后将要使用的寄存器
s0-s7
保存在堆栈中。如果该函数是非终端函数(即调用另一个函数),则返回的地址也会保存

调用函数之前,需要保存的临时寄存器或参数寄存器(
$t0-7
$a0-3
)会写入堆栈。参数被写入寄存器
$a0-3
,或者根据需要堆叠。然后调用该函数

被调用函数返回后,保留的临时寄存器将恢复

在函数返回之前,需要恢复保存的
$s0-7
寄存器和返回地址寄存器(
$ra
),释放堆栈空间,调用
jr$ra

如果所有过程都遵守这些调用约定,就不会有任何问题。编译器尊重这些约定,但它们依赖于操作系统和体系结构

通常,您的平台(操作系统/硬件)将定义一个应用程序二进制接口,其中包括指定在子例程调用中保留哪些寄存器,以及子例程可以自由修改哪些寄存器而无需恢复。通常,需要保留的寄存器被复制到堆栈中,堆栈按FIFO顺序增长/收缩,从那里可以恢复它们。最关键的是,堆栈指针寄存器是被调用方保存的(跨调用保留)。通常,您的平台(操作系统/硬件)将定义一个应用程序二进制接口,其中包括指定哪些寄存器在子例程调用中被保留,哪些寄存器可供子例程在不恢复的情况下自由修改。通常需要保留的寄存器是copi