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

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

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

Третья часть.

Архитектура ia-64 (Itanium) и архитектура AMD64 (AMD64) относительно новые, так что маловероятно, чтобы многие из вас имели дело с соглашениями вызова на этих платформах, но я включу их обзор в эту серию, потому что, кто знает, может однажды у вас появится такая машина.

У Intel есть Intel® Itanium® Architecture Software Developer's Manual (мануал разработчика программ под архитектуру Intel® Itanium®), который вы можете почитать, если вам нужна экстраординарно детализированная информация по набору инструкций и архитектуре процессора. Я собираюсь описать только модель вызова.

У процессора Itanium есть 128 целочисленных регистров, 32 из которых (от r0 до r31) глобальны и не участвуют в вызовах функций. Сама функция говорит процессору, как она будет использовать оставшиеся 96 регистров: сколько из них она хочет использовать локально ("local region"), часть первых регистров которых будут использоваться для передачи параметров, и сколько из них использовать для передачи параметров в другие функции ("output registers").

Например, предположим, что функция принимает два параметра, требует четырёх регистров для локальных переменных и вызывает функцию с тремя параметрами (если она вызывает больше одной функции, то возьмите функцию с максимальным числом параметров). Тогда функция объявляется как функция, которая локально использует шесть регистров (это будут r32-r37) и три выходных регистра (это r38, r39 и r40). Регистры от r41 до r127 не используются.

Примечание для педантов: я знаю, что в действительности это работает не так, но всё намного проще объясняется таким способом.

Когда наша функция хочет вызвать вложенную функцию, то она заносит первый параметр в r38, второй - r39, а третий - в r40, затем вызывает функцию. Процессор смещает (shift) выходные регистры вызывающего, так что они будут действовать как входные регистры для вызванной функции. В нашем случае регистр r38 копируется в r32, r39 - в r33, а r40 - в r34. Старые регистры r32-r38 сохраняются в регистровом стеке - стеке, отличном от обычного стека, на который указывает регистр sp (разумеется, в реальности, перенос откладывается - так же, как на SPARC регистровые окна (register windows) не заносятся в стек, пока это не потребуется. Фактически, можно смотреть на всю модель передачи параметров на ia64 как на те же регистровые окна SPARC, но только окна имеют переменный размер!).

Когда вызываемая функция возвращает управление, регистры возвращаются на свои предыдущие позиции и оригинальные значения регистров r32-r38 восстанавливаются с регистрового стека.

Такая схема даёт несколько удивительных ответов на некоторые традиционные вопросы о соглашениях вызова.

Какие регистры должны сохраняться во время вызова? Все в вашем локальном регионе (поскольку они автоматически сохраняются и восстанавливаются самим процессором).

Какие регистры содержат параметры? Ну, они располагаются в выходных регистрах вызывающего, они могут меняться в зависимости от того, сколько регистров вызывающий использует в своей локальной области, но вызываемый в любом случае видит их как r32, r33 и т.д.

Кто очищает параметры со стека? Никто. Параметры не располагаются в стеке.

В каком регистре возвращается результат? Ну, это хороший вопрос. Т.к. регистры вызывающего не доступны в вызываемом, вы можете подумать, что вернуть значение из функции и вовсе невозможно! И тут на сцену выходят 32 глобальных регистра. Один из глобальных регистров (r8, если я не путаю) объявлен как "регистр для возвращаемых значений". Т.к. глобальные регистры не участвует в волшебной передаче параметров, то значение, помещённое в них, сохраняется между вызовами функций.

Адрес возврата обычно сохраняется в одном из регистров в локальной области. Приятным побочным эффектом такой модели является невозможность перезаписи адреса возврата из-за ошибок переполнения буфера: поскольку адрес возврата более не хранится в стеке. Он хранится в локальном регионе, который может выгружаться в регистровый стек - область памяти, совершенно никак не связанную со стеком программы.

При желании функция может сделать вычитание из регистра sp для создания временного стека (для строковых буферов, например), который, конечно же, она обязана подчистить перед выходом.

В этой модели есть одна забавная деталь: первые 16 байт стека (первые два quadword) всегда свободны (Питер Лунд (Peter Lund) называет их "красной зоной" ("red zone")). Поэтому, если вам нужно немного памяти на короткое время - вы всегда можете просто использовать память в вершине стека, без необходимости выделять её специально. Но помните, что если вы вызываете любую функцию, то эта память может быть использована вызываемой вами функцией! Поэтому, если вам нужно сохранять значение этого "свободного блокнота" между вызовами, то вам придётся делать вычитание из sp для официального резервирования места.

И ещё одна забавная деталь о ia64: указатель на функцию не указывает на первый байт кода функции. Вместо этого он указывает на структуру, которая описывает функцию. Первые 8 байт - это адрес первого байта кода, а вторые 8 байт содержат значение так-называемого "gp" регистра. Мы узнаем больше об этом gp регистре в следующий раз.

(Этот трюк с "указатель на функцию на самом деле указывает на запись" не эксклюзивен для ia64. Он часто используется на RISC-машинах. Мне кажется, что это также использовал PPC).

Окей, я признаю, это был действительно скучный пост. Но верите вы мне или нет, но я собираюсь скоро сослаться на несколько пунктов в этом посте, так что это всё было сказано не просто так.

Читать далее.

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

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

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

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

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

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

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