ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 24

                           Ч А С Т Ь  4.

          ИСПОЛЬЗОВАНИЕ TURBO PASCAL С ЯЗЫКОМ АССЕМБЛЕРА.

                             ГЛАВА 22.

                       ВСТРОЕННЫЙ АССЕМБЛЕР.

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

     Встроенный Ассемблер    реализует     большое     подмножество
синтаксиса, поддерживаемого   Turbo  Assembler  и  макроассемблером
Microsoft. Встроенный Ассемблер поддерживает все коды операций 8086
/8087 и   80286/80287   и   почти  все  операторы  выражений  Turbo
Assembler.

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


                           Оператор asm.

     К встроенному   Ассемблеру   обращаются  через  оператор  asm.
Синтаксис оператора asm:

     asm AsmStatement < Separator AsmStatement > end

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

     if EnableInts then
       asm
         sti
       end
       else
       asm
         cli
       end;

     asm
       mov ax,Left; xchg ax,Right; mov Left,ax;
     end;
     asm
       mov    ah,0
       int    16H
       mov    CharCode,al
       mov    ScanCode,ah
     end;

     asm
       push   ds
       lds    si,Source
       les    di,Dest
       mov    cx,Count
       cld
       rep    movsb
       pop    ds
     end;

     Заметим, что  на  одной  строке  можно   поместить   несколько
операторов Ассемблера,  разделенных ";". Заметим так же, что ";" не
требуется между двумя ассемблерными операторами, если они на разных
строках. Наконец  заметим,  что  ";"  не указывает,  что оставшаяся
часть строки - комментарий,  т.к. комментарий должен быть написан в
стиле Паскаля, используя {} и (* *).


                     Использование регистров.

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


                Синтаксис ассемблерных операторов.

     Синтаксис ассемблерного оператора:

    [ Label ":" ] < Prefix > [ Opcode [ Operand < "," Operand > ] ]

     где Label - идентификатор метки, Prefix - код префикса, Opcode
- директива или инструкция  Ассемблера  и  Opеrand  -  ассемблерное
выражение.

     Комментарии разрешены  между  ассемблерными операторами, но не
внутри их. Например, это разрешено:

     asm
       mov   ax,1        {Initial value}
       mov   cx,100      {Count}
     end;

     а это нет:

     asm
       mov   {Initial value} ax,1
       mov   cx,{Count} 100
     end;


                              Метки.

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

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

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

     Идентификатор локальной   метки   состоит   из   символа  @  с
последующей одной или более буквой A..Z,  цифр 0..9, "_" или @. Как
для всех меток, после идентификатора идет ":".
     Следующий фрагмент   программы   демонстрирует   использование
локальных и глобальных меток в операторе asm:

     label Start, Stop;
       ...
     begin
       asm
         Start:
         ...
           jz      Stop
         @1:
           ...
           loop    @1
       end;
       asm
         @1:
           ...
           jc      @2
           ...
           jmp    @1
         @2:
       end;
       goto Start;
       Stop:
     end;


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

                             Префикс.

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

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
     LOCK              Захват шины
     REP               Повтор строковой операции
     REPE/REPZ         Повтор строковой операции пока =/0
     REPNE/REPNZ       Повтор строковой операции пока (не =)/(не 0)
     SEGCS             Перекрытие CS (сегмент кода)
     SEGDS             Перекрытие DS (сегмент данных)
     SEGES             Перекрытие ES (экстра сегмент)
     SEGSS             Перекрытие SS (сегмент стека)
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

     С ассемблерной инструкцией могут  быть  указаны  0  или  более
префиксов. Например:

     asm
       rep    movsb
       SEGES  lodsw
       SEGCS  mov ax,[bx]
       SEGES
       mov    WORD PTR [DI],0
     end;

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

     Код инструкции  очень  редко  имеет более одного префикса и не
может быть указано более  3  префиксов  (LOC,  затем  SEGxx,  затем
REPxx). Будьте внимательны при использовании нескольких префиксов -
их порядок важен и некоторые процессоры 80х86 не могут обрабатывать
все комбинации  правильно.  Например,  8086  или 8088 помнят только
префикс REPxx,  если  в  середине  строковой  инструкции  возникает
прерывание. Поэтому  префиксы  LOC  и  SEGxx  не могут быть надежно
закодированы до REPxx в строковой инструкции.


                         Коды инструкций.

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

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


                      Размер инструкции RET.

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

     procedure NearProc; near;
     begin
       asm
         ret     {генерирует ближний вызов}
       end;
     end;

     procedure FarProc; far;
     begin
       asm
         ret     {генерирует дальний вызов}
       end;
     end;

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


                  Автоматический размер перехода.

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

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

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

     JC Stop

     где Stop не внутри диапазона короткого перехода, преобразуется
в машинный код

     jnc Skip
     jmp Stop
     Skip:

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

     jmp NEAR PTR Stop
     jmp FAR PTR Stop

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


                       Директивы Ассемблера.

     Встроенный Ассемблер  Turbo  Pascal  поддерживает  3 директивы
Ассемблера:

     DB, DW, DD (определить байт, слово и двойное слово)

     Они генерируют данные,  соответствующие операндам, разделенным
запятыми, в этой директиве.

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

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

     Директива DD   генерирует   последовательность  двойных  слов.
Каждый операнд может быть константным выражением  со  значением  от
-2,147,483,648 до   4,294,967,295   или  адресным  выражением.  Для
адресного  выражения  встроенный   Ассемблер   генерирует   дальний
указатель, который содержит и смещение и сегментную часть адреса.

     Данные, генерируемые директивами DB,  DW, DD всегда хранятся в
кодовом сегменте так же,  как код, генерируемый другими операторами
встроенного Ассемблера. Чтобы генерировать неинициализированные или
инициализированные данные, в сегменте данных Вы должны использовать
обычные объявления Var или Const Паскаля.

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

     asm
       DB   0FFH
       DB   0,99
       DB   'A'
       DB   'Hello world...',0DH,0AH
       DB   12,"Turbo Pascal"
       DW   0FFFFH
       DW   0,9999
       DW   'A'
       DW   'BA'
       DW   MyVar
       DW   MyProc
       DD   0FFFFFFFFH
       DD   0,999999999
       DD   'A'
       DD   'DCBA'
       DD   MyVar
       DD   MyProc
     end;

     Примечание: В Turbo Assembler, когда идентификатор стоит перед
директивой DB,  DW и  DD,  это  приводит  к  объявлению  переменной
размером в байт,  слово или двойное слово по адресу этой директивы.
Например, Turbo Assembler разрешает:

     ByteVar   DB    ?
     WordVar   DW    ?
     ...
               mov   al,ByteVar
               mov   bx,WordVar

     Встроенный Ассемблер   не   поддерживает   такое    объявление
переменных. В Turbo Pascal единственный символ,  который может быть
определен в операторе  встроенного  Ассемблера  -  это  метка.  Все
переменные должны   быть   объявлены  с  использованием  синтаксиса
Паскаля и предыдущий пример соответствует:

     var
       ByteVar: Byte;
       WordVar: Word;
       ...
     asm
       mov   al,ByteVar
       mov   bx,WordVar
     end;


                             Операнды.

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

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

     AH           CL           FAR           SEG
     AL           CS           HIGH          SHL
     AND          CX           LOW           SHR
     AX           DH           MOD           SI
     BH           DI           NEAR          SP
     BL           DL           NOT           SS
     BP           DS           OFFSET        ST
     BX           DWORD        OR            TBYTE
     BYTE         DX           PTR           TYPE
     CH           ES           QWORD         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 во время
компиляции. Встроенный    Ассемблер   для   пересылки   суммы   X+Y
генерирует:

     asm
       mov   ax,X
       add   ax,Y
       mov   Z,ax
     end;

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

     asm
       mov   ax,X+4
     end;

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

     asm
       MOV   AX,X
       ADD   AX,4
     end;


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

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


                            Константы.

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


                        Числовые константы.

     Числовые константы должны быть целыми  и  их  значения  должны
быть в диапазоне от -2,147,483,648 до 4,294,967,295.

     Числовые константы по умолчанию используют десятичную нотацию,
но встроенный Ассемблер так же поддерживает двоичную, 8-ричную и 16
-ричную нотации.  Двоичная  нотация  выбирается  написанием В после
числа, 8-ричная нотация выбирается написанием буквы О после числа и
16-ричная нотация  выбирается  написанием  Н  после  числа или $ до
числа.

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

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


                       Строковые константы.

     Строковые константы  должны  быть  заключены  в  кавычки   или
апострофы. Две  последовательные  "" или '' внутри строки считаются
как один символ. Примеры строковых констант:

     'Z'
     'Turbo Pascal'
     "That's all folks"
     '"That"s all folks,"he said.'
     '100'
     '"'
     "'"

     Заметим, что  в  4  строке  использовались  2  апострофа   для
указания одиночного символа "апостроф".

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

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

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

     'a'       00000061H
     'ba'      00006261H
     'cba'     00636261H
     'dcba'    64636261H
     'a '      00006120H
     '   a'    20202061H
     'a'*2     000000E2H
     'a'-'A'   00000020H
     not'a'    FFFFFF9EH


                             Регистры.

     Следующие зарезервированные символы означают регистры 8086:

     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)  могут использоваться
вместе с  ":"  как  перекрытие  сегмента  для  указания   сегмента,
отличного от того, который процессор выбирает по умолчанию.

     Символ S  означает  самый  верхний  регистр из регистров стека
8087. Каждый  из  8  регистров  с  плавающей  точкой   может   быть
использован с  помощью  ST(x),  где  х  -  константа  от  1  до  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.

     - Константы строковые, с плавающей точкой и типа множество.

     - Процедуры и функции, объявленные с директивой inline.

     - Метки, которые не объявлены в текущем блоке.

     - Символ @Result вне функции.

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

          Таблица 22.1. Значения, классы и типы символов.

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
   Символ         Значение           Класс            Тип
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
   Метка        Адрес метки         Память            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]

     Встроенный Ассемблер всегда интерпретирует var параметр как 32
-битный указатель  и  размер  var  параметра   всегда   4   (размер
32-битного указателя).  В  Паскале  синтаксис  для  доступа  к  var
параметру и параметру  значению  одинаков.  Не  так  во  встроенном
Ассемблере. Поскольку  var  параметры в действительности указатели,
Вы должны интерпретировать их так во  встроенном  Ассемблере.  Так,
чтобы обратиться к содержимому var параметра, Вы вначале загружаете
32-битный указатель,  а затем обращаетесь к памяти,  на которую  он
указывает. Например, если X и Y - var параметры функции 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;

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

     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;

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


                         Классы выражений.

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

     Выражение, которое  состоит  только  из  имени  регистра - это
регистровое выражение.  Пример регистрового выражения - это 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  оператор  OFFSET
используется для  преобразования  Count в непосредственное значение
(смещение Count в сегменте данных).

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

     asm
       mov   ax,OFFSET [Start]
       mov   bx,[OFFSET Count]
     end;

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

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

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


                          Типы выражений.

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

     var
       QuitFlag: Boolean;
       OutBufPtr: Word;
     ...
     asm
       mov   al,QuitFlag
       mov bx,OutBufPtr
     end;

     встроенный ассемблер проверяет что размер QuitFlag - 1  байт, а
размер OutBufPtr  -  2 байта.  Если тип неправильный,  то возникает
ошибка; например, неверно:

     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
       inc   BYTE PTR [100H]
       imul  WORD PTR [100H]
     end;

     Таблица 22.2 суммирует предопределенные типы символов, которые
встроенный Ассемблер предоставляет в дополнение к типа Паскаля.

           Таблица 22.2 Предопределенные типы символов.

                   ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                        Символ           Тип
                   ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
                        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 классов   по   приоритетам.   Таблица  22.3  приводит  операторы
встроенного Ассемблера в порядке уменьшения их приоритета.

     Таблица 22.3. Операторы выражений встроенного Ассемблера.

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
            Оператор                   Комментарий
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
     &                         Оператор перекрытия идентификатора
     ()                        Селектор элемента структуры
     []
     .
     HIGH LOW
     + -                       Унарные операторы
     :                         Оператор перекрытия сегмента
     OFFSET SEG TYPE PTR *
     / MOD SHL SHR
     + -                       Бинарные операторы
     NOT AND OR XOR            Побитовые операторы
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

     Примечание: Приоритет   операторов   встроенного    Ассемблера
отличается от Паскаля.  Например, в ассемблерном выражении оператор
AND имеет меньший приоритет,  чем операторы +  и  -,  а  в  Паскале
наоборот.

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

     (...)
     Подвыражение. Выражения внутри () вычисляются до интерпретации
как элемент  выражения.  Другое  выражение   может   предшествовать
выражению внутри  ();  результат  в  этом  случае становится суммой
значений двух выражений с типом первого выражения.

     [...]
     Ссылка на память. Выражение внутри [] полностью вычисляется до
интерпретации, как один  элемент  выражения.  Выражение  внутри  []
может быть комбинировано с регистрами BX,  BP,  SI,  DI,  используя
оператор +  для  указания  индексации.   Другое   выражение   может
предшествовать выражению в []; результат становится суммой значений
двух выражений с типом первого выражения.  Результат всегда  ссылка
на память.

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

     HIGH
     Возвращает старшие 8 бит выражения типа слово,  следующего  за
оператором. Выражение   должно   быть  абсолютным  непосредственным
значением.

     LOW
     Возвращает младшие  8 бит выражения типа слово,  следующего за
оператором.  Выражение  должно  быть  абсолютным   непосредственным
значением.

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

     "-"
     Унарный минус.   Возвращает   выражение,  следующее  за  -,  с
отрицательным значением.   Выражение   должно    быть    абсолютным
непосредственным значением.

     ":"
     Перекрытие сегмента. Указывает Ассемблеру, что выражение после
":" относится к сегменту с именем сегментного регистра (CS, DS, SS,
ES) до ":".  Результат - ссылка на память  со  значением  выражения
после ":".   Когда  перекрытие  сегмента  используется  в  операнде
инструкции, инструкция будет предварена  соответствующим  префиксом
перекрытия сегмента,   гарантируя,   что   будет  выбран  указанный
сегмент.

     OFFSET
     Возвращает смещение  (младшее слово) выражения,  следующего за
оператором. Результат - непосредственное значение.

     SEG
     Возвращает сегментную   часть   (старшее   слово)   выражения,
следующего за оператором. Результат - непосредственное значение.

     TYPE
     Возвращает тип  (размер  в  байтах)  выражения,  следующего за
оператором. Тип непосредственного значения - 0.

     PTR
     Оператор приведения  типа.  Результат  -  ссылка  на память со
значением выражения,  следующего за оператором и  типом  выражения,
стоящего перед оператором.

     "*"
     Умножение. Оба    выражения    должны     быть     абсолютными
непосредственными значениями     и     результат    -    абсолютное
непосредственное значение.

     /
     Целое деление.   Оба   выражения   должны   быть   абсолютными
непосредственными   значениями    и    результат    -    абсолютное
непосредственное значение.

     MOD
     Остаток от  целого  деления.   Оба   выражения   должны   быть
абсолютными  непосредственными  значениями и результат - абсолютное
непосредственное значение.

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

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

     "+"
     Сложение. Выражения  могут  быть  непосредственными значениями
или ссылками к памяти,  но только  одно  из  выражений  может  быть
перемещаемым значением.  Если  одно  из  выражений  -  перемещаемое
значение, то результат так же перемещаемое значение.  Если одно  из
выражений - ссылка на память, то результат так же ссылка на память.

     "-"
     Вычитание. Первое выражение может быть любого класса, а второе
выражение должно   быть   абсолютным   непосредственным  значением.
Результат того же класса, что и первое значение.

     NOT
     Побитовое отрицание.    Выражение   должно   быть   абсолютным
непосредственным    значением    и    результат    -     абсолютное
непосредственное значение.

     AND
     Побитовое "и".   Оба   выражения   должны   быть   абсолютными
непосредственными    значениями    и    результат    -   абсолютное
непосредственное значение.

     OR
     Побитовое "или".   Оба   выражения   должны  быть  абсолютными
непосредственными   значениями    и    результат    -    абсолютное
непосредственное значение.

     XOR
     Побитовое исключающее  "или".  Оба   выражения   должны   быть
абсолютными  непосредственными  значениями и результат - абсолютное
непосредственное значение.


                 Ассемблерные процедуры и функции.

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

     function LongMul(X, Y: Integer): Longint; assembler;
     asm
       mov   ax,X
       imul  Y
     end;

     Директива Ассемблера  заставляет  Turbo  Pascal  выполнить ряд
оптимизаций при генерации кода:

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

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

     - Компилятор  не генерирует кадра стека для процедур и функций
без параметров и локальных переменных.

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

     push   bp
     mov    bp,sp
     sub    sp,Locals
     ...
     mov    sp,bp
     pop    bp
     ret    Params

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

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

     - Функции  порядкового  типа   (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.

     Ассемблерная директива  во  многом   сравнима   с   директивой
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    a1,'z'
         jb     @2
         sub    a1,20H
       @2:
         stosb
         loop
       @3:
       end;
     end;

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

     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    a1,'a'
       ja     @2
       cmp    a1,'z'
       jb     @2
       sub    a1,20H
     @2:
       stosb
       loop   @1
     @3:
       pop    ds
     end;


Яндекс цитирования