вторник, 9 декабря 2008 г.

История соглашений вызова, часть 1

Это перевод The history of calling conventions, part 1. Автор: Реймонд Чен.

Хорошо, когда вокруг так много соглашений вызовов: есть из чего выбирать!

В 16-ти битном мире, часть соглашения вызова была зафиксирована набором инструкций: регистр BP был селектором для сегмента SS, а остальные регистры были привязаны к DS. Поэтому регистр BP был необходим для доступа к параметрам в стеке.

Регистры для возвращаемых значений также выбирались исходя из набора инструкций. Регистр AX работал как накопитель (аккумулятор) и поэтому был естественным выбором для возвращения через него результата функции. В наборе инструкций 8086 были также специальные команды, которые оперировали с парой регистров DX:AX, интерпретируя их как одно 32-х битное число, поэтому эта пара также была естественным выбором для возврата 32-х битных значений.

Оставались свободными регистры SI, DI, BX и CX.

(Терминологическое примечание: регистры, которые не нужно сохранять при вызове функции часто называются свободно модифицируемыми ("scratch"-регистрами)).

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

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

Одним из факторов также стала и неравномерность набора инструкций x86. Регистр CX не мог использоваться для доступа к памяти, поэтому вам, вероятно, хотелось бы, чтобы какой-нибудь другой регистр был бы свободным, таким образом листовые функции могли бы обращаться к памяти, без необходимости сохранения каких-либо регистров. Поэтому свободным был выбран регистр BX, а SI и DI стали сохраняемыми.

Итак, вот краткая выдержка 16-ти битных соглашений вызова:

Все модели
Все модели вызова в 16-ти битном мире сохраняют регистры BP, SI, DI (остальные - свободны) и размещают результат функции в DX:AX или AX - в зависимости от размера.

C (cdecl)
Это единственный выбор для функций с переменным числом аргументов. Переменные аргументы требуют, чтобы стек очищался вызывающим и чтобы параметры передавались справа-налево, так что первый параметр всегда находился бы в фиксированной позиции относительно вершины стека. Классический (пре-прототипный) язык C позволял вам вызывать функции без сообщения компилятору, какие им требуются параметры. И частой практикой была передача неверного числа параметров в функцию, если вы "знали", что вызываемую функцию они не волнуют (например, классический пример - функция "open": третий параметр опционален, если второй параметр не указывает файл для создания).

Суммируя: вызывающий чистит стек, параметры передаются справа-налево.

Имена функций декорировались знаком подчёркивания в начале. Я подозреваю, что ведущее подчёркивание не позволяло имени функции совпасть с зарезервированным словом ассемблера (представьте, к примеру, если бы у вас была функция с именем "call").

Pascal (pascal)
Соглашение вызова Pascal не поддерживает функции с переменным числом параметров, так что оно может использовать очистку стека вызываемым. Параметры передаются слева-направо, потому что, ну, это же так естественно выглядит. Декорирование имён функций заключалось в приведении их имён в верхний регистр. Это было необходимо, потому что Паскаль не был языком, чувствительным к регистру.

Почти все функции Win16 экспортировались с моделью вызова Pascal. Очистка стека вызываемым позволяла экономить три байта на каждый вызов, с разовым перерасходом двух байт на саму функцию. Поэтому, если функцию вызывали десять раз, вы экономили 3*10 = 30 байт на вызовах и платили за это только 2 байта в самой функции, получая суммарный выигрыш в 28 байт. И это также работало немного быстрее. В мире Win16 экономия нескольких сотен байт и нескольких циклов процессора значила много.

Fortran (fortran)
Модель вызова Fortran - такая же как и соглашение Pascal. У этого соглашения было отдельное имя, вероятно, потому что в языке Fortran было странное поведение для передачи данных по ссылке.

Fastcall (fastcall)
Соглашение вызова Fastcall передаёт первый параметр в регистре DX, а второй - в регистре CX (мне так кажется). Будет ли это работать действительно быстрее - зависит от того, как вы используете вызовы. В общем случае это работает быстрее, т.к. параметры передаётся в регистрах и поэтому их не нужно сохранять/загружать в медленный стек. С другой стороны, если между использованием первого и второго параметра будут идти какие-то интенсивные вычисления, то вызываемому и так придётся сохранять параметры в стек. Но что ещё хуже, вызываемая функция часто копировала регистр в память, потому что ей нужен был свободный регистр для другой работы, что в случае "интенсивные вычисления между использованием параметров" превращалось в двойное копирование. Ой!

Соответственно, fastcall обычно работал более быстро для коротких листовых функций и то не всегда.

Окей, это все 16-ти битные соглашения по вызову, что я смог вспомнить. Часть 2 расскажет о 32-х битных соглашениях.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.