Ассемблерные инструкции размещаются внутри блока asm...end. Эти блоки могут появляться внутри процедур и функций обычного кода, но я настоятельно не рекомендую поступать таким образом. Гораздо лучше изолировать их в отдельной функции или процедуре. Вставка asm блока внутри обычной процедуры создает сложности для компилятора Паскаля и код становится не эффективным с точки зрения производительности. Переменные, которые обычно передаются через регистры, в этом случае будут передаваться через стек или перезагрузку. Также, это заставляет компилятор адаптировать собственный код к вашему вставленному коду, что делает механизм оптимизации менее эффективным. Так, что это становится правилом помещать ассемблерный код в отдельную процедуру или функцию. Кроме того - это вопрос проектирования. Читабельность и управляемость вашего кода становится выше, если он помещен в свой собственный блок.
Часто, ассемблерный код ассоциируется со скоростью. Поэтому циклы вы также должны по возможности организовывать внутри ассемблерного кода. Это не сложно, а иначе вы просто потеряете множество времени за счет постоянного вызова. Вместо того, чтобы делать так (см. примечание 1):
function CriticalCode(...): ...; register;
asm
...
{Here comes your assembler code}
...
end;
procedure Example;
var
I: Integer;
begin
I:=0;
...
while I < NumberOfTimes do begin
CriticalCode(...);
Inc(I);
end;
...
end;
Вы должны сделать так:
function CriticalCode(...): ...; register;
Asm
...
mov ECX,{NumberOfTimes}
@@loop:
...
{Остальной код}
...
dec ECX
jnz @@loop
...
end;
procedure Example;
begin
...
CriticalCode(...);
...
end;
Использование цикла в обратном направлении позволяет просто проверять флаг установки нуля после команды dec. Если же цикл начинать с нуля, то потребуется больше на одну команду сравнения с конечным значением, каждый раз при проходе цикла.
mov ECX,0
@@loop:
...
inc ECX
cmp ECX,{NumberOfTimes}
jne @@loop
Другая возможность – это вычесть значение NumberOfTimes из 0 и затем увеличивать переменную цикла, пока она не станет равной нулю. Этот метод обычно используется, когда переменная цикла также является индексом в таблице или массиве в памяти, поскольку механизм кэширования работает лучше, чем при доступе в прямом направлении. Это можно сделать так:
xor ECX,ECX
sub ECX,{NumberOfTimes}
@@loop:
...
inc ECX
jnz @@loop
Помните, что в этом случае базовый регистр или адрес, должен указывать на конец массива, вместо его начала.
(1) В данных главах мы специально указываем соглашение о вызове. На самом деле указание register избыточно, так как соглашением по умолчанию является передача параметров через регистры, это сделано исключительно для читабельности (или как дополнительный комментарий) и как напоминание читателю, что параметры передаются через регистры. Этот совет поступил от Christen Fihl, http://HSPascal.Fihl.net