Встроенный ассемблер Borland Pascal позволяет вам непосредственно в программах Паскаля записывать код ассемблера процессоров  8087/8087 и 80286/80287.  Вы, конечно, если требуется чередовать  код Паскаля и ассемблера,  можете преобразовать код ассемблера в  машинные инструкции вручную и воспользоваться  затем  операторами  inline,  либо выполнять компоновку с файлами .OBJ, которые содержат внешние процедуры и функции (external).

Встроенные операторы  ассемблера представляют собой большое  подмножество синтаксиса, поддерживаемого Турбо Ассемблером и Макроассемблером фирмы Microsoft.  Встроенный ассемблер поддерживает  все коды операций процессором 8086/8087 и 80286/80287 и некоторые  из операций, используемых в выражениях Турбо Ассемблера.

За исключением директив DB (определить байт), DW (определить  слово) и  DD  (определить двойное слово) никакие другие директивы  Турбо Ассемблера,  типа EQU, STRUC, SEGMENT или MACRO, встроенным  ассемблером не поддерживаются.  Однако, операции,  реализуемые с  помощью директив Турбо Ассемблера, близко соответствуют конструкциям Borland Pascal. Например, большинство директив EQU соответствуют описаниям Borland Pascal const,  var и type, директива PROC  - описаниям  procedure  и function,  а  директива STRUC - типам  record Borland Pascal.  Фактически,  встроенный ассемблер Borland  Pascal можно рассматривать,  как компилятор языка ассемблера, использующий для всех описаний синтаксис Паскаля.

Встроенный ассемблер становится доступным с помощью операторов asm. Оператор asm имеет следующий синтаксис:

asm оператор_ассемблера < разделитель оператор_ассемблера > end

  где "оператор_ассемблера" представляет собой оператор ассемблера,  а "разделитель " - это точка с запятой,  новая строка или комментарий Паскаля. Приведем некоторые примеры операторов asm:

  asm

   mov   ah,0 { считать с клавиатуры код функции }

   int   16H  { для чтения клавиши вызвать BIOS }

   mov   CharCode,al { сохранить код ASCII }

   mov   ScanCode,ah { сохранить код опроса }

  end;

 

 asm

   push  ds { сохранить DS }

   lds   si,Source  { загрузить указатель источника }

   les   di,Dest { загрузить указатель приемника }

   mov   cx,Count{ загрузить размер блока }

   cld   { переместить }

   rep   movsb{ скопировать блок }

   pop   ds{ восстановить DS }

end;

Заметим, что на одной строке можно разместить несколько операторов ассемблера, разделив их точками с запятой.  Кроме  того  следует  отметить,  что  если операторы ассемблера размещаются на  разных строках,  разделять их точками с запятой не требуется. Заметим также, что точка с запятой не говорит  о том, что остальная  часть строки представляет собой комментарий.  Комментарии следует  записывать, используя синтаксис Паскаля: с помощью { и } или (* и  *).

Правила использования регистров в операторе asm  в  основном  совпадают с этими правилами для внешних процедур и функций.  Оператор asm должен сохранять регистры BP,  SP,  SS и DS,  но  может  свободно изменять AX, BX, CX, DX, SI, DI, ES и регистр флагов. На  входе в оператор asm BP указывает на текущую рамку стека, SP указывает на вершину стека, SS содержит адрес сегмента стека, а DS -  адрес сегмента данных. За исключением регистров BP,  SP, SS и DS  оператор  asm  не может делать никаких предположений относительно  содержимого других регистров на входе в этот оператор.

Оператор ассемблера имеет следующий синтаксис:

  [ метка":" ] < префикс > [код_операции [операнд < "," операнд >]]

  где "метка" - это идентификатор метки, "префикс" - префикс кода  операции ассемблера. "Код_операции" - код инструкции или директива ассемблера, а "операнд" - выражение ассемблера.

Между операторами ассемблера (но не в них) допускается включать комментарии. Допустимо, например, следующее:

asm

   mov   ax,1  { начальное значение }

   mov   cx,100   { счетчик }

end;

  однако следующая запись ошибочна:

asm

   mov   { начальное значение } ax,1

   mov   cx, { счетчик } 100

end;

Метки в ассемблере определяются также,  как в Паскале: перед  оператором записывается идентификатор метки и двоеточие.  Как и в  Паскале, метки в ассемблере должны описываться в объявлении label  того блока, который содержит оператор asm. Однако из этого правила есть одно исключение. Это локальные метки.

Локальные метки - это метки, которые начинаются с символа @.  Поскольку этот  символ не может быть частью идентификатора Паскаля, такие локальные метки автоматически ограничиваются использованием их в операторах asm. Локальная метка известна только в определяющем ее операторе asm (то есть область  действия  локальной  метки начинается от ключевого слова asm и заканчивается ключевым  словом end оператора asm, который ее содержит).

В отличие от обычной метки, локальную метку перед ее использованием не требуется описывать в объявлении label.

Идентификатор локальной метки состоит из символа @, за которым следует одна или более букв (A..Z) цифр (0..9) символов подчеркивания или символов @.  Как и все метки, идентификатор завершается двоеточием.

Встроенный ассемблер   поддерживает  инструкции  процессоров  8086/8087 и  80286/80287. Инструкции  процессора  8087 доступны  только  в состоянии {$N+} (разрешено использование сопроцессора),  инструкции процессора 80286 - только в состоянии {$G+} (разрешена  генерация  кода для процессора 80286), а инструкции сопроцессора  80287 - только в состоянии {$G+,N+}.

Полное описание каждой инструкции  содержится  в справочных  материалах по процессорам 80х86 и 80х87.

Инструкция REP генерирует код машинной  инструкции  возврата  ближнего или дальнего типа,  в зависимости от модели вызова текущей процедуры или функции.

procedure NearProc; near;

begin

   asm

  ret   { генерируется ближний возврат }

   end;

end;

 

   procedure FarProc; far

begin

   asm

  ret   { генерируется дальний возврат }

   end;

end;

С другой стороны,  инструкции RETN и RETF всегда  генерируют  ближний или  дальний возврат соответственно, независим от модели  вызова текущей процедуры или функции.

Если не указывается противное,  встроенный ассемблер оптимизирует инструкции перехода, автоматически выбирая наиболее короткую, и, следовательно, наиболее эффективную форму инструкции перехода.  Такое  автоматическое   определение   размера   перехода  применяется  к инструкции безусловного перехода (JMP) и всем инструкциям условного перехода,  когда переход выполняется на метку,  а не процедуру или функцию.

Для инструкции  безусловного  перехода  встроенный ассемблер  генерирует короткий переход (один байт кода операции,  за которым  следует один байт смещения), если расстояние до целевой метки находится в границах от -128 до 127 байт.  В противном случае генерируется  ближний  переход  (один байт кода операции,  за которым  следую два байта смещения).

Для инструкций условного  перехода короткий  переход  (один  байт кода операции,  за которым следует один байт смещения) генерируется,  если расстояние до целевой метки находится в  пределах  от -128 до 127 байт,  в противном случае встроенный ассемблер генерирует короткий переход с обратным условием, который выполняет  переход  на целевую метку через ближний переход (в общем случае 5  байт). Например, оператор ассемблера:

JC Stop

  где Stop не находится в границах короткого перехода, преобразуется в последовательность машинных кодов,  соответствующих инструкциям:

jnc   Skip

jmp   Stop

Skip:

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

jmp   NEAR PTR Stop

jmp   FAR PTR Stop

  будут всегда генерировать соответственно ближний и дальний  переход, даже  если  на метку Stop можно перейти с помощью короткого  перехода.

Встроенный ассемблер  Borland Pascal поддерживает три директивы ассемблера: DB (определить байт), DW (определить слово) и DD  (определить двойное слово).  Каждая из них генерирует данные, со ответствующие разделенным запятым операндам,  которые следуют  за  директивой.

Директива DB генерирует последовательность байт. Каждый операнд может представлять собой выражение-константу со значением от  -128 до 255, или строку символов любой длины. Выражение-константа  генерирует 1 байт кода,  а строки генерируют  последовательность  байт со значениями, соответствующим коду ASCII каждого символа.

Директива DW генерирует последовательность слов. Каждый операнд может представлять собой выражение-константу со значением от  -32768 до 65535,  или адресное выражение. Для адресного выражения  встроенный ассемблер генерирует указатель ближнего типа, что есть  слово, содержащие смещения адреса.

Директива DD  генерирует последовательность  двойных  слов.  Каждый операнд может представлять  собой выражение-константу  со  значением  от  -2147483648  до 4294967295 или адресное выражение.  Для адресного выражения встроенный ассемблер генерирует указатель  дальнего типа, что есть слово, содержащие смещения адреса, за которым следует слово, содержащее сегментную часть адреса.

Данные, генерируемые по директивам DB, DW и DD, всегда записываются в сегмент кода,  аналогично коду,  генерируемому другими  операторами встроенного ассемблера. Чтобы сгенерировать инициализированные или неинициализированные данные в сегменте данных, вам  следует использовать обычные описания Паскаля типа var или const.

Приведем некоторые примеры директив DB, DW и DD:

asm

   DB  00FH { 1 байт }

   DB   0,99 { 2 байта }

   DB   'A'  { Ord('A) }

   DB   'Пример',0DH,OAH { строка, за которой

   следуют возврат каретки и перевод строки }

   DB   12,"Borland Pascal"   { строка Паскаля }

   DW   0FFFFH  { 1 слово }

   DW   0,9999  { 2 слова }

   DW   'A'  { эквивалентно DB 'A',0 }

   DW   'BA'  { эквивалентно DB 'A','B' }

   DW   MyVar  { смещение MyVar }

   DW   MyProc  { смещение MyProc }

   DD   0FFFFFFFH  { 1 двойное слово }

   DD   0,99999999 { 2 двойных слова }

   DD   'A'  { эквивалентно DB 'A',0,0,0 }

   DD   'DBCA'  { эквивалентно DS 'A','B','C','D' }

   DD   MyVar{ указатель на MyVar }

   DD   MyProc { указатель на MyProc }

end;

В Турбо Ассемблере,  когда перед идентификатором указывается  DB, DW или DD,  это приводит к генерации в том месте, где указана  директива,  переменной размером в байт,  слово или двойное слово.  Например, Турбо Ассемблер допускает следующее:

ByteVar  DB ?

WordVar  DW ?

   .

   .

   .

mov   al,ByteVar

mov   bx,WordVar

Встроенный ассемблер не поддерживает такие описания переменных. В Borland Pascal единственным видом идентификатора,  который  можно определить в  операторе встроенного  ассемблера,  является  метка.  Все  переменные  должны описываться с помощью синтаксиса  Паскаля, и предыдущая конструкция соответствует следующему:

var

   ByteVar: Byte;

   WordWat: Word;

  .

  .

  .

   asm

  mov al,ByteVar

  mov bx,WordVar

   end;

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

Во встроенном ассемблере предопределенный смысл имеют следующие зарезервированные слова:

AH  CL FAR SEG

AL  CSHIGH   SHL

AND CX    LOW SHR

AX  DH MOD SI

BH  DINEAR   SP

BL  DL NOT SS

BP  DSOFFSET ST

BX  DWORD    OR  TBYTE

BYTE   DX PTR TYPE

CH  ESWQORD  WORD

  XOR

Зарезервированные слова всегда имеют больший  приоритет, чем  определенные пользователем идентификаторы. Например, во фрагменте  программы:

var

   ch: Char;

   ...

asm

   mov   ch,1

end;

  1 будет загружаться в регистр CH,  а не в переменную CH. Для доступа к определенному пользователем имени нужно  использовать  амперсанд - операцию переопределения идентификатора (&).

asm

   mov  &ch,1

end;

Мы настоятельно  рекомендуем не  использовать  определенные  пользователем идентификаторы с теми же именами,  что и зарезервированные  слова встроенного ассемблера, поскольку такая путаница  имен может легко приводить к очень трудноуловимым ошибкам.

Встроенный ассемблер вычисляет все выражения, как 32-разрядные значения-указатели.  Он не поддерживает значения с  плавающей  точкой и строковые значения, за исключением строковых констант.

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

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

const

   X = 10;

   Y = 20;

var

   Z: Integer;

  следующий оператор является во встроенном ассемблере допустимым:

asm

   mov Z,X+Y

end;

Поскольку X и Y - это константы, выражение X + Y представляет собой просто удобный способ записи константы 30,  и полученная  в результате  инструкция помещает непосредственное значение 30 в  переменную Z размером в слово.  Но если вы опишете X и Y, как переменные:

var

   X, Y: Integer;

  то встроенный ассемблер не сможет на этапе  компиляции  вычислить  значение X + Y.  Корректной конструкцией встроенного ассемблера в  этом случае будет:

asm

   mov  ax,X

   add  ax,Y

   mov  Z,ax

end;

Другим важным отличием выражений Паскаля и  встроенного  Ассемблера  является  способ интерпретации переменных.  В выражении  Паскаля ссылка не переменную интерпретируется, как содержимое переменной,  но  в выражении встроенного ассемблера ссылка на пере- p>  менную означает адрес переменной. Например, в Паскале выражение X  + 4,  где X - переменная,  означает содержимое X,  плюс 4,  а во  встроенном ассемблере это означает содержимое в слове  по  адресу  на 4 байта выше, чем адрес X. Поэтому, хотя допустима запись:

asm

   mov   ax,X+4

end;

  этот код не загружает значения X, плюс 4 в AX, а загружает значение слова,  записанного через 4 байта после X. Корректной записью  сложения 4 с содержимым X будет:

asm

   MOV   AX,X

   ADD   AX,4

end;

Элементы выражений

 ДДДДДДД

Основными элементами  выражения являются константы, регистры  и идентификаторы.

Встроенный ассемблер поддерживает два типа констант:  числовые константы и строковые константы.

Числовые константы должны быть целыми и принимать значения в  диапазоне от -2147483648 до 4294967295.

По умолчанию числовые константы являются десятичными, однако  встроенный ассемблер поддерживает также двоичные,  восьмеричные и  шестнадцатиричные константы.  Двоичное представление обозначается  записью после числа B, восьмеричное - записью буквы O, а шестнадцатиричное - записью после числа H или указанием перед  числом $.

В выражениях Паскаля суффиксы B,  O и H  не  поддерживаются.  Выражения  Паскаля  допускают  только десятичную (по умолчанию) и шестнадцатиричную запись (используется префикс $).

Числовые константы должны начинаться с  одной  из цифр  или  символа $.  Таким образом, когда вы записываете шестнадцатиричную  константу с помощью суффикса H,  то если первой  значащей  цифрой  является  одна из шестнадцатиричных цифр от A до F,  то требуется  дополнительный ноль.  Например, 0BAD4H и $BAD4 представляют собой  шестнадцатиричные константы, а BAD4H - это идентификатор, так как  он начинается с буквы, а не с цифры.

Строковые константы должны заключаться в одиночные или двойные кавычки. Указание двух последовательных кавычек одного типа в  качестве  закрывающих кавычек считается за один символ.  Приведем  некоторые примеры строковых констант:

'Z'

'Borland Pascal'

"That's all folks"

    '"That''s all falks," he said.'

'100

'"'

"'"

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

В директивах  DB допускаются строковые кавычки любой длины.  Это приводит к выделению последовательности байт, содержащих значения (ASCII) символов строки.  Во всех других случаях  строковые  константы не могут превышать четырех символов и обозначают числовое значение,  которое может участвовать в  выражениях. Числовое  значение строки вычисляется следующим образом:

   Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24

  где Ch1 - это самый правый (последний) символ,  а Ch4 - самый левый (первый) символ.  Если строка короче 4 символов, то самые левые (первые) символы считаются нулевыми.  Приведем некоторые примеры строковых констант и их значений:

 Примеры строк и их значения

 Таблица 24.1

  ЪДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДї

   і   Строка і Значение   і

  ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

   і   'a' і 00000061H  і

   і   'ba'і 00006261H  і

   і   'cba'  і 00636261H  і

   і   'dcba' і 64636261H  і

   і   'a' і 00006120H  і

   і   ' a'і 20202061H  і

   і   'a'*2  і 000000E2H  і

   і  'a'-'A' і 00000020H  і

   і   not 'a'і FFFFFF9EH  і

  АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДЩ

Следующие зарезервированные  идентификаторы  обозначают  регистры ЦП:

Регистры ЦП Таблица 24.2

 ДДДДДДД

  16-разрядные регистры общего назначения:  AX  BX CX  DX

  8-разрядные младшие полурегистры:   AL  BL  CL  DL

  8-разрядные старшие полурегистры:   AH  BH  CH  DH

  16-разрядные указатели или индексные регистры:  SP  BP  SI  DI

  16-разрядные сегментные регистры:   CS  DS  SS  ES

  регистр стека процессора 8087 ST

  ДДДДДДД

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

Базовые регистры (BX или BP) и индексные  регистры  (SI или  DI) можно  записывать в квадратных скобках для указания индексации. Допустимым сочетанием базового/индексного регистра являются  [BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI] и [BP+DI].

Сегментные регистры (ES,  CS, SS и DS) могут использоваться  вместе с операцией переопределения сегмента (:)  и  указывать на  другой сегмент,  отличный от того,  который процессор выбирает по  умолчанию. На каждый из 8 регистров с плавающей точкой можно ссылаться с помощью ST(x),  где x - константа от 0 до 7, указывающая  на расстояние от вершины стека регистров.

Встроенный ассемблер позволяет в выражениях ассемблера получить доступ ко всем идентификаторам Паскаля, включая метки, константы,  типы,  переменные,  процедуры и функции.  Кроме того, во  встроенном ассемблере реализованы следующие специальные идентификаторы:

@Code @Data  @Result

Идентификаторы @Code  и @Data представляют текущие сегменты  кода и данных соответственно.  Их следует использовать  только в  сочетании с операцией SEG:

asm

   mov   ax,SEG @Data

   mov   ds,ax

end;

Идентификатор @Result в операторной части функции переменную  - результат функции. Например, в функции:

function Sum(X, Y: Integer): Integer;

begin

Sum := X + Y;

end;

  в операторе, присваивающем результат функции переменной Sum, можно было бы при записи на встроенном ассемблере использовать переменную @Result:

   function Sum(X, Y: Integer): Integer;

begin

   asm

  mov   ax,X

  add   ax,Y

  mov   @Result,ax

   end;

end;

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

- стандартные процедуры и функции (например, WriteLn, Chr);

- специальные массивы Mem, MemW, MemL, Port, PortW;

- строки,  значения с плавающей точкой и константы  множественного типа;

- метки, которые не описаны в текущем блоке;

- идентификатор @Result вне функции.

В Таблице  24.3 приведены значение,  класс и тип различного  вида идентификаторов,  которые можно  использовать  в  выражениях  встроенного  ассемблера  (классы  и типы выражений описываются в  следующем разделе):

  Значения, классы и типы идентификаторов  Таблица 24.3

 ДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДї

  іИдентификат.і  Значение і  Класс   і Тип і

 ГДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДґ

     і Метка   і Адрес метки  і Память   і SHORT   і

  і Константа  і Значение константы і Непосредствен- і  0   і

  і   і і ный   і   і

  і Тип  і 0   і Память   і Размер типаі

  і Поле і Смещение поля  і Память   і Размер типаі

  і Переменная і Адрес переменной   і Память   і Размер типаі

  і Процедура  і Адрес процедуры і Память   і  NEAR / FARі

  і Функция і Адрес функции   і Память   і  NEAR / FARі

  і Модуль  і 0   і Непосредствен- і  0   і

  і   і і ный   і   і

  і @Code   і Адрес сегмента кодаі Память   і 0FFF0H  і

  і @Data   і Адрес сегмента  і Память   і 0FFF0H  і

  і   і  данных   і і  і

  і @Result і Смещение перемен-  і Память   і Размер типаі

  і   і ной результата і і   і

 АДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДЩ

Локальные переменные (переменные,  описанные в процедурах  и  Функциях)  всегда  распределяются в стеке и доступны относительно  SS:BP, а значение идентификатора локальной переменной представляет собой ее смещение со знаком от SS:BP.  ассемблер автоматически  добавляет [BP] к ссылкам на  локальные переменные.  Например,  с  учетом описаний:

procedure Test;

var

   Count: Integer;

 

  инструкции:

 

asm

   mov ax,Count

end;

 

  ассемблируются в MOV AX,[BP-2].

Встроенный ассемблер всегда интерпретирует параметр-переменную, как 32-разрядный указатель,  а размер  параметра-переменной  всегда равен 4 (размеру 32-разрядного указателя).  В Паскале синтаксис для доступа к параметру-переменной и к  значению параметра  одинаков. В случае встроенного ассемблера это не так. Поэтому для  доступа к содержимому параметра-переменной вам  сначала  придется  загрузить 32-разрядный указатель, а затем обратиться к ячейке, на которую он указывает. Например, если X и Y - параметры-переменные  приведенной  выше  функции Sum,  то она может выглядеть следующим  образом:

function Sum(var X, Y: Integer): Integer;

begin

   asm

  les bx,X

  mov ax,es:[bx]

  les bx,Y

  add ax,es:[bx]

  mov @Result,ax

   end;

    end;

Некоторые идентификаторы, такие, как переменные типа запись,  имеют область действия,  позволяющую обращаться к ним  с помощью  операции выбора элементы структуры - точки (.).  Например, с учетом описаний:

    type

   Point = record

  X, Y: Integer;

   end;

   Rect = record

  A, B: Point;

   end;

var

   P: Point;

   R: Rect;

  для доступа к полям в переменных P и R можно использовать следующие конструкции:

asm

   mov   ax,P.X

   mov   dx,P.Y

   mov   cx,R.A.X

   mov   bx,R.B.Y

end;

Для непосредственного построения переменной можно использовать идентификатор типа.  Каждая из приведенных  ниже инструкций  генерирует   один  и  тот же  машинный  код, загружающий  в  AX  ES:[DI+4]:

asm

   mov  ax,(Rect PTR es:[di]).B.X

   mov   ax,Rect(es:[di].B.X

   mov   ax,es:Rect[di].B.X

   mov   ax,Rect[es:di].B.X

   mov   ax,es:[di].Rect.B.X

end;

Область действия задается типов, полем и идентификатором переменной типа записи или объектного типа. Кроме того, идентификатор модуля открывает область  действия конкретного  модуля  (как  полностью уточненный идентификатор в Паскале).

Выражения встроенного ассемблера подразделяются на три класса:  регистровые  значения, ссылки  на память и непосредственные  значения.

Выражение, состоящее только из имени регистра,  является регистровым значением.  Примерами регистровых значений являются AX,  CL, DI и ES. Используемые в качестве операндов, регистровые выражения указывают ассемблеру на необходимость генерировать инструкции, которые работают с регистрами ЦП.

Выражения, обозначающие адреса памяти,  являются ссылками на  память. К этой категории относятся метки Паскаля, переменные, типизованные константы, процедуры и функции.

Выражения, которые не являются регистровыми и не  связаны  с  ячейками памяти,  представляют  собой  непосредственные значения.  Эта группа включает в себя нетипизированные константы и идентификаторы типа.

Непосредственные значения и ссылки на память при использовании их в качестве операндов приводят к генерации различного кода.

Например:

const

   Start = 10;

var

   Count: Integer;

.

.

.

asm

   mov  ax,Start  { MOV AX,xxxx }

   mov  bx,Count  { MOV BX,[xxxx] }

   mov  cx,[Start]   { MOV CX,[xxxx] }

   mov  dx,OFFSET Count { MOV DX,xxxx }

end;

Поскольку Start - это непосредственное значение, первая инструкция  MOV ассемблируется в непосредственную инструкцию. Однако  вторая инструкция MOV транслируется в инструкцию,  ссылающуюся на  память,  так как Count - это ссылка на память. В третьей инструкции MOV для преобразования Start в ссылку  на  память  (в  данном  случае слово со смещением 10 в сегменте данных) используется операция квадратных скобок.  В четвертой инструкции MOV для преобразования  Count в непосредственное значение (смещение Count в сегменте данных) используется операция OFFSET.

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

asm

   mov ax,OFFSET [Start]

   mov bx,[OFFSET Count]

end;

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

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

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

Каждое выражение встроенного ассемблера имеет  соответствующий тип,  или, если говорить точнее, размер, поскольку встроенный  Ассемблер рассматривает тип выражения просто как его размер в памяти. Например, тип (размер) переменной Integer равен 2, так как  она занимает два байта.

Там, где это возможно,  встроенный ассемблер выполняет  проверку типов, поэтому в инструкциях:

var

   QuitFlag: Boolean;

   OutBufPtr: Word;

.

.

.

asm

   mov al,QuitFlag

   mov bx,OutBufPtr

end;

  встроенный ассемблер проверяет,  что размер  QuitFlag  равен  1  (байт), а размер OutBufPtr - двум (слово). Если проверка типа обнаруживает несоответствие,  возникает ошибка. Например, следующее  недопустимо:

asm

   mov  dl,OutBufPtr

end;

  так как DL - это байтовый регистр, а OutBufPtr - слово. Тип ссылки на память можно изменить с помощью назначения типа. Корректным  способом записи предыдущих инструкций будет следующий:

asm

   mov  dl,BYTE PTR OutBufPtr

   mov  dl,Byte(OutBufPtr)

   mov  dl,OutBufPtr.Byte

end;

Все эти инструкции ссылаются на первый (менее значащий) байт  переменной OutBufPtr.

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

asm

   mov  al,[100H]

   mov  bx,[100H]

end;

Встроенный ассемблер  допускает обе этих функции,  поскольку  выражение [100H] не имеет соответствующего типа, оно просто означает "содержимое по адресу 100H в сегменте данных",  а тип можно  определить из первого операнда (байт для AL, слово для BX). В том  случае, когда тип нельзя определить из другого операнда, встроенный ассемблер требует явного назначения типа:

asm

   mov  BYTE PTR [100H]

   mov  WORD PTR [100H]

end;

В Таблице 24.4 приведены предопределенные идентификаторы типа, которые предусмотрены во встроенном ассемблере дополнительно  к типам, описанным в Паскале:

Предопределенные идентификаторы типа   Таблица 24.4

ЪДДДДДДДДДДДДДДДДДДДДВДДДДї

і  Идентификатор  і   Тип   і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДґ

і  BYTE  і   1  і

і  WORD  і   2  і

і  DWORD і   4  і

і  QWORD і   8  і

і  TBYTE і   10 і

і  NEAR  і   0FFFEH   і

і  FAR   і   0FFFFH   і

АДДДДДДДДДДДДДДДДДДДДБДДДДЩ

Заметим в частности,  что NEAR и FAR - это псевдотипы, которые используются с идентификаторами процедур и функций для указания их модели вызова. Вы можете использовать назначения типа NEAR  и FAR аналогично другим идентификаторам. Например, если FarProc -  процедура с дальним типом вызова (FAR):

procedure FarProc; far;

  и если вы записываете код встроенного ассемблера в том же модуле,  где находится  FarProc,  то вы можете использовать для ее вызова  более эффективную инструкцию NEAR:

asm

   push   cs

   call   NEAR PTR FarProc

end

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

   Встроенные операции ассемблера Таблица 24.5

 ЪДВДДДї

  і Операция     і Комментарий  і

 ГДЕДДДґ

  і &   і Операция переопределения  иден-і

  і   і тификатора.  і

 ГДЕДДДґ

  і (), [], * і Выбор элемента структуры.   і

 ГДЕДДДґ

  і HIGH, LOW    і Унарные операции.  і

  і +, -  і і

 ГДЕДДДґ

  і :   і Операция  переопределения  сег-і

  і   і мента. і

  і OFFSET, SEG, TYPE, PTR, і і

  і *, /, MOD, SHL, SHR  і і

  ГДЕДДДґ

  і +, -   і Бинарные операции сложения/вы- і

  і   і читания.  і

 ГДЕДДДґ

  і NOT, AND, OR, XOR  і Поразрядные операции. і

 АДБДДДЩ

Определения операций  встроенного ассемблера  Таблица 24.6

 ЪДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДї

  іОпер. і  Описание і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і & і Переопределение идентификатора.  Идентификатор,  непос-і

  і  і редственно следующий за амперсантом, интерпретируется,і

  і  і как идентификатор, определяемый пользователем, даже ес-і

  і  і ли он соответствует зарезервированному слову встроенно-і

  і  і го ассемблера.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і (...)і Подвыражение. Выражение  в скобках полностью вычисляет-і

  і  і ся,  после чего интерпретируется, как один элемент. Вы-і

  і  і ражению  в  скобках может предшествовать другое выраже-і

  і  і ние.  Результатом в этом случае  будет  сумма  значенийі

  і   і двух выражений с типом первого выражения.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і [...]і Ссылка на  память. Выражение в квадратных скобках пол-і

  і  і ностью вычисляется,  после чего  интерпретируется,  какі

  і  і один элемент. Выражение в квадратных скобках может ком-і

  і  і бинироваться с регистрами BX, BP, SI, DI с помощью опе-і

  і  і рации  +,  что указывает на индексирование регистра ЦП.і

  і  і Выражению в  квадратных  скобках может  предшествоватьі

  і  і другое выражение. Результатом в этом случае будет суммаі

  і  і значений двух выражений с типом первого выражения.  Ре-і

  і  і зультатом всегда будет ссылка на память.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і .і Выбор элемента структуры. Результатом будет сумма выра-і

  і  і жения перед точкой и выражения после точки с типом  вы-і

  і  і ражения после точки. Идентификаторы, относящиеся к об-і

  і  і ласти действия,  и указанные в выражении  перед  точкойі

  і  і доступны в выражении после точки. і

  ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і HIGH і Возвращает старшие  8 бит  выражения размером в слово,і

  і  і следующего за операцией. Выражение должно  представлятьі

  і  і собой непосредственное абсолютное значение.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і LOW і Возвращает младшие  8  бит выражения размером в слово,і

  і  і следующего за операцией. Выражение должно  представлятьі

  і  і собой непосредственное абсолютное значение.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і +і Унарный плюс.  Возвращает следующее за плюсом выражениеі

  і  і без изменений.  Выражение должно представлять собой не-і

  і  і посредственное абсолютное значение.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і -і Унарный минус.  Возвращает следующее за минусом выраже-і

     і   і ние с обратным знаком.  Выражение  должно представлятьі

  і  і собой непосредственное абсолютное значение.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і :і Переопределение сегмента. Указывает ассемблеру, что вы-і

  і  і ражение после двоеточия относится к сегменту, заданномуі

  і  і именем сегментного регистра (CS, DS,  SS или ES) переді

  і  і двоеточием.  Результатом является ссылка на  память  соі

  і  і значением выражения после двоеточия. Когда переопреде-і

  і  і ление сегмента используется в операнде инструкции, инс-і

  і  і трукции  предшествует соответствующий префикс переопре-і

  і  і деления  сегмента,  обеспечивающий   выбор   указанногоі

  і  і сегмента. і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  іOFFSETі Возвращает смещение  следующего за операцией выраженияі

  і  і (младшее  слово).  Результатом будет  непосредственноеі

  і  і значение. і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і SEG і Возвращает сегмент следующего  за  операцией выраженияі

  і  і (старшее  слово).  Результатом будет  непосредственноеі

  і  і значение. і

  ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і TYPE і Возвращает тип (размер в байтах) следующего за операци-і

  і  і ей выражения. Типом непосредственного значения будет 0.і

  ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і PTR і Операция назначения типа. Результатом будет ссылка  наі

  і  і память со значением выражения, следующего за операциейі

  і  і и типом выражения перед операцией.   і

  ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і *і Умножение. Оба выражения должны представлять собой  не-і

  і  і посредственные  абсолютные значения.  Результатом будеті

  і  і непосредственное абсолютное значение.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і /і Целочисленное деление.  Оба выражения должны  представ-і

  і  і лять  собой  непосредственные абсолютные значения.  Ре-і

  і  і зультатом будет непосредственное абсолютное значение.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і MOD і Остаток целочисленного деления. Оба  выражения  должныі

  і  і представлять  собой  непосредственные абсолютные значе-і

  і  і ния. Результатом будет непосредственное абсолютное зна-і

  і  і чение. і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і SHL і Логический сдвиг влево.  Оба выражения должны представ-і

  і  і лять собой непосредственные абсолютные  значения.  Ре-і

  і  і зультатом будет непосредственное абсолютное значение.  і

  ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і SHR і Логический сдвиг вправо. Оба выражения должны представ-і

  і  і лять собой непосредственные абсолютные  значения.  Ре-і

  і  і зультатом будет непосредственное абсолютное значение.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і +і Сложение. Выражения могут представлять собой непосредс-і

  і  і твенные абсолютные значения или ссылки  на  память,  ноі

  і  і перемещаемым  значением  может быть только одно выраже-і

  і  і ние. Если одно из выражений - перемещаемое значение, тоі

  і  і результатом также будет перемещаемое значение. Если од-і

  і  і но из выражений - ссылка на память, то результатом так-і

  і  і же будет ссылка на память.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і -і Вычитание. Первое  выражение может иметь любой класс, аі

  і  і второе выражение должно быть непосредственным  абсолют-і

  і  і ным выражением.  Результат имеет тот же тип, что и пер-і

  і  і вое выражение.  і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і NOT і Поразрядное отрицание. Выражение  должно  представлятьі

  і  і собой непосредственные абсолютные значения. Результатомі

  і  і будет непосредственное абсолютное значение.   і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і AND і Поразрядная операция  AND  (И). Оба  выражения  должныі

  і  і представлять  собой  непосредственные абсолютные значе-і

  і  і ния. Результатом будет непосредственное абсолютное зна-і

  і  і чение. і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і OR  і Поразрядная операция  OR  (ИЛИ). Оба  выражения должныі

  і  і представлять собой непосредственные абсолютные  значе-і

  і  і ния. Результатом будет непосредственное абсолютное зна-і

  і  і чение. і

 ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і XOR і Поразрядная операция XOR (исключающее ИЛИ). Оба выраже-і

  і  і ния должны представлять собой непосредственные абсолют-і

  і  і ные значения.  Результатом будет непосредственное абсо-і

  і  і лютное значение.   і

 АДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

До сих пор мы рассматривали конструкцию asm...end,  как оператор с обычной частью begin...end. Директива assembler в Borland  Pascal позволяет вам писать на встроенном ассемблере целиком процедуры  и функции без необходимости begin...end.  Приведем пример  функции на ассемблере:

function LongMul(X, Y: Integer) : Longint; assembler;

asm

   mov   ax,X

   imul  Y

end;

Директива assembler приводит к тому,  что Borland Pascal выполняет при генерации кода следующую оптимизацию:

- Компилятор не генерирует  код  для копирования  параметров-значений в локальные переменные. Это влияет на все параметры-значения строкового типа и  другие  значения-параметры,  размер которых не равен 1,  2 или 4 байтам. Внутри   процедуры или функции такие параметры должны интерпретироваться, как если бы они были параметрами-переменными.

- Компилятор  не выделяет память для результата функции, и   ссылка на идентификатор  @Result  будет ошибкой.  Однако   строковые  функции являются исключением из этого правила -   они всегда имеют указатель @Result, который распределяется   пользователем.

- Для процедур и функций, не имеющих параметров и локальных   переменных, компилятор не генерирует кадров стека.

- Для процедуры и функции на ассемблере автоматически  генерируется код выхода:

 push   bp  ; присутствует, если Locals <> 0 или

; Params <> 0

  mov bp,sp  ; присутствует, если Locals <> 0 или

; Params <> 0

  sub sp,Locals ; присутствует, если Locals <> 0

  ...

  mov sp,bp  ; присутствует, если Locals <> 0

  pop bp  ; присутствует, если Locals <> 0 или

; Params <> 0

 ret Params ; всегда присутствует

   где Locals - размер локальных переменных,  а Params - размер параметров.  Если и Locals и Params = 0, то кода входа  не будет, и код выхода состоит просто из инструкции RET.

Функции, использующие директиву assembler, должны возвращать  результат следующим образом:

- результаты   функции  порядкового  типа  (Integer, Char,   Boolean, перечислимые типы) возвращаются в AL (8-разрядное   значение),  AX (16-разрядное значение) или DX:AX (32-разрядное значение);

- результаты функции вещественного типа  (Real) возвращаются  в DX:BX:AX;

- результаты функции типов 8087 (Single,  Double,  Extended,   Comp) возвращаются в  ST(0) (регистр  стека  сопроцессора  8087);

- результаты функции типа указатель возвращаются в DX:AX;

- результаты функции строкового типа возвращаются во временной ячейке, на которую указывает @Result.

Директива assembler во многом похожа на  директиву external.  Процедуры и функции на ассемблере должны должны  подчиняться  тем  же правилам,  что и процедуры и функции типа external.  Следующие  примеры показывают некоторые отличия  операторов  asm в  обычных  процедурах и функциях от процедур и функций ассемблера.  В первом  примере оператор asm используется в обычной функции для  преобразования строки в верхний регистр. Заметим, что значение параметра  Str в этом случае ссылается на  локальную переменную,  поскольку  компилятор автоматически генерирует код входа,  копирующий фактический параметр в локальную память.

function UpperCase(Str: String): String;

begin

   asm

  cld

  lea     si,Str

  les  di,@Result

  SEGSS   lodsb

  stosb

  xor  ah,ah

  xchg ax,cx

  jcxz @3

@1:

  SEGSS   lodsb

  cmp al,'a'

  ja   @2

  cmp  al,'a'

  ja   @2

  cmp  al,'z'

  jb   @2

  sub  al,20H

@2:

 stosb

  loop @1

@3:

  end;

end;

Второй пример на ассемблере представляет собой версию  функции UpperCase.  В  этом случае Str не копируется в локальную память, и  функция должна интерпретировать Str,  как параметр-переменную.

function UpperCase(S: String): String; assembler;

asm

   push  ds

  cld

   lds   si,Str

   les   di@Result

   lodsb

   stosb

   xor   ah,ah

   xchg  ax,cx

   jcxz  @3

@1:

   lodsb

   cmp  al,'a'

   ja @2

   cmp   al,'z'

   jb @2

   sub   al,20H

@2:

   stosb

   loop @1

@3:

  pop  ds

end;