Сборник по задачам и примерам Assembler



Препроцессор команд ХММ-расширения

Отыщи всему начало, и ты многое поймешь.
Козьма Прутков

Задачу адаптации транслятора TASM к новым командам микропроцессора, и в частности к ХММ-командам, можно решить двумя способами.

  • Можно разработать включаемый файл, в котором дляя каждой ХММ-ком ды реализовать макрокоманду, моделирующую на ббазе существующих к манд нужную ХММ-команду. Кроме этого, фирма Irintel, зная об инерцио ности процесса разработки новых версий трансляторров ассемблера, вмест с подмножеством новых команд разрабатывает соотгветствующий включа мый файл для их поддержки в ассемблерных прогрэаммах. Для подмножества ХММ-команд такой файл называется iaxmm.inac. Он ориентирован на транслятор MASM (фирмы Microsoft) и не пригодеен (требует доработки") для TASM. Однако при доработке TASM необходимую иметь в виду вопрос об авторских правах. Некоторые проблемы использоования файла iaxmm.inc совместно с TASM обсуждены ниже.
  • Можно разработать программу-препроцессор, на ввход которой подавать исходный файл с программой на ассемблере, содеряжащей новые команды процессора, а на выходе получать текст, адаптированнный для компиляции старым транслятором ассемблера. Этот путь имеет' то преимущество, что теперь при появлении новых команд можно, не вноося больших корректив в технологию разработки программ, всего лишь орпределенным образом модифицировать файл-препроцессор, дополнив его i возможностями по обработке новых команд процессора. Более того, дополяяив препроцессор средствами распознавания микропроцессора (Intel или /AMD), можно разрабатывать программы с использованием расширения 31DNo\v!.

Первый способ был реализован в уроке «ММХ-техномюгия микропроцессоров Intel» учебника для ММХ-команд. Там же были привзедены примеры включаемых файлов, полностью пригодных для использованияя как 16-, так и 32-разрядными приложениями на ассемблере. Поэтому основносе внимание мы уделим второму способу организации поддержки ХММ-команд — препроцессорному. Но вначале рассмотрим структуру и содержание включаеемого файла iaxmm.inc. Текст этого файла можно загрузить с официального сайта компании Intel (http:// www.intel.com).

Поддержка ХММ-команд в файле iaxmm.inc

С точки зрения структуры включаемый файл iaxmm.inc представляет собой набор макрокоманд двух типов — основных и вспомогательньях. Названия основных макрокоманд полностью совпадают с названиями ХММ-команд, и эти макрокоманды обеспечивают моделирование определенных XlMM-команд. Вспомогательные макрокоманды расположены в начале файла и предназначены для обеспечения работы основных макрокоманд. В частности, :эти макрокоманды устанавливают тип операндов, указанных при обращении к (основной макрокоманде, причем делают это исходя из режима функционировашия транслятора — 16-или 32-разрядного. Другое важное действие — установление соответствия между названиями ХММ-регистров и регистров общего назначения. Дело в том, что для моделирования ХММ-команд в 16- или 32-разрядном режиме работы ассемблера используются разные регистры общего назначения — 1 6-разрядные регистры в 16-разрядном режиме, и 32-разрядные в 32-разрядном режиме.
Рассмотрим процесс моделирования ХММ-команд. В! качестве основы для моделирования выступает команда основного процессора!. Эта команда должнаудовлетворять определенным требованиям. Каковы они? В поисках ответа посмотрим на машинные коды ХММ-команд в литературе [40, 41]. Видно, что общими у них являются два момента:

  • поле кода операции ХММ-команд состоит из двух или трех байтов, один
    из которых равен Ofh;
  • большинство ХММ-команд использует форматы адресации с байтами modR/M и sib и соответственно допускает сочетание операндов как обычных двух-операндных команд целочисленного устройства — регистр-регистр или память-регистр.

Для моделирования ХММ-команд нужно подобрать такую команду основного процессора, которая удовлетворяет этим двум условиям. Во включаемом файле iaxmm.inc в качестве таких команды присутствуют две — CMPXCHG и ADD. В процессе моделирования на место нужного байта кода операции этих команд помещаются байты со значениями кода операции соответствующей ХММ-команды. Когда микропроцессор «видит», что очередная команда является ХММ-командой, то он начинает трактовать коды регистров в машинной команде как коды ХММ-регистров и ссылки на память, размерностью соответствующей данной команде. В машинном формате команды нет символических названий регистров, которыми мы пользуемся при написании исходного текста программы, например АХ или ВХ. В этом формате они определенным образом кодируются. Например, регистр АХ кодируется в поле REG машинной команды как 000. Если заменить код операции команды, в которой одним из операндов является регистр АХ, на код операции некоторой ХММ-команды, то это же значение в поле reg микропроцессор будет трактовать как регистр RXMMO. Таким образом, в ХММ-командах коды регистров воспринимаются соответственно коду операции. В табл. 10.1 приведены коды регистров общего назначения и соответствующих им ХММ-регистров. В правом столбце этой таблицы содержится условное обозначение ХММ-регистров, принятое в файле iaxmm.inc. Это же соответствие закреплено рядом определений в этом файле, которые иллюстрирует следующая программа.

DefineXMMxRegs Macro IFDEF APPJ.6BIT
rxmmO TEXTEQU<AX>
rxmml TEXTEQU<CX>
rxmm2 TEXTEQU<DX>
rxmm3 TEXTEQU<BX>
rxmm4 TEXTEQU<SP>
rxmm5 TEXTEQU<BP>
гхттб TEXTEQU<SI>
rxmm7 TEXTEQU<DI>
RXMMO TEXTEQU<AX>
RXMM1 TEXTEQU<CX>
RXMM2 TEXTEQU<DX>
RXMM3 TEXTEQU<BX>
RXMM4 TEXTEQU<SP>
RXMM5 TEXTEQU<BP>
P.XMM6 TEXTEQU<SI>
RXMM7 TEXTEQU<DI>
rxmml TEXTEQU<ECX>
rxmm2 TEXTEQU<EDX>
rxmm3 TEXTEQU<EBX>
rxmm4 TEXTEQU<ESP>
rxmm5 TEXTEQU<EBP>
гхттб TEXTEQU<ESI>
ГХШП7 TEXTEQU<EDI>
RXMMO TEXTEQU<EAX>
RXMM1 TEXTEQU<ECX>
NRXMM2 TEXTEQU<EDX>
RXMM3 TEXTEQU<EBX>
RXMM4 TEXTEQU<ESP>
RXMM5 TEXTEQU<EBP>
RXMM6 TEXTEQU<ESI>
RXMM7 TEXTEQU<EDI> ENDIF endm

Таблица 10.1. Кодировка регистров в машинном коде команды

Код в поле reg
Регистр целочисленного
устройства
ХММ-регистр
000
АХ/ЕАХ
RXMM0
001
СХ/ЕСХ
RXMM1
010
DX/EDX
RXMM2
Oil
ВХ/ЕВХ
RXMM3
100
SP/ESP
RXMM4
101
ВР/ЕВР
RXMM5
110
SI/ESI
RXMM6
111
DI/EDI
RXMM7

Теперь в исходном тексте программы можно использовать символические имена ХММ-регистров в качестве аргументов макрокоманд, моделирующих ХММ-команды.
Рассмотрим, как в файле iammx.inc описано макроопределение для моделирования ХММ-команды скалярной пересылки MOVSS.

:F3 OF 10 /г movss xrrnil. xmm2/m32 :F3 OF 11 /r movss xmm2/m32. xnrnl movss macro dst:req. src:req
XMMld_st_f3 opc_Mo«s. dst, src endm

Понимание структуры приведенного макроопределения не должно вызвать у читателя трудностей. Начать следует с того, что данная команда содержит вложенный вызов макрокоманды XMMld_st_f3, у которой две задачи — определить вариант сочетания операндов, после чего сформировать правильный код операции и подставить его на место соответствующих байтов в команде CMPXCHG. В результате этих действий команда CMPXCHG «превращается» в ХММ-команду MOVSS.

1. XMMld_St f3 macro op:req.dst:req, src:req
2. local x. у
3. Defin'eXMMxRegs
4. IF (OPATTR(dst)) AND OOOlOOOOy -.register
5. x: lock cmpxchg src. dst
6. у: org x
7. byte OF3H.0Fh. op& Id
8. org у
9. ELSE
10. x: lock cmpxchgdst. src
11. y: orgx
12. byte 0F3H.0Fh. op&_st
13. orgy
14. ENDIF
15. UnDefineXMMxRegs
16. endm

Центральное место в макроопределении ХММ1 d_st_f3 занимают команда целочисленного устройства (в данном случае — CMPXCHG) и директива ORG. Первое действие данной макрокоманды — выяснить тип операнда приемника (dst) в макрокоманде MOVSS, так как он может быть и регистром, и ячейкой памяти. Это необходимо для правильного определения кода операции, которая будет управлять направлением потока данных. После того как определен приемник данных, с помощью условного перехода осуществляется переход на ветвь программы, где будет выполняться собственно формирование соответствующего ХММ-команде
MOVSS кода операции.
Формирование кода операции ХММ-команды MOVSS производится с помощью директивы org, которая предназначена для изменения значения счетчика адреса. В строках 6 или 11 директива org устанавливает значение счетчика адреса равным адресу метки х. Адрес метки х является адресом первого байта машинного кода команды CMPXCHG. Директива db в следующих строках размещает по этому адресу байтовые значения 0F3H,0Fh, ор&_1 d или 0F3H,0Fh, op&st, в зависимости от того, какое действие производится — загрузка (_ld) или сохранение (_st). Значение opc_Movss, с помощью которого формируются значения op&_st и ор&_1 d, определены в начале файла iaxmm.inc:

opcjtovssjd - 010Н
opc_Movss_st - 011H

Для дотошных читателей заметим еще один характерный момент. Для его полного понимания необходимо хорошо представлять себе формат машинной команды и назначение его полей. Достаточно полная информация об этом приведена в литературе [39, 40]. Обратите внимание на порядок следования операндов в заголовке макрокоманды, который построен по обычной для команд ассемблера схеме: коп назначение, источник. В команде CMPXCHG порядок обратный. Этого требует синтаксис команды. Это хорошо поясняет назначение бита d во втором байте кода операции, который характеризует направление передачи данных в микропроцессор (то есть в регистр) или в память (из микропроцессора (регистра)). Вы можете провести эксперимент. Проанализируйте машинные коды команды MOV:

 

  • Команды с непосредственным операндом:
    CMPPS RXMM1. RXMM2/ml28, 18 CMPSS RXMM1, RXMM2/m32. i8
  • Однооперандные команды: FXRSTOR m512 FXSAVE m512 LDMXCSR m32 STMXCSR m32

Из перечисленных выше групп команд можно вывести следующую обобщенную структуру команды:

метка: код_операции операнд1. операнд2, операндЗ] ;текст комментария

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

  1. 1. Лексический анализ (сканирование) исходного текста.
    2. Синтаксический анализ.
    3. Генерация кода.

Необходимо отметить, что по принципу действия разрабатываемый нами препроцессор относится к интерпретаторам. Читатель наверняка понимает, в чем состоит разница между интерпретатором и компилятором. Объект для работы компилятора — исходный текст программы в полном объеме. Выход компилятора — объектный модуль, то есть машинное представление исходной программы, пригодное для компоновки с другими модулями или получения исполняемого модуля. Интерпретатор работает с отдельными строками исходной программы. Распознав синтаксическую правильность строки, интерпретатор исполняет ее. В частности, интерпретация характерна для обработки входных строк командного процессора. Поэтому на примере данной задачи читатель может научиться достаточно профессионально организовывать языковое взаимодействие с пользователями своих программ.
В главе 2 описаны основные шаги разработки компилятора. Для интерпретатора разница невелика, в чем мы убедимся ниже.
Для распознавания лексем входной программы разработаем сканер, следуя для этого следующему алгоритму.

  1. 1. Выделить классы лексем.
    2. Определить классы литер.
    3. Определить условия выхода из сканера для каждого класса лексем.
    4. Каждому классу лексем поставить в соответствие грамматику класса 3.
    5. Для каждой грамматики, построенной на шаге 4, построить конечный автомат, который будет распознавать лексему данного класса.
    6. Выполнить объединение («склеивание») конечных автоматов для всех классов лексем.
    7. Составить матрицу переходов для «склеенного» конечного автомата.
    8. «Навесить» семантику на дуги «склеенного» конечного автомата.
    9. Выбрать коды лексической свертки для терминалов грамматики и формат таблицы идентификаторов.
    10. Разработать программу сканера.

Язык описания команд ассемблера

Множество команд ассемблера можно описать с помощью следующего языка:

ASM_LENG={
Vt=(+ - AL АН BL BH CL CH DL DH AX EAX BX EBX CX ECX DX EDX BP EBP SP ESP DI EDI SI ESI BYTE SBYTE WORD SWORD DWORD SDWORD FWORD QWORD TBYTE REAL4 REAL8 REAL10 0 12 3 4 56789abcdefABCDEF NEAR16 NEAR32 FAR16 FAR32 AND NOT HIGH LOW HIGHWORO LOWWORD OFFSET SEG LROFFSET TYPE THIS PTR :.{)[] WIDTH MASK SIZE SIZEOF LENGTH LENGTHOF ST SHORT .TYPE OPATTR . название_команды * / MOD NEAR FAR OR XOR " 'hoqt у H 0 Q T Y { } < > :; EQ NE LT LE GT GE CS DS ES FS GS SS SHR SHL CRO CR2 CR3 DRO DR1 DR2 DR3 DR6 DR7 TR3 TR4 TR5 TR6 TR7 А5СП_символ_буква любой_символ_кроме_кавычки). Vn-( addOp asmlnstruction byteRegister constant constExpr dataType decdigit digits distance expr exprtist Expr eOl eO2 eO3 eO4 eO5 eO6 eO7 eO8 eO9 eOlO eOll hexdigit id mnemonic mulOp nearfar radixOverride orOp oldRecordFieldList relOp recordConst recordFieldList register shiftOp sizeArg string type segmentRegister specialRegister stext string stringChar structTag quote type typeld unionTag). P.
Z=(<asmlnstruction>) }

Множество правил Р грамматики ASM_LENG выглядит следующим образом:

smlnstruction => mnemonic [[ exprList ]] AddOp => + | -
byteRegister => AL | AH | BL | BH 1 CL j CH | DL j DH constant => digits [[ radixOverride ]] constExpr => Expr dataType => BYTE | SBYTE | WORD | SWORD | DWORD | SDWORD | FWORD | QWORD |
TBYTE | REAL4 | REAL8 | REAL 10
decdigit => 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 digits => decdigit | digits decdigit | digits hexdigit distance => nearfar | NEAR16 | NEAR32 | FAR16 | FAR32 eOl => eOl orOp eO2 | eO2 eO2 => eO2 AND еОЗ | еОЗ eO3 => NOT eO4 | eO4 eO4 => eO4 relOp eO5 | eO5 eO5 => eO5 addOp eO6 | eO6 eO6 => eO6 mulOp eO7 | eO6 shiftOp eO7 | eQ7 eO7 => eO7 addOp eO8 | eO8
eO8 => HIGH eO9 | LOW eO9 | HIGHWORD eO9 | LOWWORD eO9 | eO9 eO9 => OFFSET elO | SEG elO | LROFFSET elO | TYPE elO ] THIS elO | eO9 PTR
elO | eO9 : elO | elO
elO => elO . ell | elO [[ expr ]] | ell
, ell => ( expr ) | [ expr ] | WIDTH id | MASK id | SIZE SizeArg | SIZEOF
sizeArg | LENGTH id | LENGTHOF id | recordConst | string | constant | type | id | $ | segmentRegister | register | ST | ST ( expr ) expr => SHORT eO5 | .TYPE eOl | OPATTR eOl | eOl exprList => expr | exprList . expr gpRegister => AX | EAX | BX | EBX | CX | ECX | DX 1 EDX | BP | EBP | SP | ESP I
DI | EDI | SI | ESI
hexdigit =>a|b|c|d|e|f|A|B|C|D|E|F
id => А5С11_символ_буква | id А5СП_символ_буква | id decdigit
mnemonic => название_команды
mulOp => * | / | MOD
nearfar => NEAR | FAR
oldRecordFieldList=> [[ constExpr ]] | oldRecordFieldList . [[ constExpr ]]

За основу языка ASMLENG было взято описание языка MASM (из документации на него), ассемблер, поддерживаемый TASM, незначительно отличается от этого описания (в основном это касается некоторых операторов, типа OPATTR). Подчеркнем тот факт, что язык ASMLENG описывает лишь правило построения команд ассемблера, не затрагивая синтаксиса всей программы ассемблера в целом. Все строки, не являющиеся командами, будут просто игнорироваться и включаться в выходной файл в своем изначальном виде.

Выделение классов лексем

Для грамматики языка ASMLENG можно определить следующие классы лексем:

  • идентификатор — id;
  • ключевые слова - AL АН BL ВН CL СН DL DH АХ ЕАХ ВХ ЕВХ СХ ЕСХ DX EDX ВР ЕВР SP ESP DI EDI SI ESI BYTE SBYTE WORD SWORD DWORD SDWORD FWORD QWORD TBYTE REAL4 REAL8 REAL10 NEAR16 NEAR32 FAR16 FAR32 AND NOT HIGH LOW HIGHWORD LOWWORD OFFSET SEG LROFFSET TYPE THIS PTR WIDTH MASK SIZE SIZEOF LENGTH LENGTHOF ST SHORT .TYPE OPATTR MOD NEAR FAR OR XOR EQ NE LT LE GT GE CS DS ES FS GS SS SHR SHL CRO CR2 CR3 DRO DR1 DR2 DR3 DR6 DR7 TR3 TR4 TR5 TR6 TR7 на-звание_команды;
  • целые числа (константы) — 0123456789abcdefABCDEF;
  • однолитерные разделители — +-/: . ()[] ,*" ' {}<>hoqtyHOQ Т Y;
  • двулитерный разделитель — ;;
  • символьные строки — А5СП_символ_буква, любой_символ_кроме_кавычки.

Классы литер

В случае грамматики языка ASMLENG можно определить следующие классы литер:

  • б — цифра;
  • 1 — буква;
  • b — литеры, которые игнорируются (к ним отнесем пробел); ¦ .
  • si - одиночные разделители: + -/:.()[],*"'{}<>;;
  • s2 — двулитерный разделитель: ;;.

Определение условий выхода из сканера для каждого класса лексем
Для каждого класса лексем определим условия, при которых сканер переходит в конечное состояние:

  • для идентификаторов — появление во входном потоке сканера любого символа, отличного от d (цифра) или 1 (буква);
  • ключевые слова — появление пробела и нахождение соответствия введенной лексемы одному из ключевых слов языка;
  • целые числа (константы) — появление любого символа, отличного от d;
  • однолитерные разделители — появление любого символа;
  • двулитерные разделители — появление любого символа; ,;;.
    символьные строки — появление завершающей кавычки. ......г.

Построение автоматных грамматик для выделенных классов лексем
Для каждого класса лексем строится отдельная автоматная грамматика, соответствующая грамматике типа 3 по Хомско.му. В нашем случае набор таких грамматик может выглядеть так, как показано ниже:

  • идентификаторы — id, к которым по принципу построения можно отнести и ключевые слова — название_команды:

id=>ASCII_CMMBon_6yKea | id
ASCII_символ_буква | id decdigit :,; decdigit ^ 0|l|2|...8|9

  • целые числа — chint:

digits => decdigit | digits decdigit | digits hexdigit decdigit =>0|l|2|3|4|5|6|7|8|9 ¦

  • Oднолитерные разделители — +-/:.()[]. *"¦'---

SEPiL^. . | . | : |: | + | - | * I 4 ) / I L I ] Г Г I

  • двулитерный разделитель — ;;:

SEP2L=> : Q

«Склеивание» конечных автоматов для всех классов лексем

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

Рис. 10.2. Упрощенный вариант «склеенного» конечного автомата

Для представленного на рисунке склеенного конечного автомата таблица (матрица) переходов показана ниже.

L D [ ] пробел
SO SI S2 S4 S4 SO S6
SI SI SI S5 S4
со S2 S2 S4
S3

S4
S5 S3 S3 S3 S3 S3 S3 S4
S6 SG S6 S6 S6 S6 S6 S6 S6

Как обычно, два из этих состояний являются конечными:

  • S3 — состояние ошибки;
  • S4 — конечное состояние.

Состояние S6 — состояние, соответствующее комментарию, то есть допустимы любые символы. Ограничение комментария — конец строки. Строки в таблице переходов соответствуют состояниям склеенного конечного автомата, основа столбцов — классы лексем. Логика работы сканера с использованием таблицы переходов описана в главе 2.
После заполнения таблицы переходов можно навешивать семантику на « дуги» переходов из одного состояния в другое. Основная задача при этом — не брать на себя ничего лишнего. Главное — локализовать поле с названием команды, определить принадлежность ее к группе ХММ-команд. Если это не так, то дальней
ший процесс сканирования строки можно прекращать, копировать ее в выходной поток (пусть транслятор ассемблера разбирается с ней сам) и переходить к анализу очередной строки исходного текста ассемблерной программы.
В самом простом случае нашу задачу можно решить легко — в очередной строке выделить метку, если она есть, затем выделить название команды, и если она является ХММ-командой, то продолжить обработку строки. Если очередная строка не является ХММ-командой, то копируем ее полностью в выходной файл. Если очередная строка — ХММ-команда, то локализуем операнды и определяем их тип. По крайней мере один из операндов должен быть регистром. Если строка синтаксически верна для конкретной ХММ-команды, то формируем ее аналог, понятный для восприятия используемым нами транслятором ассемблера. Этот процесс может быть похожим на первый способ формирования ХММ-команд с помощью включаемого файла iaxmm.inc.

Синтаксический анализ

Если веденная строка исходной программы является строкой с ХММ-командой, то необходимо получить ее лексическую свертку для проведения синтаксической проверки и подготовки к дальнейшему преобразованию. Здесь простор для творческих изысканий весьма велик. Поэтому мы не будем повторять соответствующий материал главы 2, а обратимся к тексту программы, который выполняет такое преобразование. Его описание содержит множество технических подробностей, не относящихся к предмету книги. По этой причине программа-препроцессор с соответствующими пояснениями деталей реализации вынесена на дискету, прилагаемую к книге. Протестировать эту программу вы можете на примерах программ с ХММ-командами, приведенными в первой части данной главы.


Книжный магазин