Фреймы, локальные переменные, рекурсия
Ранее рассмотренная конвенция не обеспечивает некоторые возможности языков высокого уровня. Добавим некоторые из этих функций в новой конвенции о вызове процедур основе стека.
- Сохранение регистров в стеке
- Сохранение адреса возврата в стеке($ra)
- Сохранение и востаноление регистров $s0―$s7 в/из стеке/стека
- Конвенция о вызове процедур на основе стека.
- Это не официальное соглашение. Однако оно может быть использовано для небольшого проекта на языке ассемблера. Конвенция не очень сложна и делает почти все, что может быть нужно. Если вы хотите использовать процедуры из программы на языке "C" или использовать процедуры из библиотек программ, то необходимо использовать официальные правила в полном объеме. (Что в эмуляторе MARS невозможно.)
- Правила:
- Вызов подпрограммы (делает вызывающая процедура):
- Сохранить в стеке регистры "$t0 - $t9", которые должны быть сохранены(будут использованы вызывающей процедурой после вызова подпрограммы). Подпрограмма может изменить эти регистры.
- Загрузить значения аргументов в регистры "$a0 - $a3".
- Вызов подпрограммы с помощью "jal".
- Пролог подпрограммы:
- Сохранить регистр "$ra" в стек.
- Сохранить в стек регистры "$s0 - $s7"(подпрограмма может изменять их).
- Тело подпрограммы:
- Подпрограмма может изменять регистры "$t0 - $t9", или те из регистров "$s0 - $s7", которые были сохранены в прологе.
- Из подпрограммы можно вызывать другую подпрограмму, следуя этим правилам.
- Эпилог подпрограммы (непосредственно перед возвращением):
- Загрузить возвращаемые значения в регистры "$v0 - $v1".
- Извлечь из стека (в обратном порядке) сохраненые в прологе регистры "$s0 - $s7".
- Извлечь из стека в регистр '$ra" адрес возврата.
- Вернуться в вызывающую процедуру, используя "jr $ra".
- Восстановление состояния при выходе из подпрограммы:
- Извлечь из стека (в обратном порядке) сохраненые регистры "$t0 - $t9".
- Вызов подпрограммы (делает вызывающая процедура):
- Пролог и эпилог вызываемой подпрограммы: ris
- Вызов и возврат.
- Вложенные вызовы подпрограмм и цепь активации.
- О реальных конвенциях(ABI).
- Пример программы:
1 ## Driver -- main program for the application 2 3 .text 4 .globl main 5 6 main: 7 sub $sp,$sp,4 # push the return address 8 sw $ra,($sp) 9 sub $sp,$sp,4 # push $s0 10 sw $s0,($sp) 11 12 la $a0,xprompt # prompt the user 13 li $v0,4 # service 4 14 syscall 15 li $v0,5 # service 5 -- read int 16 syscall # $v0 = integer 17 move $s0,$v0 # save x 18 19 la $a0,yprompt # prompt the user 20 li $v0,4 # service 4 21 syscall 22 li $v0,5 # service 5 -- read int 23 syscall # $v0 = integer 24 25 # prepare arguments 26 move $a0,$s0 # x 27 move $a1,$v0 # y 28 jal maxExp # maximum expression 29 nop # returned in $v0 30 move $s0,$v0 # keep it safe 31 32 la $a0,rprompt # output title 33 li $v0,4 # service 4 34 syscall 35 36 move $a0,$s0 # get maximum 37 li $v0,1 # print it out 38 syscall 39 40 lw $ra,($sp) # pop $s0 41 add $s0,$sp,4 42 lw $ra,($sp) # pop return address 43 add $sp,$sp,4 44 45 li $s0,0 # return to OS 46 li $v0,10 47 syscall 48 49 50 .data 51 xprompt: .asciiz "Enter a value for x --> " 52 yprompt: .asciiz "Enter a value for y --> " 53 rprompt: .asciiz "The maximum expression is: " 54 55 ## maxInt -- compute the maximum of two integer arguments 56 ## 57 ## Input: 58 ## $a0 -- a signed integer 59 ## $a1 -- a signed integer 60 ## 61 ## Returns: 62 ## $v0 -- maximum 63 64 .text 65 .globl maxInt 66 67 maxInt: 68 # body 69 move $v0,$a0 # max = $a0 70 bgt $a0,$a1,endif # if $a1 > $a0 71 nop 72 move $v0,$a1 # max = $a1 73 endif: # endif 74 # epilog 75 jr $ra # return to caller 76 nop 77 78 ## maxExp -- compute the maximum of three expressions 79 ## 80 ## Input: 81 ## $a0 -- a signed integer, x 82 ## $a1 -- a signed integer, y 83 ## 84 ## Returns: 85 ## $v0 -- the maximum of x*x, x*y, or 5*y 86 ## 87 ## Registers: 88 ## $s0 -- x*x 89 ## $s1 -- x*y 90 ## $s2 -- 5*y 91 92 .text 93 .globl maxExp 94 95 maxExp: 96 # prolog 97 sub $sp,$sp,4 # push the return address 98 sw $ra,($sp) 99 sub $sp,$sp,4 # push $s0 100 sw $s0,($sp) 101 sub $sp,$sp,4 # push $s1 102 sw $s1,($sp) 103 sub $sp,$sp,4 # push $s2 104 sw $s2,($sp) 105 106 # body 107 mul $s0,$a0,$a0 # x*x 108 mul $s1,$a0,$a1 # x*y 109 li $t0,5 110 mul $s2,$t0,$a1 # 5*y 111 112 move $a0,$s0 # compute max of x*x 113 move $a1,$s1 # and x*y 114 jal maxInt # current max in $v0 115 nop 116 117 move $a0,$v0 # compute max of 118 move $a1,$s2 # current max, and 5*y 119 jal maxInt # total max will be in $v0 120 nop 121 122 # epilog 123 lw $s2,($sp) # pop $s2 124 add $sp,$sp,4 125 lw $s1,($sp) # pop $s1 126 add $sp,$sp,4 127 lw $s0,($sp) # pop $s0 128 add $sp,$sp,4 129 lw $ra,($sp) # pop return address 130 add $sp,$sp,4 131 jr $ra # return to caller 132 nop
- Локальные переменные.
- Кадр стека.
- Конвенция о вызове процедур с использованием указателя кадра стека.
- Это не официальное соглашение. В большинстве реальных MIPS-ABI использование указателя кадра стека факультативно. В широко распространенной архитектуре "intel32/64" указателя кадра стека активно используется.
- Вызов подпрограммы (делает вызывающая процедура):
- Сохранить в стеке регистры "$t0 - $t9", которые должны быть сохранены(будут использованы вызывающей процедурой после вызова подпрограммы). Подпрограмма может изменить эти регистры.
- Загрузить значения аргументов в регистры "$a0 - $a3".
- Вызов подпрограммы с помощью "jal".
- Пролог подпрограммы:
- Сохранить регистр "$ra" в стек.
- Сохранить регистр "$fp" в стек.
- Сохранить в стек регистры "$s0 - $s7"(подпрограмма может изменять их).
- Инициализировать указатель кадра: $fp = ($sp - размер пространства для локальных переменных).
NB Помните, что вычитание из $sp увеличивает стек, что стек растет вниз, что размер переменной всегда четыре байта.
- Инициализировать указателя стека: $sp = $fp.
- Тело подпрограммы:
- Подпрограмма может изменять регистры "$t0 - $t9", или те из регистров "$s0 - $s7", которые были сохранены в прологе.
- Из подпрограммы можно вызывать другую подпрограмму, следуя этим правилам.
- Подпрограмма обращается к локальным переменным, как disp($fp).
- Подпрограмма может работать со стеком, используя регистр "$sp".
- Эпилог подпрограммы (непосредственно перед возвращением):
- Загрузить возвращаемые значения в регистры "$v0 - $v1".
- $sp = ($fp + размер пространства для локальных переменных).
- Извлечь из стека (в обратном порядке) сохраненные в прологе регистры "$s0 - $s7".
- Извлечь из стека в регистр "$fp" адрес кадра стека.
- Извлечь из стека в регистр '$ra" адрес возврата.
- Вернуться в вызывающую процедуру, используя "jr $ra".
- Восстановление состояния при выходе из подпрограммы (делает вызывающая процедура):
- Извлечь из стека (в обратном порядке) сохраненые регистры "$t0 - $t9".
- Пример программы:
1 ## file variablesStack.asm 2 3 # int mysub( int arg ) 4 # { 5 # int b,c; // b: 0($fp) 6 # // c: 4($fp) 7 # b = arg*2; 8 # c = b + 7; 9 # 10 # return c; 11 # } 12 .text 13 .globl mysub 14 mysub: 15 # prolog 16 sub $sp,$sp,4 # 1. Push return address 17 sw $ra,($sp) 18 sub $sp,$sp,4 # 2. Push caller's frame pointer 19 sw $fp,($sp) 20 sub $sp,$sp,4 # 3. Push register $s1 21 sw $s1,($sp) 22 sub $fp,$sp,8 # 4. $fp = $sp - space_for_variables 23 move $sp,$fp # 5. $sp = $fp 24 25 # body of subroutine 26 mul $s1,$a0,2 # arg*2 27 sw $s1,0($fp) # b = " " 28 29 lw $t0,0($fp) # get b 30 add $t0,$t0,7 # b+7 31 sw $t0,4($fp) # c = " " 32 33 # epilog 34 lw $v0,4($fp) # 1. Put return value in $v0 35 add $sp,$fp,8 # 2. $sp = $fp + space_for_variables 36 lw $s1,($sp) # 3. Pop register $s1 37 add $sp,$sp,4 # 38 lw $fp,($sp) # 4. Pop $fp 39 add $sp,$sp,4 # 40 lw $ra,($sp) # 5. Pop $ra 41 add $sp,$sp,4 # 42 jr $ra # 6. return to caller 43 44 # main() 45 # { 46 # int a; // a: 0($fp) 47 # a = mysub( 6 ); 48 # print( a ); 49 # } 50 .text 51 .globl main 52 main: 53 # prolog 54 sub $sp,$sp,4 # 1. Push return address 55 sw $ra,($sp) 56 sub $sp,$sp,4 # 2. Push caller's frame pointer 57 sw $fp,($sp) 58 # 3. No S registers to push 59 sub $fp,$sp,4 # 4. $fp = $sp - space_for_variables 60 move $sp,$fp # 5. $sp = $fp 61 62 # subroutine call 63 # 1. No T registers to push 64 li $a0,6 # 2. Put argument into $a0 65 jal mysub # 3. Jump and link to subroutine 66 67 # return from subroutine 68 # 1. No T registers to restore 69 70 sw $v0,0($fp) # a = mysub( 6 ) 71 72 # print a 73 lw $a0,0($fp) # load a into $a0 74 li $v0,1 # print integer service 75 syscall 76 # epilog 77 # 1. No return value 78 add $sp,$fp,4 # 2. $sp = $fp + space_for_variables 79 # 3. No S registers to pop 80 lw $fp,($sp) # 4. Pop $fp 81 add $sp,$sp,4 # 82 lw $ra,($sp) # 5. Pop $ra 83 add $sp,$sp,4 # 84 85 li $s0,0 # return to OS 86 li $v0,10 87 syscall