Фреймы, локальные переменные, рекурсия
Ранее рассмотренная конвенция не обеспечивает некоторые возможности языков высокого уровня. Добавим некоторые из этих функций в новой конвенции о вызове процедур на основе стека.
- Сохранение регистров в стеке
- Сохранение адреса возврата в стеке($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".
- Вызов подпрограммы (делает вызывающая процедура):
- Пролог и эпилог вызываемой подпрограммы:
- Вызов и возврат.
- Вложенные вызовы подпрограмм и цепь активации.
- О реальных конвенциях(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
- Приложения:
- MIPS ABI