Материал: Архитектура и программирование MIPS-процессоров. Разинкин К.А

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

void simple() { return;

}

Код на языке ассемблера MIPS

0x00400200 main: jal simple # call function

0x00400204 …

0x00401020 simple: jr $ra # return

Инструкции безусловного перехода с возвратом (jal) и безусловного перехода по регистру (jr $ra) – две необходимые для вызова функций инструкции. Инструкция jal выполняет две операции: сохраняет адрес следующей за ней инструкции в регистре адреса возврата ($ra) и выполняет переход по адресу первой инструкции вызываемой функции.

В предыдущем примере кода функция main вызывает функцию simple, выполняя инструкцию jal. Инструкция jal выполняет переход на метку simple и сохраняет значение 0x00400204 в регистре $ra. Функция simple немедленно завершается, выполняя инструкцию jr $ra, то есть осуществляет переход к инструкции по адресу, находящемуся в регистре $ra. После этого функция main продолжает выполняться с этого ад-

реса (0x00400204).

Входные аргументы и возвращаемые значения

В представленном выше примере кода функция simple не очень-то полезна, потому что она не получает входных значений от вызывающей функции (main) и ничего не возвращает. По соглашениям, принятым в архитектуре MIPS, функции используют регистры $a0–$a3 для входных аргументов и регистры $v0–$v1 для возвращаемого значения. В примере кода представленного ниже функция diffofsums вызывается с четырьмя аргументами и возвращает один результат.

Следуя соглашениям MIPS, вызывающая функция main помещает аргументы функции слева направо в регистры вход-

76

ных значений $a0–$a3. Вызываемая функция diffofsums размещает возвращаемое значение в регистре возвращаемых значе-

ний $v0.

Функция, возвращающая 64-битное значение, например, число с плавающей точкой двойной точности, использует оба регистра $v0 и $v1. Если функции нужно передать более четырех аргументов, то дополнительные аргументы размещаются в стеке, который мы обсудим далее.

Пример. Вызов функции с аргументами и возвращаемым значением.

Код на языке высокого уровня int main()

{

int y;

…

y = diffofsums(2, 3, 4, 5);

…

}

int diffofsums(int f, int g, int h, int i)

{

int result;

result = (f + g) − (h + i); return result;

}

Код на языке ассемблера MIPS

# $s0 = y

 

main:

 

…

 

 

addi $a0, $0, 2

# argument 0 = 2

addi $a1, $0, 3

# argument 1 = 3

addi $a2, $0, 4

# argument 2 = 4

addi $a3, $0, 5

# argument 3 = 5

jal

diffofsums

# call function

add

$s0, $v0, $0

# y = returned value

…

 

 

 

 

77

# $s0 = result

 

 

 

 

diffofsums:

 

 

 

 

add

$t0, $a0, $a1

# $t0

= f + g

add

$t1, $a2, $a3

# $t1

= h + i

sub $s0, $t0, $t1

# result = (f + g) − (h + i)

add

$v0, $s0, $0

 

# put return value in $v0

jr $ra

# return to caller

Стек

Стек (англ.: stack) – это участок памяти для хранения локальных переменных функции. Стек расширяется (занимает больше памяти), если процессору нужно больше места, и сужается (занимает меньше памяти), если процессору больше не нужны сохранённые там переменные. Перед тем, как объяснить, как функции используют стек для хранения временных переменных, мы объясним, как стек работает.

Стек является очередью, работающей в режиме «последним пришёл – первым ушёл» (LIFO, от англ. last-in-first- out). Как и в стопке тарелок, последний элемент, помещенный (англ.: push) на стек (верхняя тарелка), будет первым элементом, который с него снимут (извлекут, англ.: pop). Каждая функция может выделить память на стеке для хранения локальных переменных, и она же должна освободить её перед возвратом. Верхушка стека (англ.: top of the stack) – это память, которая была выделена последней. Так же, как стопка тарелок растёт вверх в пространстве, стек в архитектуре MIPS увеличивается в памяти.

Стек расширяется в сторону младших адресов по мере выделения нового места в памяти для программы (функции).

На рис. 2.14 изображен стек. Регистр указателя стека ($sp, от англ. stack pointer) – это специальный регистр, который указывает на верхушку стека. Указатель (англ.: pointer) – причудливое имя для обычного адреса памяти. Он указывает на

78

данные, то есть предоставляет их адрес. Например, на рис. 2.14 (a) указатель стека $sp содержит адрес 0x7FFFFFFC и указывает на значение 0x12345678.

Регистр $sp указывает на верхушку стека – наименьший адрес памяти, доступной на стеке. Таким образом, на Рис. 2.14

(a)стек не может использовать память ниже слова с адресом

0x7FFFFFFC.

Указатель стека ($sp) изначально равен большему адресу памяти, после чего его значение по необходимости уменьшается для увеличения доступного программе места. На Рис. 2.14

(b)изображен стек, расширяющийся для того, чтобы выделить два дополнительных слова данных для хранения временных

переменных. Для этого значение регистра $sp уменьшается на 8 и становится равным 0x7FFFFFF4. Два дополнительных слова данных 0xAABBCCDD и 0x11223344 временно размещаются на стеке.

Рис. 2.14. Стек

Одно из важных применений стека – сохранение и восстановление значений регистров, используемых внутри функции. Вспомним, что функция должна производить вычисления и возвращать значения, но не должна приводить к неожиданным побочным эффектам. В частности, она не должна менять значения никаких регистров кроме регистра $v0, содержащего

79

возвращаемое значение. В предыдущем примере функция diffofsums нарушает это правило, потому что она изменяет регистры $t0, $t1 и $s0. Если бы функция main использовала регистры $t0, $t1 или $s0 до вызова diffofsums, то содержимое этих регистров было бы повреждено вызовом этой функции.

Чтобы решить эту проблему, функция сохраняет значения регистров на стеке перед тем, как изменить их, и восстанавливает их из стека перед тем, как завершиться. А именно, совершает следующие шаги:

1.Выделяет пространство на стеке для сохранения значений одного или нескольких регистров.

2.Сохраняет значения регистров на стек.

3.Выполняет функцию, используя регистры.

4.Восстанавливает исходные значения регистров из

стека.

5.Освобождает пространство на стеке.

В следующем примере кода приведена улучшенная версия функции diffofsums, которая сохраняет и восстанавливает регистры $t0, $t1 и $s0.

Пример. Функция, сохраняющая регистры на стеке Код на языке ассемблера MIPS

# $s0 = result diffofsums:

addi $sp, $sp, −12 # make space on stack to store three

registers

sw

$s0, 8($sp)

# save $s0 on stack

sw

$t0, 4($sp)

# save $t0 on stack

sw

$t1, 0($sp)

# save $t1 on stack

add

$t0, $a0, $a1

# $t0 = f + g

add

$t1, $a2, $a3

# $t1 = h + i

sub

$s0, $t0, $t1

# result = (f + g) − (h + i)

add

$v0, $s0, $0

# put return value in $v0

lw

$t1, 0($sp)

# restore $t1 from stack

lw

$t0, 4($sp)

# restore $t0 from stack

 

 

80

Источник: https://studfile.net/preview/16568788/