Первая публикация 18.05.2013
On possible enhancements of the IA-32 processor’s instruction set
Введение
Как-то на одном из компьютерных форумов автор статьи прочитал обсуждение, насколько удачно разработана система команд x86 или, правильнее, IA-32/IA-32e. Большинство участников этого обсуждения сошлись на том, что система команд разработана плохо. «Команда цикла работает только с ECX. А если я хочу использовать другой регистр?» — вот типичный пример высказанных претензий. Скорее всего, этим программистам на глаза не попалась книга [1], изданная в нашей стране еще в 1990 году. Книга интересна тем, что в ней авторы (один из которых разработчик архитектуры х86) не только рассказывают, как устроена система команд, но и в ряде случаев объясняют, почему разработчики сделали именно так. Это довольно редкий случай разъяснения замысла, а не просто реализации. Подобное разъяснение замыслов разработчиков автор встречал, пожалуй, только еще в прекрасной книге [2] Пентковского, посвященной процессору «Эльбрус».
Прошу прощения за довольно пространную цитату из указанной старой книги о процессоре 80286, но она могла бы быть одним из ответов на обвинения разработчиков в неразумности:
«Специализация РОНов (регистров общего назначения – Д.К.) затрудняет изучение процессора из-за необходимости помнить специальные правила. Она же несколько увеличивает длину программы, так как до выполнения некоторых команд приходится пересылать данные из одного РОНа в другой. Однако рассмотрим разработку программы для процессора, в котором все РОНы одинаковы. Чтобы следить за ее выполнением, нам, вероятно, придется организовать программу так, чтобы определенные данные всегда находились в конкретных регистрах. Например, можно договориться всегда использовать регистр СХ для учета числа элементов цепочки. Тогда нам никогда не придется пересылать размер цепочки в регистр СХ, так как он всегда будет там. Но поскольку цепочечная команда в нашем гипотетическом процессоре может получать размер цепочки из любого РОНа, каждая цепочечная команда должна указывать, где находится размер цепочки. Для этого придется либо увеличить длину каждой цепочечной команды (два байта вместо одного), либо ввести больше однобайтных цепочечных команд. Первое решение прямо ведет к увеличению программы, а второе имеет такие же последствия. Действительно, всего может быть только 256 однобайтных команд, и увеличение числа однобайтных цепочечных команд заставит удлинить другие однобайтные команды до двух байт. Таким образом, специализация РОНов в некоторых командах уменьшает длину программы».
Данный ответ годится и для команды цикла, а также иллюстрирует то обстоятельство, что не так-то просто предложить усовершенствование системы команд без основательного анализа. В частности, требование сделать команду цикла симметричной относительно всех регистров в большинстве случаев только раздует код программы, о чем и пишут разработчики системы команд. Причем, даже в случае двух вложенных циклов, где команда цикла с разными регистрами кажется более выгодной, пары дополнительных команд сохранения и восстановления регистра ECX при существующей команде цикла потребуют тех же двух байт, что и гипотетическое общее увеличение длины из-за каждой команды LOOP и, следовательно, сокращение кода из-за новой команды в двух вложенных циклах тоже не получится.
Но в принципе, конечно, усовершенствование давно разработанной системы команд x86 вполне возможно. Далее рассматривается один из таких возможных вариантов внесения улучшений в систему команд x86.
Предпосылки к совершенствованию системы команд
Примером усовершенствования команд x86 служит появление 64-разрядных команд IA-32e. За счет исключения дублирования команд инкремента и декремента было освобождено кодовое «пространство» в таблице «первичных» кодов команд и добавлены новые «префиксные» команды, предоставляющие обычным командам большие возможности, например, позволяющие использовать большее число регистров. Следует отметить, что в данной статье речь идет именно об усовершенствовании системы команд, а не просто о добавлении новых и новых команд. Понятно, что, зарезервировав один байт в таблице «первичных» кодов, один байт в таблице «вторичных» кодов и т.д., можно добавить очень большое число новых команд, которые, однако, будут иметь все более и более длинные коды. Поэтому предпосылкой к совершенствованию должно стать очередное освобождение имеющегося кодового «пространства» без необходимости увеличения длины самих команд.
В «первичных» кодах существующей сегодня системы команд IA-32/IA-32e находится 12 необходимых, но редко (по частоте появления в программах) используемых команд обращения к портам ввода-вывода. Это «цепочечный» ввод-вывод, который содержит четыре команды INSB, INSD, OUTSB, OUTSD, а также восемь основных команд ввода-вывода IN AL, IN EAX, OUT AL, OUT EAX относительно регистра DX и в формате с непосредственным номером порта. Рассмотрим, как процессор реализует команды ввода-вывода. В первых процессорах был специальный выходной сигнал INOUT, который затем стал называться M/IO#, затем отдельный сигнал исчез и в современных процессорах довольно сложным образом выдается так называемый «тип транзакции». Но в любом случае процессор сигнализирует аппаратуре, что это не обращение к обычной памяти, а обращение именно к пространству портов ввода-вывода. Несмотря на все эти аппаратные тонкости, реализация ввода-вывода остается одинаковой: на адресную шину процессор выставляет значение порта, на шину данных – данные из регистра AL/EAX (или наоборот, данные в AL/EAX читаются с шины данных). Например, если регистр EDX содержит значение 378H, то команды:
MOV AL,[EDX]
IN AL,DX
с точки зрения сигналов на шине процессора одинаковы и отличаются только признаком (в виде «типа транзакции») обращения к портам ввода-вывода.
Таким образом, если в систему команд ввести новую «префиксную» однобайтную команду (похожую по применению на команду LOCK), которая указывает процессору дать сигнал обращения к портам, то все имеющиеся команды ввода-вывода можно заменить обычными командами с обращением к памяти и с этим новым префиксом. Для конкретности пусть такая новая команда имеет код 6СH, который теперь «свободен», и аббревиатуру INOUT, тогда соответствие имеющихся и новых команд приведено ниже.
Существующая команда Заменяющая команда с префиксом INOUT
6C……..INSB……………………………………..6CAA………………….INOUT STOSB
6D……..INSD……………………………………..6CAB…………………..INOUT STOSD
6E……..OUTSB…………………………………..6CAC………………….INOUT LODSB
6F……..OUTSD…………………………………..6CAD………………….INOUT LODSD
EC……..IN AL,DX……………………………….6C8A02………………INOUT MOV AL,[EDX]
ED……..IN EAX,DX…………………………….6C8B02………………INOUT MOV EAX,[EDX]
EE……..OUT DX,AL…………………………….6C8802………………INOUT MOV [EDX],AL
EF……..OUT DX,EAX…………………………..6C8902……………….INOUT MOV [EDX],EAX
E480….IN AL,80H………………………………6C8A068000……….INOUT MOV AL,.80H
E580….IN EAX,80H……………………………6C8B068000………..INOUT MOV EAX,.80H
E680….OUT 80H,AL…………………………..6C88068000…………INOUT MOV .80H,AL
E780….OUT 80H,EAX…………………………6C89068000…………INOUT MOV .80H,EAX
При этом в новых командах вовсе не обязательно использовать именно AL, EAX и EDX.
Таким образом, введение нового «префикса» INOUT позволило бы освободить еще 11 байт из «первичного» кодового «пространства» команд, однако при этом все команды обращения к портам удлинились бы. Впрочем, поскольку в реальных программах обращения к портам встречаются редко, увеличение кода из-за применения новых команд ввода-вывода было бы небольшим.
Примеры усовершенствованных команд
Одиннадцать новых команд с начальными кодами 6DH-6FH, E4H-E7H и ECH-EFH, появление которых становится возможным после исключения существующих команд ввода-вывода, могут быть добавлены для совершенно разных целей. Например, можно продолжить линию развития в сторону увеличения разрядности команд (до 128), подобно тому, как это сделано в IA-32e, где добавлено целых 16 новых «префиксов», указывающих, в том числе, и на разрядность.
Однако здесь рассматриваются примеры более простых усовершенствований, а в качестве критерия совершенства принята длина команд. Также для примера рассматривается, как повлияли бы такие команды на код ядра Windows-XP, т.е. на программу NTOSKRNL.EXE версии от 14.04.08, имеющей длину 2145280 байт. Данная программа работает на большом количестве компьютеров и ее эффективность очень важна. Хотя возникают сомнения в том, что разработчики много времени уделяют повышению ее эффективности. Например, после того, как было обнаружено, что в предыдущих версиях этой программы был неправильно сгенерирован код команд типа PUSH DS (везде добавлялся безвредный, но бессмысленный префикс 66H) и в течение нескольких лет никто из разработчиков этого не замечал.
С точки зрения автора, было бы целесообразно добавить новую форму команды возврата из подпрограммы с очисткой стека. В существующей форме этой команды размер очищаемого стека занимает 2 байта, хотя значение больше 255 байт в реальных программах встречается крайне редко. Введя новую форму этой команды с операндом-байтом, можно сократить размер кода. Для конкретности примем, что код новой формы команды — один из «освободившихся» кодов 6DH, тогда:
Существующая команда Новая форма команды
C20800 RET 8 6D08 RET 8
По подсчетам автора команда возврата с очисткой стека встречается в программе NTOSKRNL.EXE 5318 раз и во всех этих случаях размер очистки стека не превышает байта. Таким образом, ввод новой формы команды позволил бы сократить размер всей программы на 5 Кбайт или на 0.25%.
Еще одним примером возможного усовершенствования является добавление отсутствующей команды явного обнуления операнда. Используемая обычно для обнуления операнда-регистра команда XOR меняет состояние флагов процессора, а обнуление операнда в памяти требует указания в команде непосредственного операнда-нуля. Команда явного обнуления могла бы иметь аббревиатуру CLR (как когда-то команда для СМ-4) и иметь только один операнд в памяти или в регистре, подобно командам NEG или NOT. Для конкретности примем, что коды новых команд обнуления — это «освободившиеся» коды 6EH и 6FH, тогда:
Существующая команда Новая форма команды
C6050000000000……………MOV B PTR X,0………..6E1500000000………CLR B PTR X
66C705000000000000…….MOV W PTR X,0……….666F1500000000…..CLR W PTR X
C7050000000000000000..MOV D PTR X,0………..6F1500000000……….CRL D PTR X
B800000000……………………MOV EAX,0………………6FD0………………………CLR EAX
Хотя команды обнуления операндов в памяти встречаются не так часто, как возврат с очисткой стека, все же в программе NTOSKRNL.EXE по подсчетам автора обнуление операнда-байта в памяти встретилось 1236 раз, операнда-слова – 24 раза и операнда-двойного слова – 155 раз. Таким образом, сокращение кода при вводе команды явного обнуления составило бы 1236+24*2+155*4=1904 байта или 0.1% от размера всей программы. Кроме этого, использование команды CLR облегчило бы чтение программы на ассемблере, поскольку оно (в отличие от XOR) явно обозначает намерение программиста.
Оставшиеся «свободными» еще 8 кодов первичного «пространства» могут быть использованы по-разному. Возможно, для IA-32e имеет смысл вернуться к однобайтовым формам команд инкремента и декремента хотя бы для регистра EAX, поскольку, например, в NTOSKRNL.EXE команда INC EAX встречается около 2000 раз, т.е. довольно часто. Но возможно, выгоднее использовать эти коды для улучшения совсем других команд. В любом случае разработчики должны делать это обоснованно, в результате анализа текстов существующих программ.
Совместимость
Все предложенные выше изменения, как, например, и расширение IA-32e, являются несовместимыми с предыдущими системами команд x86. Для использования новых форм команд потребуются, естественно, новые версии трансляторов. Поскольку в предлагаемом варианте изменения затрагивают лишь небольшую часть команд, то было бы целесообразно ввести в процессор еще один, устанавливаемый программой в регистре EFLAGS флаг, включающий режим новой интерпретации некоторых кодов, в данном случае 6СH-6FH, E4H-E7H и ECH-EFH. При переключении задач такой флаг запоминался бы в контексте состояния процессора, и было бы возможно одновременное исполнение «старых» и «новых» команд в разных программах. По мере появления новых поколений трансляторов и вывода старых программ из эксплуатации, проблема совместимости имела бы все меньшее и меньшее значение.
Заключение
Как показано выше, усовершенствование даже такой давно используемой системы команд, как x86, вполне возможно. Например, удлинив формат некоторых редко используемые команд, можно «освободить» коды для создания более коротких форм часто используемых команд и добиться, тем самым, общего сокращения кода. Сокращение кода, в свою очередь, упростит дешифровку команд процессором, улучшит использование кэш и увеличит скорость выполнения программы.
Однако предложения по улучшению должны опираться на анализ существующего программного обеспечения и ясное понимание замыслов разработчиков архитектуры процессоров.
Несмотря на проблемы совместимости (не такие уж непреодолимые, как показывает опыт внедрения IA-32e) совершенствование систем команд является одним из главных направлений общего прогресса вычислительной техники.
Литература
- Морс С.П., Алберт Д.Д. Архитектура микропроцессора 80286: Пер. с англ. М.: Радио и связь, 1990
- В.М.Пентковский Автокод Эльбрус. Эль-76. Принципы построения языка и руководство к использованию. М.: Наука, 1982