Введение
Много лет назад американским специалистом Гарри Килдэллом (Gary Kildall) в рамках создания системы программирования для персональных компьютеров был разработан транслятор с языка ассемблера для процессора Intel 8086, который он назвал RASM-86 (Relocating ASseMbler). Этот во многом типичный для своего времени продукт имел особенность: он позволял, не меняя транслятора, добавлять описания новых команд процессора с помощью специальных макросредств.
Автор статьи, используя и развивая этот транслятор, успешно применял данные средства по мере появления новых поколений процессоров. Конечно, иногда и сам транслятор требовал ряда доработок, например, при переходе на архитектуру IA-32, а затем и на x86-64 (IA-32e). Тем не менее, изначально заложенная идея позволила легко продолжать эволюцию транслятора до настоящего времени. Некоторые итоги этой работы рассматриваются далее.
Организация генерации команд в трансляторе с ассемблера
Обычно программистам безразлично, как именно организована генерация кодов команд внутри используемого ими транслятора. Однако, поскольку в данном случае можно влиять на этот процесс, рассмотрим на примере конкретного транслятора ход генерации.
Транслятор RASM имеет общую внутреннюю таблицу всех возможных команд процессора. Элементы таблицы начинаются с текста мнемоники команды, за которым следует связанный список всех возможных форм данной команды в зависимости от числа, типа и размера ее операндов. Содержимое очередной формы команды состоит из последовательности «микрокодов», каждый из которых представляет отдельно обрабатываемую и уже неделимую часть команды.
Сначала транслятор ищет заданную команду в таблице по ее мнемонике, а затем, идя по связанному списку, ищет подходящую комбинацию операндов. Если заданной комбинации операндов (включая и случай отсутствия операндов в команде) не найдено – выдается сообщение об ошибке. Иначе транслятор читает последовательность «микрокодов» и выполняет каждый из них, составляя, таким образом, код заданной команды. При этом частные случаи комбинации операндов должны располагаться ранее общих случаев. Например, если команда имеет отдельную форму для операнда-байта, расширяемого со знаком, такой операнд должен встретиться в связанном списке раньше общего случая операнда-константы иначе, естественно, будет находиться только общий случай.
Идея Килдэлла состояла в том, что программист с помощью специальных макросредств может описать новую команду или новую форму с новой комбинацией операндов для уже существующей команды или даже повторить уже существующую комбинацию операндов, но указать новое содержимое. Команда описывается по тем же правилам, по которым были первоначально описаны все команды при создании самого транслятора.
Трехпроходный транслятор RASM на первом проходе переведет это описание в последовательность «микрокодов» и вставит новое описание в общую таблицу команд. В этом принципиальное отличие данных макросредств от «обычных», которыми можно изменить исходный текст программы, но нельзя изменить содержимое самого транслятора. Если такая команда уже существовала, новое описание добавляется в начало имеющегося связанного списка. Если уже существовала и такая команда и такая комбинация операндов для нее – новое описание отменит предыдущее, так как будет вставлена «выше». После этого в тексте программы можно свободно использовать новую команду так же, как если бы она изначально была представлена в таблице транслятора.
Макросредства описания команд
По виду специальные макросредства RASM похожи на обычные средства макроподстановки: имеются ключевые слова CodeMacro и EndM, между которыми пишется «тело» макроопределения. В первой строке пишется имя макро и, возможно, список его параметров. Например:
CodeMacro AAA
DB 37H
EndM
CodeMacro DIV divisor:Eb
SEGFIX divisor
DB 6FH
EndM
CodeMacro OR dst:Re, src:Ee
SEGFIX src
DB 0BH
MODRM dst,src
EndM
Описание формальных параметров
Каждый формальный параметр в макро имеет имя, задаваемое программистом, и после двоеточия спецификацию, состоящую из типа, размера и при необходимости диапазона допустимых значений в круглых скобках.
Типы формальных параметров:
A – сумматор EAX/AX/AL
C – выражение типа метка
D – непосредственный операнд
E – адресное выражение, записанное в регистре или памяти
M – адресное выражение, может иметь базовые и индексные регистры
R – один из общих регистров
S – сегментный регистр
X – прямое обращение к памяти при обмене с сумматором
Размеры формальных параметров
n - длина неопределенна
b – байт
w – слово
e – двойное слово
d – длина при использовании адреса смещение+сегмент
sb – знаковый байт, расширяемый до слова
se – знаковый байт, расширяемый до двойного слова
Примеры описания формальных параметров:
CodeMacro IN dst:Aw, port:Rw (DX)
CodeMacro ROR dst:Ee, count:Rb (CL)
Директивы макроопределений
Первоначально все описания команд х86 свелись к нескольким директивам, часть из которых используются редко. Перевод транслятора на архитектуру IA-32 потребовал добавления лишь одной новой директивы управления префиксами размера/адреса 66H/67H, причем, чтобы не вводить новых ключевых слов используется уже имевшаяся директива, но с другой формой параметра.
Директивы DB, DW и DD
Данные директивы в макро почти эквиваленты обычным операторам ассемблера и используются для задания констант и адресов. Эти директивы характерны и для любых других (не специальных) макросредств.
Директива DW используется для задания адреса (4 байта в 32-х разрядном режиме), а директива DD – для задания адреса в виде смещение+сегмент. Примеры использования DB, DW и DD:
CodeMacro CLC
DB 0F8H
EndM
CodeMacro XOR dst:Ee,src:De
SEGFIX dst
DB 81H
MODRM 6,dst
DW src
EndM
CodeMacro CALLF label:Cd
DB 9AH
DD label
EndM
Директива адресации MODRM
Это главная из «специальных» директив, определяющая адресацию архитектуры IA-32. Она определяет и основное отличие данных макросредств от обычных. Именно «микрокод», порождаемый этой директивой, указывает транслятору генерировать адресную часть команды, включая и байт режимов адресации и смещение и SIB-байт, если операнды подразумевают это. Директива имеет два параметра. Это или два имени формальных параметров макро или константа-число и имя. Например:
CodeMacro RCR dst:Ee, count:Rb(CL)
SEGFIX dst
DB 0D3H
MODRM 3,dst
EndM
CodeMacro XOR dst:Re,src:Ee
SEGFIX src
DB 33H
MODRM dst,src
EndM
Директивы определения относительного адреса RELB, RELW
Эти директивы используются для описания команд передачи управления по относительному адресу, занимающему или байт или 4 байта для IA-32. Пример:
CodeMacro LOOP place:Cb
DB 0E2H
RELB place
EndM
Директива задания кодов DBIT
Директива позволяет прямо сформировать цепочку бит, подставив в нее параметры. В ней указывается список полей через запятую. Для каждого поля задается его размер в битах, значение как константа или как имя формального параметра вместе с указанием в круглых скобках сдвига этого параметра вправо, например:
CodeMacro DEC dst:Re
DBIT 5(9), 3(dst(0))
EndM
Директива формирования префикса сегмента SEGFIX
Параметром данной директивы является имя формального параметра. Директива указывает транслятору, что если заданный параметр находится не в сегменте, который предполагается по умолчанию, необходимо генерировать соответствующий префикс сегментного регистра.
Директива контроля сегментов NOSEGFIX
Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.
Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 – нет префиксов, 1- может быть префикс 66H, 2 – может быть префикс 67H, 3 – могут быть оба префикса, 4 – всегда есть оба, 5 – никогда нет 66H, 6 – никогда нет 67H и т.п.
Такими простыми средствами удается описать все множество команд IA-32, например:
CodeMacro FLDCW src:Mw
SEGFIX src
DB 0D9H
MODRM 5, src
EndM
CodeMacro CMOVAE dst:Re, src:Ee
SEGFIX src
DB 0FH
DB 43H
MODRM dst,src
EndM
Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.
Создание псевдокоманд с помощью макросредств
Используя возможность добавления новых комбинаций операндов можно конструировать новые «команды» процессора. Например, команду MOV ECX,10 часто целесообразно заменять двумя командами с более коротким кодом PUSH 10 и POP ECX. А эти две команды можно описать в виде одного макроопределения:
CodeMacro MOVSX dst:Re, src:Dse
NOSEGFIX 6
DB 6AH
DB src
DBIT 5(0BH),3(dst(0))
EndM
Такую псевдокоманду можно создать и «обычными» макросредствами, однако назвать ее именем уже существующей команды MOVSX другие трансляторы (кроме RASM), скорее всего, не позволят. С точки зрения результата, это именно выполнение MOVSX с параметром-константой. Но такой команды в процессоре нет. А с макросредствами RASM можно считать, что на самом деле есть такая формы команды. Тот факт, что реально выполнение идет за два приема и стек меняется, а затем восстанавливается, в большинстве случаев можно не учитывать.
За время использования транслятора RASM накопился ряд таких полезных псевдокоманд, например:
MOV X,Y, где X,Y переменные в памяти;
MOV DS,0 или MOV DS,ES;
Команды PUSH и POP для нескольких регистров сразу, т.е. PUSH EAX,EBX,ECX;
Обращение к портам без указания регистра DX и т.п.
Добавление новых типов команд
Но, конечно, главное назначение описываемых средств – это добавление в имеющийся транслятор новых групп команд по мере появления новых поколений процессоров.
При этом в транслятор иногда приходится добавлять и новую группу имен специальных регистров этих команд (внутри транслятора имена это просто переименованные числа). Так, коды имен регистров CR0-CR7 являются внутри транслятора RASM числами 10H-17H, коды имен регистров MM0-MM7 числами 40H-47H, коды имен регистров XMM0-XMM7 числами 50H-57H, и т.д. Младшая цифра чисел (всегда 0-7) участвует в генерации кода через директиву MODRM, а собственно значения чисел используются для задания допустимого диапазона в формальных параметрах новых макро.
При поиске подходящих операндов транслятор проверит, что указанный в команде регистр входит в допустимый диапазон и поэтому, например, в командах MMX вместо MM0 нельзя указать «чужой» регистр CR0 или XMM0.
Часто в новых множествах команд требуется применить директиву NOSEGFIX 5, выключающую обычные правила использования префикса 66H (в зависимости от размера операндов), поскольку в описываемых командах этот префикс используется по-своему.
Тогда, например, для команд из множества MMX описания выглядят так:
CodeMacro MOVQ dst:Rn(40H,47H),src:Mn
NOSEGFIX 5
SEGFIX src
DB 0FH
DB 6FH
MODRM dst,src
EndM
Для команд из множества XMM:
CodeMacro ADDPS dst:Rn(50H,57H),src:Mn
NOSEGFIX 5
SEGFIX src
DB 0FH
DB 58H
MODRM dst,src
EndM
Для команд из множества SSE2:
CodeMacro ADDPD dst:Rn(50H,57H),src:Mn
NOSEGFIX 5
DB 66H
SEGFIX src
DB 0FH
DB 58H
MODRM dst,src
EndM
Для команд из множества 3DNow!:
CodeMacro PFACC dst:Rn(40H,47H),src:Mn
NOSEGFIX 5
SEGFIX src
DB 0FH
DB 0FH
MODRM dst,src
DB 0AEH
EndM
Расширение макросредств для x86-64 (IA-32e), AVX-команд и т.д.
Разумеется, расширение транслятора для генерации 64-х разрядных команд потребовало очередных доработок макросредств в виде добавления новой длины операнда «Q» (64-битный операнд/регистр) и новой директивы REX, формирующей REX-префикс команд. Потребовалось также ввести новые диапазоны регистров, ну и конечно дополнить таблицу служебных слов названиями требуемых регистров, вроде «SPL» или «R14D» или «YMM15».
Однако все эти доработки потребовали именно расширения, но не кардинальной переделки транслятора.
Использование макросредств для генерации команд процессоров другой архитектуры
При выполнении работ по программированию RISC-процессора микроконтроллера AT90S2313 «штатный» транслятор с ассемблера показался автору после работы с RASM непривычным и поэтому неудобным. Возникла идея использовать специальные макросредства и для того, чтобы генерировать коды команд RISC-архитектуры в соответствии с документацией Atmel, но при этом остаться в привычной среде RASM. Дело упрощалось тем, что RASM имеет режим формирования загрузочного модуля сразу, без использования редактора связей.
Анализ показал, что имеется лишь три препятствия такого использования RASM для генерирования команд RISC-архитектуры: конфликт мнемоники команды ST с названием регистра FPU, форма записи инкремента указателя типа X+ и другой способ вычисления относительного адреса, делающий директиву RELW неподходящей.
Первые два препятствия были обойдены с помощью введения новых директив в RASM, позволяющих исключать из лексического анализа заданную лексему (в данном случае ST) и разрешать синтаксические конструкции инкремента типа X+.
Для вычисления относительного адреса команд RISC-архитектуры были доработаны директивы макро RELW и DBIT. В директиве RELW стало возможно указывать необязательные дополнительные параметры в виде «добавки» и «сдвига вправо», позволяющие не просто вычислить адрес относительно текущего места, но и пересчитать его к нужному виду прибавлением «добавки» и сдвигом на заданную величину. При этом новая форма директивы RELW сама уже не генерирует адрес, а запоминает его для последующего использования в директиве DBIT. Доработка DBIT заключалась в возможности использования адреса, вычисленного выше директивой RELW. Для указания такого адреса используется строка “S” вместо имени параметра.
Такие несложные доработки транслятора повысили универсальность макросредств. Все RISC-команды были легко описаны с их помощью, например:
…
CODEMACRO RJMP k:Cw
RELW 2,12,k
DBIT 8('S'(1))
DBIT 4(0CH),4('S'(9))
ENDM
CODEMACRO LDI R_d:Db(16,31),K:Dn
DBIT 4(R_d(0)),4(K(0))
DBIT 4(0EH),4(K(4))
ENDM
CODEMACRO OUT P:Dn(0,63),R1_r:Db(0,31)
DBIT 4(R1_r(0)),4(P(0))
DBIT 5(17H),2(P(4)),1(R1_r(4))
ENDM
и т.д.
И наконец стало можно программировать микроконтроллер AT90S2313 на RASM:
…
;---- ПЕРЕХОД ПО RESET (0) ----
0000 02C0 0006 rjmp РЕСТАРТ
;---- ПЕРЕХОД ПО INT 0 (1) ----
0002 CBC0 019A rjmp ПРЕРЫВАНИЕ_ОТ_ГПР
РЕСТАРТ:
;---- ИНИЦИАЛИЗАЦИЯ СТЕКА ----
0006 BFED ldi СЧ_ТМ,СТЕК ;КОНЕЦ РАБОЧЕЙ ПАМЯТИ
0008 BDBF out SPL,СЧ_ТМ ;УСТАНОВИЛИ СТЕК
;---- ИНИЦИАЦИЯ ВЫХОДОВ ПОРТА "B" ----
000A 2FE5 ldi tmp,РАЗР_B
000C 27BB out DDRB,tmp
;---- ИНИЦИАЦИЯ ВЫХОДОВ ПОРТА "D" ----
000E 22E0 ldi tmp,РАЗР_D
0010 21BB out DDRD,tmp
;---- ИНИЦИАЦИЯ RS-232 ----
0012 24E0 ldi tmp,4 ;115200 БОД
0014 29B9 out UBRR,tmp
0016 28E1 ldi tmp,(1 SHL RXEN) OR (1 SHL TXEN)
0018 2AB9 out UCR,tmp
…
Заключение
Предложенная много лет назад идея, образно говоря, «оставить открытой дверь» в транслятор с ассемблера оказалось конструктивной и легко реализуемой. Почти не меняя транслятора можно оперативно отслеживать постоянно дополняемое множество команд современных процессоров. Легкость реализации обусловлена тем, что все множество команд состоит из небольшого числа базовых элементов.
Подобные средства целесообразно было бы включать вообще во все трансляторы, поскольку они позволяют начинать использование новых возможностей процессоров, не дожидаясь обновления соответствующих средств разработки. Кроме этого, с помощью данных средств можно совершенствовать систему команд добавлением псевдокоманд типа MOVSX EAX,10, практически ничем не отличающихся от реальных команд процессора.
Как было показано выше, универсальность описанных макросредств такова, что при незначительных доработках транслятора возможно даже настраивать его на генерацию команд для процессоров совсем другой архитектуры.
Использование RASM сегодня, разумеется, не требует таскать вместе с ним еще и файлы с описаниями команд, накопившиеся за все это время их эволюции. С помощью примитивной программы такие описания переводятся во внутреннее представление и становятся частью общей таблицы после очередной пересборки транслятора.
Разумеется также, что очередные изменения архитектуры (например, переход к x86-64) не могут свестись в трансляторе только к использованию подобных макросредств, а объективно требуют и доработок самого транслятора. Тем не менее, как бы в дальнейшем ни продолжилась эволюция команд процессоров, данные средства наверняка окажутся полезными.
Литература
1. «RASM-86 Programmer’s Guide» Digital Research, Сalifornia
https://ift.tt/3iklbKM
2. М.Гук, В. Юров «Процессоры Pentium 4, Athlon и Duron». СПб.: Из-во Питер, 2001
Комментариев нет:
Отправить комментарий