Сборка AT&T: как умножить двойное на 2 без локальной переменной. Сдвиг двойной?
[домашнее задание]: попробуйте сделать именно то, что написано в коде C.
Полный код функций с C-версией прокомментирован:
/*
double f(double x)
{
return x * 2.0;
}
double foo (int a[], double b[], int n)
{
int *pint;
double *pdouble;
double sum = 0.0;
for (pint = a, pdouble = b; n-- ; pint++, pdouble++)
{
*pdouble = f((double)*pint);
sum += *pdouble;
}
return sum;
}
*/
.text
.globl f
f:
pushl %ebp
movl %esp, %ebp
pushl %ebx
/*fldl 8(%ebp)
fstpl 8(%ebp)*/
/*movl 8(%ebp), %ebx*/
shll $2, 8(%ebp)
fldl 8(%ebp)
popl %ebx
movl %ebp, %esp
popl %ebp
ret
.globl foo
foo:
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %esi
subl $16, %esp /* for the local variables */
fldz /* 0 to the top of the FPU's stack */
fstpl (%esp) /* double sum = 0 */
movl 8(%ebp), %ebx /* > pint */
movl %ebx, -4(%ebp) /* = a < | INIT */
movl 12(%ebp), %esi /* > pdouble */
movl %esi, -8(%ebp) /* = b < | INIT */
L1: cmpl $0, 16(%ebp)
js outfor
pushl %eax
pushl %ecx
pushl %edx
movl -4(%ebp), %edi
fildl (%edi) /* *pint to the top of FPU'S stack */
subl $8, %esp /* > pushdouble */
fstpl (%esp) /* *pint < */
call f /* f((double)*pint) to the top of FPU'S stack */
addl $8, %esp /* clears double pint from stack */
popl %edx
popl %ecx
popl %eax
movl -8(%ebp), %eax
fldl (%eax) /* *pdouble to the top of FPU's stack */
faddl -16(%ebp) /* sum += *pdouble (stored in FPU'S stack) */
fstpl -16(%ebp) /* sum += *pdouble (stored in sum) */
decl 16(%ebp)
addl $4, -4(%ebp)
addl $8, -8(%ebp)
jmp L1
outfor: fldl -16(%ebp)
popl %esi
popl %ebx
movl %ebp, %esp
popl %ebp
foo
позвоню f
в той же точке. И моя проблема там. Как мне сообщить компилятору об угрозе x
как double
? Моя лучшая ставка fldl 8(%ebp) fstpl 8(%ebp)
дает мне плохие номера. Любой другой суффикс l
Инструкция угрожает ему как 4 байта длиной, очевидно.
И тогда, как только я получаю правильное толкование x
, как умножить, если на 2.0
(читать хорошо, а не целое число) без использования локальной переменной? Я пытался использовать shll
но, похоже, не существует сдвига для double
значение. Я не могу fldl $2
или.
3 ответа
Вы не можете сдвинуть двойную левую сторону, чтобы умножить ее на 2, но вы можете рассматривать двойное как 64-битное целое и увеличивать поле экспоненты (переполнение может быть проблемой). Непонятно, что подразумевается под локальной переменной. В сборке X86 вы можете использовать немедленное добавление в память для увеличения поля экспоненты, но функция возвращает значение, которое можно рассматривать как локальную переменную.
Двойное число с плавающей запятой имеет несколько разных данных в своих битах. Так что нельзя просто сдвинуть его, чтобы удвоить. Как уже упоминалось, вы можете добавить его самостоятельно. Значение с плавающей запятой должно быть возвращено в регистр FPU ST0
(Соглашение о вызове cdecl). Взгляните на следующую программу:
# Name: double_the_double.s
# Compile: gcc -m32 double_the_double.s
# Run: ./a.out
.globl main
.section .data
dubbel: .double 50.0
result: .double 0.0
fmt: .asciz "%f\n"
.section .text
f:
fldl 4(%esp)
fadd %st(0),%st(0)
ret
main:
pushl (dubbel+4)
pushl (dubbel)
push $fmt
call printf
add $12, %esp
pushl (dubbel+4)
pushl (dubbel)
call f
add $8, %esp
fstpl (result)
pushl (result+4)
pushl (result)
push $fmt
call printf
add $12, %esp
# Exit from _main
ret
Я вижу, что у вас есть *pint, который является целым числом, и, таким образом, вы можете использовать shl $1, %reg для умножения на 2 (а не shl $2, %reg, который умножается на 4.)
Если вы хотите умножить удвоение на 2, вы должны добавить его к себе. Вероятно, лучший / самый быстрый способ справиться с этим.
Если вы действительно хотите сделать это вручную, вам нужно найти определение двойного, его знак, его показатель степени и его мантиссу. Умножение на 2 означает добавление 1 к показателю степени:
+----+-----------+-----------+
| 63 | 62 ... 52 | 51 ... 00 |
+----+-----------+-----------+
| X | x x x x x | x x x x x |
+----+-----------+-----------+
( Узнайте больше о форматах IEEE с плавающей запятой)
Из-за знака проще всего было бы что-то вроде этого:
mov (%r1), %r2 ; Load the double
roll $1, %r2 ; move the sign out of the way (bit 0)
mov $(1 << 53), %r3 ; add 1 to exponent (shifted by 1 to the left)
add %r3, %r2 ; do the add now
rolr $1, %r2 ; restore the sign in bit 63
Тем не менее, это не заботится о переполнениях... и это, вероятно, медленнее, чем добавление двойного к себе с помощью FPU.