Введение

Способы вызова подпрограмм в компьютерах

В информатике конвенция вызова — это схема на уровне реализации (низкоуровневая), определяющая, как подпрограммы или функции получают параметры от вызывающей стороны и как возвращают результат. Когда какой-либо код вызывает функцию, принимаются проектные решения относительно того, где и как передаются параметры этой функции, а также где и как возвращаются результаты, при этом эти передачи обычно осуществляются через определенные регистры или в рамках стека вызовов. Существуют различные варианты организации разделения задач подготовки к вызову функции и восстановления окружения после завершения функции между вызывающей стороной и вызываемой функцией. Некоторые конвенции вызова предписывают способ вызова каждой функции. Для обеспечения корректного и надежного выполнения всей программы с использованием этих функций необходимо использовать правильную конвенцию вызова для каждого вызова функции.

Введение

Конвенции вызова обычно считаются частью бинарного интерфейса приложения (ABI). Они могут рассматриваться как соглашение между вызывающей и вызываемой функцией. Эта конвенция вызова определяет, что "типичная" подпрограмма ARM:
В прологе помещает регистры r4–r11 в стек, а также адрес возврата из r14 в стек (это можно сделать одной инструкцией STM);
Копирует переданные аргументы (в r0–r3) в локальные рабочие регистры (r4–r11);
Выделяет другие локальные переменные в оставшиеся локальные рабочие регистры (r4–r11);
Выполняет вычисления и вызывает другие подпрограммы при необходимости, используя BL, предполагая, что регистры r0–r3, r12 и r14 не сохраняются;
Помещает результат в r0;
В эпилоге извлекает регистры r4–r11 из стека и адрес возврата в счетчик команд r15. Это можно сделать одной инструкцией LDM.

RISC-V ISA

RISC V имеет определенную конвенцию вызова с двумя вариантами: с поддержкой операций с плавающей точкой и без нее. Аргументы передаются в регистрах, когда это возможно.

POWER, PowerPC и Power ISA

Архитектуры POWER, PowerPC и Power ISA имеют большое количество регистров, поэтому большинство функций могут передавать все аргументы в регистрах при вызовах первого уровня. Дополнительные аргументы передаются через стек, а место для аргументов, передаваемых в регистрах, также всегда выделяется в стеке для удобства вызываемой функции, на случай использования многоуровневых вызовов (рекурсивных или иных) и необходимости сохранения регистров. Это также полезно в функциях с переменным числом аргументов, где аргументы функции должны быть доступны как массив. Для всех процедурных языков используется единая конвенция вызова. Инструкции ветвления и связи сохраняют адрес возврата в специальном регистре связи, отдельном от регистров общего назначения; подпрограмма возвращается вызывающей стороне с помощью инструкции ветвления, использующей регистр связи в качестве адреса назначения. Листовые подпрограммы не нуждаются в сохранении или восстановлении регистра связи; нелистовые подпрограммы должны сохранить адрес возврата перед вызовом другой подпрограммы и восстановить его перед возвратом, сохраняя его с помощью инструкции "Move From Special Purpose Register" для перемещения регистра связи в регистр общего назначения и, при необходимости, затем сохраняя его в стек, а восстанавливая его, если он был сохранен в стек, загружая сохраненное значение регистра связи в регистр общего назначения, а затем используя инструкцию "Move To Special Purpose Register" для перемещения регистра, содержащего сохраненное значение регистра связи, в регистр связи.

МИПС

O32 ABI является наиболее часто используемым ABI благодаря своему статусу оригинального ABI System V для MIPS. Он строго основан на стеке, и для передачи аргументов доступно всего четыре регистра. Эта кажущаяся медлительность, в сочетании с устаревшей моделью чисел с плавающей запятой, включающей всего 16 регистров, способствовала распространению множества других соглашений о вызовах. ABI был разработан в 1990 году и с 1994 года не обновлялся. Он определен только для 32-битного MIPS, но GCC создал 64-битную версию под названием O64. Для 64-битных систем наиболее часто используется N64 ABI (не связанный с Nintendo 64) от Silicon Graphics. Наиболее важным улучшением является то, что теперь для передачи аргументов доступно восемь регистров; также увеличено количество регистров с плавающей запятой до 32. Существует также версия ILP32 под названием N32, которая использует 32-битные указатели для уменьшения размера кода, аналогично ABI x32. Обе версии работают в 64-битном режиме процессора. EABI вдохновил MIPS Technologies на предложение более радикального ABI "NUBI", который дополнительно повторно использует регистры аргументов для возвращаемого значения. MIPS EABI поддерживается GCC, но не LLVM; ни один из них не поддерживает NUBI. Для всех ABI O32 и N32/N64 адрес возврата хранится в регистре. Это автоматически устанавливается с помощью инструкций `jal` (jump and link) или `jalr` (jump and link register). Стек растет в направлении уменьшения адресов.

Спарк

Архитектура SPARC, в отличие от большинства архитектур RISC, построена на использовании окон регистров. В каждом окне регистров доступно 24 регистра: 8 – "входные" регистры (%i0–%i7), 8 – "локальные" регистры (%l0–%l7) и 8 – "выходные" регистры (%o0–%o7). "Входные" регистры используются для передачи аргументов вызываемой функции, а любые дополнительные аргументы помещаются в стек. Однако, вызванная функция всегда выделяет место для обработки возможного переполнения окна регистров, локальных переменных и (в 32-битной версии SPARC) возврата структуры по значению. Для вызова функции аргументы передаются в "выходные" регистры; при вызове функции "выходные" регистры становятся "входными", и вызванная функция получает доступ к аргументам через свои "входные" регистры. По завершении вызванная функция помещает возвращаемое значение в первый "входной" регистр, который становится первым "выходным" регистром при возврате. ABI System V, которой следуют большинство современных Unix-подобных систем, передает первые шесть аргументов во "входные" регистры %i0–%i5, резервируя %i6 для указателя фрейма и %i7 для адреса возврата.

Супер-Х

Регистр Windows CE 5.0 gcc Renesas R0 Возвращаемые значения. Временный для расширения псевдоинструкций ассемблера. Неявный операнд-источник/назначения для 8/16-битных операций. Не сохраняется. Возвращаемое значение, сохраняется вызывающей функцией. Переменные/временные. Не гарантировано. R1 – R3 Используются как временные регистры. Не сохраняются. Временный регистр, сохраняется вызывающей функцией. Адрес структуры (по умолчанию сохраняется вызывающей функцией). Переменные/временные. Не гарантировано. R4 – R7 Первые четыре слова целочисленных аргументов. Область построения аргументов предоставляет место, куда могут быть вытеснены аргументы, хранящиеся в R4–R7. Не сохраняются. Передача параметров, сохраняется вызывающей функцией. Аргументы. Не гарантировано. R8 – R13 Используются как постоянные регистры. Сохраняются. Сохраняется вызываемой функцией. Переменные/временные. Гарантировано. R14 Указатель кадра по умолчанию. (R8–R13 также могут использоваться как указатель кадра, а в листовых подпрограммах R1–R3 могут использоваться как указатель кадра). Сохраняется. Указатель кадра, FP, сохраняется вызываемой функцией. Переменные/временные. Гарантировано. R15 Используется как указатель стека или как постоянный регистр. Сохраняется. Указатель стека, SP, сохраняется вызываемой функцией. Указатель стека. Гарантировано. Примечание: "сохраняются" означает, что регистры сохраняются вызываемой функцией; то же самое относится к "гарантировано".

Код с нитками

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

ПЛ/И

По умолчанию, соглашение о вызовах для программ, написанных на языке PL/I, передает все аргументы по ссылке, хотя могут быть опционально указаны и другие соглашения. Обработка аргументов может различаться в зависимости от компилятора и платформы, но обычно адреса аргументов передаются через список аргументов в памяти. В конце также может быть передан скрытый адрес, указывающий на область для хранения возвращаемого значения. Ввиду широкого спектра поддерживаемых PL/I типов данных, может также передаваться дескриптор данных для определения, например, длины символьных или битовых строк, размерности и границ массивов (векторов описания), или структуры и содержимого структуры данных. Фиктивные аргументы создаются для аргументов, являющихся константами, или не соответствующих типу аргумента, ожидаемого вызываемой процедурой.