                             ГЛАВА 23.

                РЕДАКТИРОВАНИЕ АССЕМБЛЕРНОГО КОДА.

     Процедуры и  функции,  написанные  на  ассемблере  могут  быть
связаны  с  программами и модулями Turbo Pascal с помощью директивы
компилятора  $L.  Исходный   файл   на   ассемблере   должен   быть
ассемблирован в  объектный файл (.OВJ) с помощью Turbo Assembler. С
программой или модулем можно связать несколько объектных  файлов  с
помощью нескольких директив $L.
     Процедуры и функции,  написанные на  ассемблере,  должны  быть
объявлены   в  программе  или  модуле  на  Паскале,  как  external,
например:

           function LoCase(Ch: Char): Char; external;

     В соответствующей  исходной  программе  на   ассемблере,   все
процедуры  и  функции  должны  быть расположены в сегменте с именем
CODE или CSEG,  или в сегменте,  чье имя оканчивается _TEXT и имена
внешних процедур и функций должны быть в директиве PUBLIC.
     Вы должны  быть  уверены,  что  процедура   или   функция   на
ассемблере соответствует ее определению на Паскале по модели вызова
(NEAR  или  FAR),  числу  параметров,  типу   параметров   и   типу
результата.
     Исходный файл на ассемблере может объявлять инициализированные
переменные  в  сегменте  с  именем  CONST  или в сегменте,  чье имя
оканчивается _DATA и неинициализированные переменные в  сегменте  с
именем  DATA или DSEG,  или в сегменте,  чье имя оканчивается _BSS.
Такие переменные являются локальными в программе на Ассемблере  и к
ним  нельзя обратиться из программы или модуля на Паскале.  Однако,
они размещаются в том же  сегменте,  что  и  глобальные  переменные
Паскаля и к ним можно обратиться через сегментный регистр DS.
     Ко всем  процедурам,  функциям  и  переменным,  объявленным  в
программе или  модуле  на  Паскале и объявленным в секции interface
используемых модулей,  можно обратиться из программы на  Ассемблере
через директиву EXTRN.  Конечно,  Вы должны использовать корректный
тип в описании EXTRN.
     Когда в  директиве $L появляется объектный файл,  Turbo Pascal
преобразует этот файл из перемещаемого  формата  объектного  модуля
Intel   (.OBJ)   в   свой   внутренний   перемещаемый  формат.  Это
преобразование возможно только при соблюдении правил:

     - Все процедуры должны быть размещены в сегменте с именем CODE
или CSEG,  или  в  сегменте,  чье  имя  оканчивается на _TEXT.  Все
инициализированные локальные  переменные   должны   размещаться   в
сегменте с  именем  CONST или сегменте с именем,  оканчивающимся на
_DATA. Все неинициализированные локальные  переменные  должны  быть
размещены в  сегменте  с  именем  DATA  или DSEG,  или в сегменте с
именем, оканчивающимся на _BSS.  Все другие сегменты  игнорируются,
также, как  и  директива GROUP.  Описания сегментов могут указывать
выравнивание BYTE или WORD;  при  редактировании  кодовые  сегменты
всегда выравнены  на  байт,  а  сегменты данных всегда выравнены на
слово. Описание сегментов могут указывать PUBLIC и имя  класса,  но
они игнорируются.

     - Turbo  Pascal игнорирует все данные для сегментов,  отличных
от сегментов  кода  (CODE,   CSEG   или   xxxх_TEXT)   и   сегменты
инициализированных данных (CONST или xxxx_DATA).  Когда объявляется
переменные в сегменте неинициализированных данных (DATA,  DSEG  или
xxxx_BSS), всегда  используйте  знак  (?)  для  указания  значений,
например:

     Count   DW ?
     Buffer  DB 128 DUP(?)

     - Однобайтовые   ссылки  к  EXTRN  символам  недопустимы.  Это
означает,  что операторы HIGH и LOW не могут  быть  использованы  с
EXTRN символами.


                  Turbo Assembler и Turbo Pascal.

     Turbo Assembler (TASM) позволяет просто  писать  программы  на
ассемблере  и  связывать  их  с Вашими программами на Turbo Pascal.
Turbo Assembler обеспечивает простую сегментацию,  модели памяти  и
языковую поддержку для программистов на Turbo Pascal.
     Использование TPASCAL  с   директивой   .MODEL   устанавливает
соглашения  о вызовах Паскаля,  определяет имена сегментов,  делает
PUSH BP и MOV BP,SP и также устанавливает возврат через  POP  BP  и
RET N (где N число байтов параметров).
     Директива PROC позволяет Вам определять ваши параметры  в  том
же порядке,  как они определены в Вашей программе на Паскале.  Если
Вы определяете функцию,  которая возвращает  строку,  помните,  что
директива PROC   имеет   опцию   RETURNS,   которая  позволяет  Вам
обращаться к указателю на временную строку в стеке без  учета числа
байт параметров, добавленных к оператору RET.
     Пример кода с использованием директив .MODEL и PROC:

        .MODEL TPASCAL
        .CODE
 MyProc  PROC FAR I: Byte, J: Byte RETURNS Result: DWORD
         PUBLIC MyProc
         les  DI,Result   ; получить адрес временной строки
         mov  AL,I        ; получить первый параметр I
         mov  BL,J        ; получить второй параметр J
         ...
         ret

     Описание функции на Паскале:

     function MyProc(I, J: Char): string; external;


                  Примеры программ на Ассемблере.

     Здесь представлен   пример   модуля,   который  реализует  две
программы  обработки  строк  на   Ассемблере.   Функция   UpperCase
преобразует  все  символы  в  строке в заглавные.  Функция StringOf
возвращает строку символов заданной длины.

     unit Strings;
     interfase
     function UpperCase(S: String): String;
     function StringOf(Ch: Char; Count: Byte): String;
     implementation
     {$L STRS}
     function UpperCase; external;
     function StringOf; external;
     end.

     Программа на  Ассемблере,  реализующая  функции  UpperCase   и
StringOf,  показана  ниже.  Она  должна  быть ассемблирована в файл
Strs.OBJ до  компиляции  модуля  Strings.  Заметим,  что  программы
используют   дальнюю   модель  вызова,  так  как  они  объявлены  в
интерфейсной части модуля.

     CODE    SEGMENT BYTE PUBLIC
             ASSUME  CS:CODE
             PUBLIC UpperCase, StringOf   ; объявление функций

     ; function UpperCase(S: String): String;

     UpperRes  EQU     DWORD PTR [BP+10]
     UpperStr  EQU     DWORD PTR [BP+6]

     UpperCase PROC FAR
         push   bp             ; сохранить bp
         mov    bp,sp          ; установить стек
         push   ds             ; сохранить ds
         lds    si,UpperStr    ; загрузить адрес строки
         les    di,UpperRes    ; загрузить адрес результата
         cld
         lodsb                 ; загрузить длину строки
         stosb                 ; копировать в результат
         mov    cl,al          ; длину строки в CX
         xor    ch,ch
         jcxz   U3             ; пропустить, если строка пустая
     U1: lodsb                 ; загрузить символ
         cmp  al,'a'           ; пропустить если не 'a'..'z'
         jb   U2
         cmp  al,'z'
         ja   U2
         sub  al,'a'-'a'       ; преобразовать в заглавные
     U2: stosb                 ; запомнить в результат
         loop U1               ; цикл для всех символов
     U3: pop  ds               ; востановить ds
         pop  bp               ; востановить bp
         ret  4                ; удалить параметр и возврат
     UpperCase  ENDP

      ; procedure StringOf(var S :string; Ch : char; Count: byte)

      StrOfs     EQU   DWORD PTR [BP + 10]
      StrOfChar  EQU   BYTE PTR [BP + 8]
      StrOfCount EQU   BYTE PTR [BP + 6]
      StringOf   Proc FAR
          push  bp              ; сохранить bp
          mov   bp,sp           ; установить стек
          les   di,StrOfRes     ; загрузить адрес результата
          mov   al,StrOfCount   ; загрузить счетчик
          cld
          stosb                 ; сохранить длину
          mov   cl,al           ; счетчик в CX
          xor   ch,ch
          mov   al, StrOfChar   ; загрузить символ
          rep   STOSB           ; сохранить строку символов
          pop   bp              ; восстановить bp
          ret   8               ; удалить параметр и возврат
      StringOf  ENDP
      CODE      ENDS
                END


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

     TASM STRS
     TPC strings

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

     program Numbers;
     {$L CHECK}
     var
        Buffer: array[1..100] of Integer;
        Count: Integer;
     procedure RangeError(N: Integer);
     begin
        WriteLn('Range error: ', N);
     end;

     procedure CheckRange(Min, Max: Integer); external;
     begin
        Count := 0;
        while not EOF and (Count < 100) do
        begin
           Count := Count + 1;
           ReadLn(Buffer[Count]);
     {закончится, когда пользователь введет CTRL-Z или
      после 100 итераций}
        end;
        CheckRange(-10,10);
      end.

     Программа на  Ассемблере,  реализующая  процедуру  CheckRange,
приведена ниже.
     Она должна быть ассемблирована в файл Check.OBJ  до компиляции
программы Numbers. Заметим, что процедура использует ближнюю модель
вызова, так как объявлена в программе.

     DATA SEGMENT WORD PUBLIC
          EXTRN   Buffer: WORD, Count: WORD;   ;Переменные Паскаля
     DATA ENDS
     CODE SEGMENT BYTE PUBLIC
          ASSUME CS: CODE, DS: Buffer
          EXTRN  RangeError: NEAR        ;реализован в Паскале
          PUBLIC CheckRange              ;реализован здесь
     CheckRange PROC NEAR
          mov   bx,sp           ;получить указатель параметров
          mov   ax,ss:[bx+4]    ;загрузить Min
          mov   dx,ss:[bx+2]    ;загрузить Max
          xor   bx,bx           ;очистить индекс данных
          mov   cx,Count        ;загрузить Count
          jcxz  SD4             ;пропустить если 0
     SD1: cmp   Buffer[BX],AX   ;слишком мал?
          jl    SD2             ;да, перейти
          cmp   Buffer[BX],DX   ;слишком велик?
          jle   SD3             ;нет, перейти
     SD2: push  ax              ;сохранить регистр
          push  bx
          push  cx
          push  dx
          push  Buffer[BX]      ;передать значение в Паскаль
          CALL  RangeError      ;вызвать процедуру Паскаля
          pop   dx              ;восстановить регистры
          pop   cx
          pop   bx
          pop   ax
     SD3: inc   BX              ;перейти к следующему элементу
          inc   BX
          loop  SD1             ;цикл для каждого элемента
     SD4: ret                   ;возврат

     CheckRange ENDP
     CODE       ENDS
     END


                    Пример на Turbo Assembler.

     Здесь представлена версия предыдущей программы  на Ассемблере,
которая показывает  преимущества  применения  Turbo  Assembler  при
стыковке с Паскалем:

     .MODEL  TPASCAL         ;модель кода Турбо-Паскаля
     LOCALS  @@              ;определить префикс локальных меток
     .DATA                   ;сегмент данных
     EXTRN   Buffer: WORD, Count: WORD;      ;Переменные Паскаля
     .CODE                          ;сегмент кода
     EXTRN  RangeError: NEAR        ;реализован в Паскале
     PUBLIC CheckRange              ;реализован здесь

ChechRange   Proc NEAR Min : WORD, Max : WORD

     mov   ax,Min        ;загрузить Min в ax
     mov   dx,Max        ;загрузить Max в dx
     xor   bx,bx         ;очистить индекс данных
     mov   cx,Count      ;загрузить Count
     jcxz  @@4           ;пропустить если 0
@@1: cmp   ax,Buffer[BX] ;слишком мал?
     jg    @@2           ;да, перейти на @@2
     cmp   dx,Buffer[BX] ;слишком велик?
     jge   @@3           ;нет, перейти на @@3
@@2: push  ax            ;сохранить регистр
     push  bx
     push  cx
     push  dx
     push  Buffer[BX]    ;передать значение в Паскаль
     call  RangeError    ;вызвать процедуру Паскаля
     pop   dx            ;восстановить регистры
     pop   cx
     pop   bx
     pop   ax
@@3: inc   BX            ;перейти к следующему элементу
     inc   BX
     loop  @@1           ;цикл для каждого элемента
@@4: ret                 ;возврат

CheckRange ENDP
     END

     Заметим, что  .MODEL  TPASCAL  Turbo  Assembler  автоматически
генерирует код входа до первой инструкции и код выхода для RET.


                     Встроенный машинный код.

     Для очень  коротких  программ  на  Ассемблере удобно применять
оператор  или  директиву  Inline.  Они   позволяют   Вам   вставить
инструкции машинного кода прямо в текст программы или модуля вместо
использования объектного файла.


                         Оператор Inline.

     Оператор Inline  состоит  из зарезервированного слова Inline и
следующих за ним одного или более  элементов,  разделенных  слэшами
(/) и заключенных в скобки:

     inline(10/$2345/Count+1/Data-OffSet);

     Синтаксис оператора Inline:


                    +——————+   +—+     +——————————————+    +—+
оператор Inline ———Ў|inline+——Ў|(+————Ў|элемент inline+—+—Ў|)+——Ў
                    +——————+   +—+  °  +——————————————+ |  +—+
                                    |       +—+         |
                                    +———————+/|ў————————+
                                            +—+


     Каждый элемент   оператора   Inline   состоит   из  возможного
указателя  размера,  <  или  >  и  константы   или   идентификатора
переменной  идущими  за  0  или  более  указателями  смещения  (см.
синтаксис  ниже).  Указатель  смещения  состоит  из  +  или   -   с
константой.


элемент inline
 |              +————————+
 +—+———————————Ў|сonstant|—————————————————————————————————————Ў
   |       °    +————————+                                  °
   |  +—+  |                                                |
   +—Ў|<+——+                                                |
   |  +—+  |                                                |
   |  +—+  |                                                |
   +—Ў|<+——+                                                |
   |  +—+                                                   |
   |  +————————————————————————+                            |
   +—Ў|идентификатор переменной+—+——————————————————————————+
      +————————————————————————+ |                        °
                                 |   +————+  +—————————+  |
                                 +——Ў|знак+—Ў|константа+—++
                                   ° +————+  +—————————+ |
                                   +—————————————————————+


     Каждый элемент оператора Inline генерирует 1 байт или  1 слово
кода.   Значения  вычисляются  из  значения  первой  константы  или
смещения идентификатора  переменной,  к  которому добавлено/вычтено
значение каждой из констант, которые следуют за ним.
     Элемент Inline генерирует 1 байт кода,  если он состоит только
из  констант  и  если  их  значения  внутри  8-и битового диапазона
(0..255).  Если значение выходит за 8-и битовый диапазон,  или если
элемент  Inline  ссылается к переменной - генерируется код длиной в
одно слово (меньший значащий байт стоит первым).
     Операторы <  и  >  могут  быть  использованы  для того,  чтобы
перекрыть  автоматический  выбор  размера,  описанный  ранее.  Если
элемент  Inline  начинается  с  оператора  <,  только  меньший байт
значения будет кодироваться,  даже если это 16-и битовое  значение.
Если элемент Inline начинается с оператора >, то будет кодироваться
слово, даже если наибольший байт равен 0. Например, оператор

     Inline(<$1234/>$44);

     генерирует 3 байта кода: $34,$44,$00.
     Значение идентификатора    переменной    в   элементе   Inline
представляет собой смещение адреса переменной  внутри  ее  базового
сегмента.   Базовой  сегмент  глобальной  переменной  -  переменной
объявленной  на  внешнем  уровне  в  программе   или   модуле   или
типированной константы - это сегмент данных,  к которому обращаются
через  регистр  DS.  Базовый   сегмент   локальной   переменной   -
переменной,  объявленной  внутри текущей подпрограммы - это сегмент
стека.  В этом случае переменная смещена относительно регистра  BP,
который автоматически ссылается на сегмент стека.

     Примечание: Регистры  BP,  SP,  SS  и DS должны быть сохранены
оператором Inlile; все остальные регистры могут изменяться.

     Следующий пример оператора Inline генерирует машинный  код для
запоминания  заданного  числа  слов  данных  в заданной переменной.
Процедура FillWord запоминает Count слов в значении Data  в памяти,
начиная с первого байта Dest.

     procedure FillWord(var Dest; Count, Data: Word);
     begin
        Inline(
           $C4/$BE/Dest/       {LES DI, Dest[BP]}
           $8B/$8E/Count/      {MOV CX, Count[BP]}
           $8B/$86/Data/       {MOV AX, Data[BP]}
           $FC/                {CLD}
           $F3/$AB);           {REP STOSW}
     end;

     Оператор Inline   может   быть   свободно   смешан  с  другими
операторами в операторной части блока.


                         Директива Inline.

     Директива Inline позволяет Вам  писать  процедуры  и  функции,
вместо  которых  вставляется  данная  последовательность инструкций
машинного кода,  в том  месте,  где  они  вызываются.  Они  подобны
макросам  в  Ассемблере.  Синтаксис директивы Inline такой же как и
оператора Inline:


                     +————————————————+
директива Inline ———Ў|оператор  inline|
                     +————————————————+

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

     procedure DisableInterrupts; Inline($FA);      {CLI}
     procedure EnableInterrupts; Inline($FB);       {STI}

     Когда вызывается  DisableInterrupts - генерируется 1 байт кода
- инструкция CLI.
     Процедуры и  функции,  объявленные с директивой Inline,  могут
иметь параметры;  однако,  к параметрам нельзя обращаться по  имени
(но  к  другим  переменным  можно).  Также  из-за  того,  что такие
процедуры и  функции  в   действительности   макро,   в   них   нет
автоматического  входного  и  выходного  кода,  и  не  должно  быть
инструкции возврата.
     Следующая функция   умножает   два  целых  значения,  создавая
результат типа LongInt:

     function LongMul(X, Y: Integer): Longint;
     Inline (
         $5A/          {POP AX; POP X}
         $5E/          {POP DX; POP Y}
         $F7/$EA);     { IMUL DX; DX : AX = X * Y}


     Заметьте отсутствие   входного  и  выходного  кода  и  пропуск
инструкции выхода.  Здесь они не требуются,  так как  эти  4  байта
вставляются в остальной код при вызове LongMul.
     Директива Inline  применяется  только   для   очень   коротких
процедур и функций ( < 10 байт).
     Так как Inline процедуры и функции - это макро,  они не  могут
использоваться как аргументы оператора @ и функций Addr, Ofs и Seg.
