Зачем нужен регистр SPL

image-loader.svg

Так и тянет меня задать в заголовке статьи вопрос, что по здешним правилам не допускается. А ответ опять очевиден: регистр SPL вообще не нужен.

Я уже давно выступал с критикой системы команд AMD64, сейчас более известной как x86–64. Причем, задача специально анализировать появившиеся и исчезнувшие команды не стояла. Просто при переносе средств программирования с Win32 на Win64 возникал ряд проблем, вызывавших один и тот же вопрос: «почему же раньше все работало, а теперь нет?». Это касается некоторых выброшенных разработчиками архитектуры AMD64 команд, которые пришлось эмулировать, и, особенно, аппаратной поддержки контроля целочисленного переполнения с помощью инструкции INTO, которая вдруг стала недоступной.

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

Но все-таки проблемы как-то разрешились, и пришло время не только бороться с недостатками системы команд AMD64, но и воспользоваться ее достоинствами. А основных достоинств, по сравнению с IA-32, напомню, два: восьмибайтная адресация, снимающая предел в 4 Гбайт, и увеличенное число регистров общего назначения в два раза.

В случае регистров размером в 2, 4 или 8 байт действительно все логично и естественно. Можно даже сказать, что число регистров увеличилось более чем в два раза, поскольку указатель стека и не используется в вычислениях как остальные. Поэтому в IA-32 у программиста реально было 7 регистров общего назначения, а в AMD64 их стало 15, т.е. RAX, RBX, RCX, RDX, RBP, RSI, RDI и R8-R15.

Но вот в случае регистров размером в байт не все так логично. Немного отвлекаясь, замечу, что объектам размером в байт вообще «не везет» в системах программирования. По сравнению с объектами больших размеров они выглядят какими-то ущербными.

Помнится, в «Турбо»-Паскале был всего один тип переменной размером в байт — это CHAR, т.е. символ. Но это еще ладно. А вот в стандарте Си CHAR — это вообще почему-то не символ, а целое число.

Мне это потому кажется странным, что в языке PL/1, который я использую, объекты размером в байт — это самые обычные объекты. Только маленькие. Есть целое знаковое число с атрибутами BINARY FIXED (7), есть строка бит BIT (8), есть строка символов CHAR (1). Да и логические данные с атрибутами BIT (1) при вычислениях все равно обычно занимают весь байтный регистр, поскольку так намного удобнее.

Я это все веду к тому, что регистры с размером в байт очень часто нужны и используются в самых разных задачах. Поэтому увеличение числа таких регистров можно только приветствовать. Тем более что разработчики системы команд AMD64 предложили неожиданный бонус: регистров стало не 16, как можно было предположить, а целых 20.

Так как к восьми имевшимся AL, AH, BL, BH, CL, CH, DL, DH добавлены R8L-R15L, а также еще SPL, BPL, SIL и DIL.

Но когда я приступил к доработкам транслятора в части распределения байтовых регистров, возникло чувство, которое иногда бывает при настойчивой рекламе «торговых скидок». Вроде и платишь меньше, а в конечном итоге как-то так получается, что столько же (а то и больше).

Первая ложка дегтя — появление регистра SPL, т.е. младшего байта указателя стека. Вопрос для чего он нужен, я и вынес в заголовок. Единственная, хоть какая-то полезная операция, которую я смог для него придумать — это проверка указателя стека на кратность 8 или 16. В самом деле, если проверять весь указатель стека RSP на кратность 8 потребуется команда в 7 байт:

48F7C407000000        test rsp,7

А если использовать регистр SPL, то только 4:

40F6C407              test spl,7

Но это сущая мелочь. И, кстати, если использовать не RSP, а двухбайтный SP, команда станет лишь на байт длиннее:

66F7C40700            test sp,7

И для выравнивания стека на 16, которое часто требуется для обращения к Win API, наличие регистра SPL вообще не дает преимуществ, сравните:

4080E4F0            AND SPL,0F0H
6683E4F0            AND SP,0FFF0H

Безо всякого SPL можно выполнить такое же действие и командой такой же длины. Т.е. этот регистр бесполезен и просто занимает номер (№4) в системе кодов команд. Так что, реально можно использовать уже не 20 регистров, а 19.

Но хуже другое. Дополнительные «бонусные» регистры SPL, BPL, SIL и DIL используют те же коды, что AH, BH, CH, DH (т.е. 4, 5, 6, 7). Это приводит к тому, что «старые» регистры нельзя использовать вместе с «бонусными» в одной команде. Например, нельзя написать команду:

408AE6              mov ah,sil

Потому, что процессор просто воспримет ее как:

408AE6              mov spl,sil

Поскольку коды регистров AH и SPL одинаковые — 4.

А для задачи распределения регистров при компиляции очень важно, чтобы все регистры были «совместимы», т.е. чтобы их легко можно было присваивать друг другу. Потому что часто байтовый регистр нужно сохранить, а затем восстановить. Эффективнее всего запомнить его в другом байтовом регистре. Но, например, если нужно сохранить регистр AH, а свободны в этот момент R8L-R15L, нельзя это сделать командой:

mov r8l,ah

а потом восстановить командой:

mov ah,r8l

Вот и получается, что надо или вообще не использовать AH, BH, CH, DH и тогда байтовых регистров остается 15 (а если бы не делали «бонусные» регистры их реально было бы 16), или как-то эмулировать отсутствующие команды. Например, несуществующую команду:

mov ah,sil

Можно эмулировать тремя командами:

86E0                xchg ah,al
408AC6              mov al,sil
86E0                xchg ah,al

Но все это начинает убивать преимущества дополнительных байтовых регистров.

На мой взгляд, разработчики системы команд AMD64 в данном случае не использовали все возможности.

Обратите внимание, что новые по сравнению с IA-32 команды реализованы с помощью REX-префиксов. По сути это просто часть кода команды, вынесенная в отдельный байт. И вот в этом REX-байте только 4 бита несут информацию. Три — это старшие биты номеров регистров-операндов команды (что и дает формальное удвоение их числа), а четвертый — это признак «W», определяющий работу данной команды с 8 или 4 байтами.

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

408AF4              mov sil,spl ; бит «W» сброшен
488AF4              mov sil,spl ; бит «W» установлен, но игнорируется

Этот неиспользуемый бит «W» и надо было бы тоже задействовать для указания типов байтовых регистров. Например, если бит сброшен, то считать, что по-прежнему используются «старые» восемь регистров AL, AH, BL, BH, CL, CH, DL, DH. Тогда бы и получилось 16 (а не 15 + ненужный SPL) нормальных полностью независимых и «совместимых» регистров, которые любым образом можно было бы использовать в одной команде.

Если же бит «W» установлен, то считать, что это дополнительные регистры. Хотя я бы ввел не BPL, SIL, DIL и уж тем более не бессмысленный SPL, а вторые байты регистров R8-R15, т.е. ввел бы дополнительные регистры R8H-R15H, которые и имели бы в этом случае коды 0–7.

Тогда бы получилось не 20, а даже 24 байтовых регистра, и можно было бы хранить по две независимые переменные (размером в байт) во всех регистрах RAX, RBX, RCX, RDX, R8-R15.

Правда, все равно «старые» и «бонусные» регистры в одной команде было бы использовать нельзя. Одного признака «W», увы, для возможности таких команд мало.

Т.е. можно было бы писать команды типа:

and r12l,r13h

Но нельзя писать:

and ah,r13h

Поскольку нет способа указать, какой из двух операндов «старый», а какой — «бонусный» из AMD64.

Заключение

Разумеется, разработка системы команд это очень трудоемкое и ответственное дело. Наверняка при разработке системы команд AMD64 был проведен анализ реальных программ и обработана статистика использования регистров, что и привело к предложению ввести дополнительные регистры размером в байт.

Но, возможно, при этом рассматривались как раз системы программирования, где байтные объекты являются «пасынками» и поэтому просто не попалось программ «нормально» работающих с регистрами AH, BH, CH, DH, хотя это совершенно обычные регистры при манипулировании объектами размером в байт. Эти регистры также позволяют удобно хранить по две независимые переменные в одном физическом регистре и не тратить команды на распаковку.

В результате стройная система, идущая еще от 8-разрядных микропроцессоров, когда все байтные регистры равноправны и могут встречаться в одной команде, была (на мой взгляд, не совсем обоснованно) нарушена. К тому же появился просто бесполезный регистр SPL. Целесообразней вместо SPL было бы оставить AH.

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

© Habrahabr.ru