stringtranslate.com

Соглашение о вызовах

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

Введение

Соглашения о вызовах обычно считаются частью двоичного интерфейса приложения (ABI).

Связанные понятия

Имена или значения параметров и возвращаемых значений определяются в интерфейсе прикладного программирования (API, в отличие от ABI), который представляет собой отдельную, хотя и связанную концепцию с ABI и соглашением о вызовах. Имена членов переданных структур и объектов также будут считаться частью API, а не ABI. Иногда API включают ключевые слова для указания соглашения о вызове функций.

Соглашения о вызовах обычно не включают информацию о сроке службы динамически выделяемых структур и объектов. {{{1}}} в других документах указывается, где лежит ответственность за освобождение выделенной памяти.

Соглашения о вызовах вряд ли будут определять расположение элементов внутри структур и объектов, например порядок байтов или упаковку структур.

Для некоторых языков соглашение о вызовах включает детали обработки ошибок или исключений (например, Go , Java ), а для других — нет (например, C++ ).

Для удаленных вызовов процедур существует аналогичная концепция, называемая маршаллингом .

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

Различные соглашения о вызовах

Соглашения о вызовах могут отличаться:

Соглашения о вызовах на одной платформе

Иногда на одной платформе появляется несколько соглашений о вызовах; данная платформа и языковая реализация могут предлагать выбор соглашений о вызовах. Причины этого включают производительность, адаптацию соглашений других популярных языков, а также ограничения или соглашения, налагаемые различными « вычислительными платформами ».

Во многих архитектурах имеется только одно широко используемое соглашение о вызовах, часто предлагаемое архитектором. Для RISC, включая SPARC, MIPS и RISC-V , часто используются имена регистров, основанные на этом соглашении о вызовах. Например, регистры MIPS $4через $7имеют «имена ABI» $a0через $a3, что отражает их использование для передачи параметров в стандартном соглашении о вызовах. (ЦП RISC имеют множество эквивалентных регистров общего назначения, поэтому обычно нет аппаратных причин давать им имена, кроме чисел.)

Соглашение о вызовах языка данной программы может отличаться от соглашения о вызовах базовой платформы, ОС или какой-либо библиотеки, с которой связана ссылка. Например, в 32-разрядной версии Windows вызовы операционной системы имеют соглашение о вызовах stdcall , тогда как многие программы C , которые там выполняются, используют соглашение о вызовах cdecl . Чтобы учесть эти различия в соглашении о вызовах, компиляторы часто допускают ключевые слова, определяющие соглашение о вызовах для данной функции. Объявления функций будут включать дополнительные ключевые слова, специфичные для платформы, которые указывают используемое соглашение о вызовах. При правильной обработке компилятор сгенерирует код для вызова функций соответствующим образом.

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

Архитектуры

x86 (32-разрядная версия)

32-разрядная версия архитектуры x86 используется со многими различными соглашениями о вызовах. Из-за небольшого количества архитектурных регистров и исторического акцента на простоте и небольшом размере кода многие соглашения о вызовах x86 передают аргументы в стек. Возвращаемое значение (или указатель на него) возвращается в регистр. В некоторых соглашениях для первых нескольких параметров используются регистры, что может повысить производительность, особенно для очень часто вызываемых коротких и простых листовых подпрограмм (т. е. подпрограмм, которые не вызывают другие подпрограммы).

Пример вызова:

 нажать EAX ; передать результат регистра push dword [ EBP + 20 ] ; передать некоторую переменную памяти (синтаксис FASM/TASM) push 3 ; передать некоторый постоянный вызов Calc ; возвращенный результат теперь находится в EAX            

Типичная структура вызываемого объекта: (некоторые или все (кроме ret) приведенные ниже инструкции могут быть оптимизированы с помощью простых процедур). Некоторые соглашения оставляют пространство параметров выделенным, используя Plain retвместо ret imm16. В этом случае вызывающая сторона могла бы add esp,12в этом примере или иным образом справиться с изменением ESP.

расчет: нажмите EBP ; сохранить указатель старого кадра mov EBP , ESP ; получить указатель нового кадра sub ESP , localsize ; зарезервируйте место в стеке для местных жителей . . ; выполнить вычисления, оставить результат в EAX . мов ESP , EBP ; свободное место для местных жителей поп EBP ; восстановить указатель старого кадра ret paramsize ; свободное пространство параметров и возврат.                      

х86-64

В 64-битной версии архитектуры x86, известной как x86-64 , AMD64 и Intel 64, обычно используются две последовательности вызовов. В Windows используется одна последовательность вызовов, определенная Microsoft; другая последовательность вызовов, указанная в AMD64 System V ABI, используется Unix-подобными системами и, с некоторыми изменениями, OpenVMS . Поскольку x86-64 имеет больше регистров общего назначения, чем 16-битный x86, оба соглашения передают некоторые аргументы в регистрах.

АРМ (А32)

Стандартное 32-битное соглашение о вызовах ARM распределяет 16 регистров общего назначения следующим образом:

Если тип возвращаемого значения слишком велик, чтобы поместиться в от r0 до r3, или его размер не может быть определен статически во время компиляции, то вызывающая сторона должна выделить место для этого значения во время выполнения и передать указатель на это пространство в r0.

Подпрограммы должны сохранять содержимое от r4 до r11 и указатель стека (возможно, сохраняя их в стек в прологе функции , затем используя их как рабочее пространство, а затем восстанавливая их из стека в эпилоге функции ). В частности, подпрограммы, вызывающие другие подпрограммы, должны сохранить адрес возврата в регистре связи r14 в стеке перед вызовом этих других подпрограмм. Однако таким подпрограммам не требуется возвращать это значение в r14 — им просто нужно загрузить это значение в r15, программный счетчик, чтобы вернуться.

Соглашение о вызовах ARM требует использования полного нисходящего стека. Кроме того, указатель стека всегда должен быть выровнен по 4 байтам и всегда должен быть выровнен по 8 байтам при вызове функции с открытым интерфейсом. [1]

Это соглашение о вызовах приводит к тому, что «типичная» подпрограмма ARM:

АРМ (А64)

Соглашение о вызовах 64-битного ARM ( AArch64 ) распределяет 31 регистр общего назначения следующим образом: [2]

Все регистры, начинающиеся с x , имеют соответствующий 32-битный регистр с префиксом w . Таким образом, 32-битный x0 называется w0.

Аналогично, 32 регистра с плавающей запятой распределяются следующим образом: [3]

RISC-V ISA

RISC-V имеет определенное соглашение о вызовах с двумя вариантами: с плавающей запятой или без нее. [4] По возможности он передает аргументы в регистры.

POWER, PowerPC и Power ISA

Архитектуры POWER , PowerPC и Power ISA имеют большое количество регистров, поэтому большинство функций могут передавать все аргументы в регистры для одноуровневых вызовов. Дополнительные аргументы передаются в стек, и место для аргументов на основе регистров также всегда выделяется в стеке для удобства вызываемой функции в случае, если используются многоуровневые вызовы (рекурсивные или иные) и регистры необходимо сохранить. Это также полезно в функциях с переменным числом аргументов , таких как printf(), где к аргументам функции необходимо обращаться как к массиву. Для всех процедурных языков используется единое соглашение о вызовах.

Инструкции ветвления и связи сохраняют адрес возврата в специальном регистре связи , отдельном от регистров общего назначения; процедура возвращается вызывающей стороне с инструкцией ветвления, которая использует регистр связи в качестве адреса назначения. Листовым процедурам не требуется сохранять или восстанавливать регистр связи; неконечные подпрограммы должны сохранять адрес возврата перед вызовом другой подпрограммы и восстанавливать его перед возвратом, сохраняя его с помощью команды «Переместить из регистра специального назначения», чтобы переместить регистр связи в регистр общего назначения и, при необходимости, затем сохранить его в стек и восстановить, если оно было сохранено в стеке, загрузив сохраненное значение регистра связи в регистр общего назначения, а затем используя команду «Переместить в регистр специального назначения» для перемещения регистра, содержащего сохраненную информацию. значение регистра связи в регистр связи.

МИПС

ABI O32 [5] является наиболее часто используемым ABI благодаря своему статусу исходного ABI System V для MIPS. [6] Он строго основан на стеке, и для передачи аргументов доступны только четыре регистра. Эта кажущаяся медлительность, а также устаревшая модель с плавающей запятой, состоящая всего из 16 регистров, способствовали распространению многих других соглашений о вызовах. ABI сформировался в 1990 году и никогда не обновлялся с 1994 года. Он определен только для 32-битного MIPS, но GCC создал 64-битный вариант под названием O64. [7]$a0-$a3

Для 64-разрядной версии чаще всего используется N64 ABI (не связанный с Nintendo 64 ) от Silicon Graphics. Самым важным улучшением является то, что теперь для передачи аргументов доступны восемь регистров; Это также увеличивает количество регистров с плавающей запятой до 32. Существует также версия ILP32 под названием N32, которая использует 32-битные указатели для меньшего кода, аналогично x32 ABI . Оба работают в 64-битном режиме процессора. [7]

Было предпринято несколько попыток заменить O32 32-битным ABI, который больше напоминает N32. На конференции 1995 года был предложен MIPS EABI, 32-битная версия которого была очень похожа. [8] EABI вдохновила MIPS Technologies предложить более радикальный ABI «NUBI», который дополнительно повторно использует регистры аргументов для возвращаемого значения. [9] MIPS EABI поддерживается GCC, но не LLVM; ни один из них не поддерживает NUBI.

Для всех O32 и N32/N64 адрес возврата хранится в $raрегистре. Это устанавливается автоматически с использованием инструкций JAL(переход и соединение) или JALR(регистр перехода и соединение). Стек растет вниз.

СПАРК

Архитектура SPARC , в отличие от большинства RISC- архитектур, построена на окнах регистров . В каждом окне регистров имеется 24 доступных регистра: 8 — «входящие» регистры (%i0-%i7), 8 — «локальные» регистры (%l0-%l7) и 8 — «выходящие» регистры (% о0-%о7). Регистры «in» используются для передачи аргументов вызываемой функции, а любые дополнительные аргументы необходимо помещать в стек . Однако вызываемая функция всегда выделяет пространство для обработки потенциального переполнения окна регистра, локальных переменных и (в 32-битном SPARC) возврата структуры по значению. Чтобы вызвать функцию, аргументы вызываемой функции помещаются в «выходные» регистры; когда функция вызывается, «выходные» регистры становятся «входящими» регистрами, и вызываемая функция получает доступ к аргументам в своих «входящих» регистрах. Когда вызываемая функция завершается, она помещает возвращаемое значение в первый входной регистр, который становится первым выходным регистром при возвращении вызванной функции.

ABI System V , [10] которому следуют большинство современных Unix -подобных систем, передает первые шесть аргументов во входные регистры от %i0 до %i5, резервируя %i6 для указателя кадра и %i7 для адреса возврата.

IBM System/360 и его преемники

IBM System/360 — еще одна архитектура без аппаратного стека. Приведенные ниже примеры иллюстрируют соглашение о вызовах, использовавшееся в OS/360 и его преемниках до появления 64-разрядной версии z/Architecture ; другие операционные системы для System/360 могут иметь другие соглашения о вызовах.

Вызов программы:

 LA 1,ARGS Загрузить адрес списка аргументов L 15,=A(SUB) Загрузка адреса подпрограммы BALR 14,15 Переход к вызываемой подпрограмме 1 ...ARGS DC A(FIRST) Адрес первого аргумента Постоянный ток А (ВТОРОЙ) ... DC A(THIRD)+X'80000000' Последний аргумент 2

Вызываемая программа:

SUB EQU * Это точка входа в подпрограмму.

Стандартная последовательность ввода:

 ИСПОЛЬЗОВАНИЕ *,15 3 STM 14,12,12(13) Сохранение регистров 4 ST 13,SAVE+4 Сохранить адрес области сохранения вызывающего абонента LA 12,SAVE Цепочка сохранений СТ 12,8(13) ЛР 13,12 ...

Стандартная последовательность возврата:

 Л 13,СОХРАНИТЬ+4 5 ЛМ 14,12,12(13) Л 15, РЕТВАЛЬ 6 BR 14 Возврат к абонентуСОХРАНИТЬ DS 18F Сохранить область 7

Примечания:

  1. Команда BALRсохраняет адрес следующей инструкции (адрес возврата) в регистре, указанном первым аргументом (регистр 14), и переходит к адресу второго аргумента в регистре 15.
  2. Вызывающая сторона передает адрес списка адресов аргументов в регистр 1. В последнем адресе установлен старший бит, обозначающий конец списка. Это ограничивает программы, использующие это соглашение, 31-битной адресацией.
  3. Адрес вызываемой процедуры находится в регистре 15. Обычно он загружается в другой регистр, и регистр 15 не используется в качестве базового регистра.
  4. Команда STMсохраняет регистры 14, 15 и с 0 по 12 в 72-байтовой области, предоставленной вызывающей стороной, называемой областью сохранения, на которую указывает регистр 13. Вызываемая процедура предоставляет свою собственную область сохранения для использования подпрограммами, которые она вызывает; адрес этой области обычно хранится в регистре 13 на протяжении всей процедуры. Следующие инструкции STMобновляют прямую и обратную цепочки, связывающие эту область сохранения с областью сохранения вызывающего абонента.
  5. Последовательность возврата восстанавливает регистры вызывающей стороны.
  6. Регистр 15 обычно используется для передачи возвращаемого значения.
  7. Статическое объявление saveareaв вызываемой подпрограмме делает ее нереентерабельной и нерекурсивной ; повторно входящая программа использует динамический объект savearea, полученный либо из операционной системы и освобождаемый при возврате, либо в памяти, переданной вызывающей программой.

В ABI System/390 [11] и ABI z/Architecture [12] используется в Linux:

Дополнительные аргументы передаются в стек.

СуперХ

Примечание: «сохраненные» резервы для сохранения вызываемого абонента; то же самое касается и «гарантированного».

68 тыс.

Наиболее распространенное соглашение о вызовах для серии Motorola 68000 : [13] [14] [15] [16]

ИБМ 1130

IBM 1130 представлял собой небольшую 16-битную машину с пословной адресацией. В нем было всего шесть регистров плюс индикаторы состояния и не было стека. Регистрами являются регистр адреса инструкции (IAR) , аккумулятор (ACC) , расширение аккумулятора (EXT) и три индексных регистра X1–X3. Вызывающая программа отвечает за сохранение ACC, EXT, X1 и X2. [17] Существуют две псевдооперации для вызова подпрограмм: CALLдля кодирования неперемещаемых подпрограмм, напрямую связанных с основной программой, и LIBFдля вызова перемещаемых библиотечных подпрограмм через вектор передачи . [18] Обе псевдооперации преобразуются в машинную инструкцию перехода и сохранения IAR ( BSI), которая сохраняет адрес следующей инструкции по ее эффективному адресу (EA) и выполняет переход к EA+1.

Аргументы следуют за BSI‍—‌обычно это адреса аргументов, состоящие из одного слова‍—‌вызываемая подпрограмма должна знать, сколько аргументов ожидать, чтобы она могла пропустить их при возврате. Альтернативно аргументы могут передаваться в регистрах. Подпрограммы функций возвращали результат в ACC для реальных аргументов или в ячейку памяти, называемую псевдоаккумулятором действительных чисел (FAC). Аргументы и адрес возврата были адресованы с использованием смещения значения IAR, хранящегося в первом месте подпрограммы.

 * 1130 пример подпрограммы ENT SUB Объявить «SUB» внешней точкой входа. SUB DC 0 Зарезервированное слово в точке входа, обычно кодируемое «DC *-*» * Код подпрограммы начинается здесь * Если были аргументы, адреса можно загрузить косвенно из адреса возврата. LDX I 1 SUB Загрузить в X1 адрес первого аргумента (например) ... * Возвратная последовательность LD RES Загрузить целочисленный результат в ACC * Если аргументы не были предоставлены, выполняется косвенный переход к сохраненному адресу возврата. BI SUB Если аргументы не были предоставлены КОНЕЦ ПОДПИСКИ

Подпрограммы в IBM 1130, CDC 6600 и PDP-8 (все три компьютера были представлены в 1965 году) хранят обратный адрес в первом месте подпрограммы. [19]

Соглашения о вызовах вне машинной архитектуры

Резьбовой код

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

Поточный код передает все аргументы в стек. Все возвращаемые значения возвращаются в стек. Это делает наивные реализации медленнее, чем соглашения о вызовах, которые сохраняют больше значений в регистрах. Однако реализации многопоточного кода, которые кэшируют в регистрах несколько значений верхнего уровня стека, в частности адрес возврата, обычно работают быстрее, чем соглашения о вызове подпрограмм, которые всегда помещают и извлекают адрес возврата в стек. [20] [21] [22]

ПЛ/И

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

Смотрите также

Рекомендации

  1. ^ «Стандарт вызова процедур для архитектуры ARM». 2021.
  2. ^ «Параметры в регистрах общего назначения». Руководство программиста серии ARM Cortex-A для ARMv8-A . Проверено 12 ноября 2020 г.
  3. ^ «Параметры в NEON и регистрах с плавающей запятой». Developer.arm.com . Проверено 13 ноября 2020 г.
  4. ^ «Соглашение о вызовах RISC-V» (PDF) .
  5. ^ «Краткий справочник набора инструкций MIPS32» .
  6. ^ Свитман, Доминик. См. MIPS Run (2-е изд.). Издательство Морган Кауфманн . ISBN 0-12088-421-6.
  7. ^ ab "История MIPS ABI" .
  8. Кристофер, Эрик (11 июня 2003 г.). «Документация mips eabi». [email protected] (список рассылки) . Проверено 19 июня 2020 г.
  9. ^ "НУБИ".
  10. ^ Дополнение к процессору SPARC для бинарного интерфейса приложения System V (3-е изд.).
  11. ^ «Дополнение к двоичному интерфейсу приложения S / 390 ELF» .
  12. ^ «Дополнение к двоичному интерфейсу приложения zSeries ELF» .
  13. ^ Смит, доктор Майк. «Сравнение регистров SHARC (21k) и 68k».
  14. ^ XGCC: Языковая система Gnu C/C++ для разработки встраиваемых систем (PDF) . Корпорация инструментов встроенной поддержки. 2000. с. 59.
  15. ^ «COLDFIRE/68K: ThreadX для семейства Freescale ColdFire» . Архивировано из оригинала 2 октября 2015 г.
  16. ^ Мошовос, Андреас. «Продолжение подпрограмм: передача аргументов, возврат значений и распределение локальных переменных». все регистры, кроме d0, d1, a0, a1 и a7, должны сохраняться во время вызова.
  17. ^ Корпорация IBM (1967). Система IBM 1130 Disk Monitor, версия 2. Введение в систему (C26-3709-0) (PDF) . п. 67 . Проверено 21 декабря 2014 г.
  18. ^ Корпорация IBM (1968). Язык ассемблера IBM 1130 (C26-5927-4) (PDF) . стр. 24–25.
  19. ^ Смотерман, Марк (2004). «Поддержка вызовов подпрограмм и процедур: ранняя история».
  20. ^ Родригес, Брэд. «Движение вперед, часть 1: проектные решения в ядре Forth». На 6809 или Zilog Super8 DTC работает быстрее, чем STC.
  21. ^ Эртл, Антон. «Скорость различных методов отправки переводчиков».
  22. ^ Залески, Мэтью (2008). «Глава 4: Разработка и реализация эффективной интерпретации». YETI: постепенно расширяемый интерпретатор трассировок . Хотя известно, что интерпретаторы с прямым потоком имеют плохие свойства прогнозирования ветвления... задержка вызова и возврата может быть больше, чем при косвенном переходе.

Внешние ссылки