Содержание материала

 

Урок 2

Это вторая глава введения в программирование с помощью BASM в Delphi. В первой главе было короткое введение в целочисленный код, а в этой главе введение в код с плавающей запятой. В нашем примере мы рассчитаем полином второго порядка. Параметры A, B и C, которые определяют полином, закодированы как локальные константы. В функцию передается переменная X типа double и результат также типа double. Функция выглядит так.

Code:

function SecondOrderPolynomial1(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

begin

Result := A*X*X + B*X + C;

end;

 

Просмотр кода в окне CPU показывает следующее.

Code:

function SecondOrderPolynomial2(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

begin

{

push  ebp

mov   ebp,esp

add   esp,-$08

}

Result := A*X*X + B*X + C;

{

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp st(1)

fadd  qword ptr [C]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

}

{

pop   ecx

pop   ecx

pop   ebp

}

end;

 

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

Code:

begin

{

push  ebp

mov   ebp,esp

add   esp,-$08

}

 

 

Здесь устанавливается фрейм стека для функции. Фрейм стека просто часть памяти, которая выделена в стеке. Фрейм стека доступен через два указателя, указатель базы и указатель стека. Указатель базы это регистр EBP и указатель стека это регистр ESP. Эти два регистра резервированы только для использования в качестве этих указателей. Первая инструкция PUSH EBP сохраняет указатель базы. В строке MOV EBP, ESP устанавливается новая база для адресации по стеку. В строке ADD ESP, -$08 указатель стека смещается на 8 вниз. Как курьез, стек увеличивается вниз, и более понятной командой было бы его установка с помощью инструкции SUB ESP, 8. Новый фрейм стека устанавливается с помощью этих трех строк, поверх старого фрейма,  который был размещен функцией, которая вызвала нашу функцию SecondOrderPolynomial.

Следующая строка Паскаля компилируется в 9 строк на ассемблере.

Code:

Result := A*X*X + B*X + C;

{

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp st(1)

fadd  qword ptr [C]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

}

 

 

Для тех, кто использует калькуляторы HP для расчетов с плавающей запятой, данный код очень прост для понимания. В первой строке, FLD QWORD PTR [A], загружается константа A в регистр стека с плавающей запятой. Строка, FMUL QWORD PTR [EBP+$08], умножает A на X. Это понятно при просмотре Паскаль кода, но что означает "QWORD PTR [EBP+$08]". QWORD PTR означает "указатель на двойное слово, которое размером с double (64 бита). Значение указателя между квадратными скобками [EBP+$08]. Регистр EBP это указатель базы и $08 это да просто 8. Поскольку стек при увеличении движется вниз, то это смещение на 8 байт вверх относительно указателя базы в текущем фрейме. Здесь находится переданный параметр X, помещенный сюда вызывающей функцией. При соглашение о регистром вызове, значение не помещается в 32-разрядный регистр, но оно хорошо помещается в регистр с плавающей запятой. Borland решил передавать параметры с плавающей запятой двойной точности через стек, но передача через регистры с плавающей запятой, была бы более эффективной. Следующие три строки не требуют специального пояснения, but the line, но инструкция FADDP ST(1), нуждается в объяснении. Все инструкции с плавающей запятой начинаются с префикса f. add это сложение. ST(1) это название регистра с плавающей запятой номер 1, который является вторым, поскольку первый регистр это ST(0)! Регистры с плавающей запятой скомпонованы в стек и инструкции по умолчанию работаю с верхушкой стека, которая равна ST(0). FADDP ST(1) идентична инструкции FADDP ST(0), ST(1) - складывает содержимое регистров ST(0) и ST(1), результат помещается в регистр ST(1). P в FADDP означает POP ST(0) из стека. Таким путем результат помещается в ST(0). Строка FADD QWORD PTR [C] заканчивает вычисление, и единственная вещь, которая осталась, это помещения результата в  ST(0). Результат и так уже там, поэтому две следующие строки кода излишни.

fstp  qword ptr [ebp-$08]

fld   qword ptr [ebp-$08]

Они просто копируют результат на стек и обратно. Такая затрата времени и энергии :-). Инструкция WAIT обеспечивает обработку возможных исключений при выполнении операций с плавающей запятой. Смотри руководство Intel SW Developers Manual Volume 2, страницу 822 для полного понимания этого.

Осталось объяснить еще три строки кода.

Code:

{

pop   ecx

pop   ecx

pop   ebp

}

end;

 

 

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

add esp, 4

pop ebp

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

Осталось также объяснить, что скрывается за [A] в строке fld qword ptr [A]. Мы знаем, что A должен быть указателем на место, где хранится само A в памяти. Адрес A закодирован в инструкции. Вот полная строка из окна CPU.

00451E40 DD05803C4500     fld qword ptr [B]

00451E40 это адрес инструкции в exe файле. DD05803C4500 это машинный код строки FLD QWORD PTR [B], которая более понятна для человеческого разума. При просмотре руководства Intel SW Developers Manual Volume 2, страница 280, мы увидим, что код команды для FLD равен D9, DD, DB или D9C0, в зависимости от типа данных. Мы узнаем, что DD это код для FLD DOUBLE. Остается еще 05803C4500. 05 это (Не знаю, может быть, кто-то поможет мне!), а 803C4500 это 32-битный адрес константы A.

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

Code:

function SecondOrderPolynomial3(X : Double) : Double;

const

A : Double = 1;

B : Double = 2;

C : Double = 3;

 

asm

push  ebp

mov   ebp,esp

add   esp,-$08

//Result := A*X*X + B*X + C;

fld   qword ptr [A]

fmul  qword ptr [ebp+$08]

fmul  qword ptr [ebp+$08]

fld   qword ptr [B]

fmul  qword ptr [ebp+$08]

faddp //st(1)

fadd  qword ptr [C]

fstp  qword ptr [ebp-$08]

wait

fld   qword ptr [ebp-$08]

pop   ecx

pop   ecx

pop   ebp

end;

 

Добавить комментарий

Не использовать не нормативную лексику.

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

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить