Фреймы, локальные переменные, рекурсия

Ранее рассмотренная конвенция не обеспечивает некоторые возможности языков высокого уровня. Добавим некоторые из этих функций в новой конвенции о вызове процедур основе стека.

  1. Сохранение регистров в стеке
    • Сохранение адреса возврата в стеке($ra)
    • Сохранение и востаноление регистров $s0―$s7 в/из стеке/стека
  2. Конвенция о вызове процедур на основе стека.
    • Это не официальное соглашение. Однако оно может быть использовано для небольшого проекта на языке ассемблера. Конвенция не очень сложна и делает почти все, что может быть нужно. Если вы хотите использовать процедуры из программы на языке "C" или использовать процедуры из библиотек программ, то необходимо использовать официальные правила в полном объеме. (Что в эмуляторе MARS невозможно.)
    • Правила:
      1. Вызов подпрограммы (делает вызывающая процедура):
        • Сохранить в стеке регистры "$t0 - $t9", которые должны быть сохранены(будут использованы вызывающей процедурой после вызова подпрограммы). Подпрограмма может изменить эти регистры.
        • Загрузить значения аргументов в регистры "$a0 - $a3".
        • Вызов подпрограммы с помощью "jal".
      2. Пролог подпрограммы:
        • Сохранить регистр "$ra" в стек.
        • Сохранить в стек регистры "$s0 - $s7"(подпрограмма может изменять их).
      3. Тело подпрограммы:
        • Подпрограмма может изменять регистры "$t0 - $t9", или те из регистров "$s0 - $s7", которые были сохранены в прологе.
        • Из подпрограммы можно вызывать другую подпрограмму, следуя этим правилам.
      4. Эпилог подпрограммы (непосредственно перед возвращением):
        • Загрузить возвращаемые значения в регистры "$v0 - $v1".
        • Извлечь из стека (в обратном порядке) сохраненые в прологе регистры "$s0 - $s7".
        • Извлечь из стека в регистр '$ra" адрес возврата.
        • Вернуться в вызывающую процедуру, используя "jr $ra".
      5. Восстановление состояния при выходе из подпрограммы:
        • Извлечь из стека (в обратном порядке) сохраненые регистры "$t0 - $t9".
  3. Пролог и эпилог вызываемой подпрограммы: ris
  4. Вызов и возврат.
  5. Вложенные вызовы подпрограмм и цепь активации.
  6. О реальных конвенциях(ABI).
  7. Пример программы:
    •    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         
      
  8. Локальные переменные.
  9. Кадр стека.
  10. Конвенция о вызове процедур с использованием указателя кадра стека.
    • Это не официальное соглашение. В большинстве реальных MIPS-ABI использование указателя кадра стека факультативно. В широко распространенной архитектуре "intel32/64" указателя кадра стека активно используется.
    1. Вызов подпрограммы (делает вызывающая процедура):
      • Сохранить в стеке регистры "$t0 - $t9", которые должны быть сохранены(будут использованы вызывающей процедурой после вызова подпрограммы). Подпрограмма может изменить эти регистры.
      • Загрузить значения аргументов в регистры "$a0 - $a3".
      • Вызов подпрограммы с помощью "jal".
    2. Пролог подпрограммы:
      • Сохранить регистр "$ra" в стек.
      • Сохранить регистр "$fp" в стек.
      • Сохранить в стек регистры "$s0 - $s7"(подпрограмма может изменять их).
      • Инициализировать указатель кадра: $fp = ($sp - размер пространства для локальных переменных).
        • NB Помните, что вычитание из $sp увеличивает стек, что стек растет вниз, что размер переменной всегда четыре байта.

      • Инициализировать указателя стека: $sp = $fp.
    3. Тело подпрограммы:
      • Подпрограмма может изменять регистры "$t0 - $t9", или те из регистров "$s0 - $s7", которые были сохранены в прологе.
      • Из подпрограммы можно вызывать другую подпрограмму, следуя этим правилам.
      • Подпрограмма обращается к локальным переменным, как disp($fp).
      • Подпрограмма может работать со стеком, используя регистр "$sp".
    4. Эпилог подпрограммы (непосредственно перед возвращением):
      • Загрузить возвращаемые значения в регистры "$v0 - $v1".
      • $sp = ($fp + размер пространства для локальных переменных).
      • Извлечь из стека (в обратном порядке) сохраненные в прологе регистры "$s0 - $s7".
      • Извлечь из стека в регистр "$fp" адрес кадра стека.
      • Извлечь из стека в регистр '$ra" адрес возврата.
      • Вернуться в вызывающую процедуру, используя "jr $ra".
    5. Восстановление состояния при выходе из подпрограммы (делает вызывающая процедура):
      • Извлечь из стека (в обратном порядке) сохраненые регистры "$t0 - $t9".
  11. Пример программы:
       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