x86 программа для выполнения экспоненты
Для продолжения, мой последний вопрос касался сложения чисел вместе с вызовом функции: Пример программы x86-64 . В этом я попытался применить уроки, указанные в предыдущем ответе (выравнивание стека, использование edxвместо того, rdxгде будут соответствовать результаты). Я попытался прокомментировать встроенный код:
# We are going to calculate 7^2 + 2^4 = 49 + 16 = 65
# The exponent function supports whole numbers (long) >= 0
.section .data
base_1: .long 7
exp_1: .long 2
base_2: .long 2
exp_2: .long 4
.section .text
.globl _start
_start:
# We will do the first function call, 7^2
mov base_1(%rip), %edi
mov exp_1(%rip), %esi
call exp
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax
# Now do the second function call
mov base_2(%rip), %edi
mov exp_2(%rip), %esi
call exp
# We have the return value in %eax so let's add this with our previous function's value
popq %rdi
add %eax, %edi
mov $60, %eax
syscall
exp:
# Initialize %eax to 1
mov $1, %eax
exp_op:
cmp $0, %esi
je exp_ret
imul %edi, %eax
dec %esi
jmp exp_op
exp_ret:
ret
У меня есть несколько конкретных вопросов по коду:
- Делает сырое
push/popобычное? Или это принято всегда делатьpush rbpmov rsp rbp...pop rbp. Почему один метод предпочтительнее другого? - Что, если бы я хотел сохранить результат первого вычисления в регистре: есть ли какие-нибудь регистры, которые гарантированно сохраняются между вызовами функций? Или поэтому
push...popметод так часто используется? - Наконец, мне
expкажется, все в порядке? Немного странно иметь три метки, составляющие одну функцию. Как это можно было улучшить?
Ответы
Я не эксперт, но какого черта, вот комментарий к обзору:
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax
Комментарий хороший, но я бы его перефразировал. Два восьмибайтовых нажатия составляют 16 байтов и не меняют выравнивания стека. Поэтому я предполагаю, что один из этих толчков является значительным, а другой - несущественным, но в вашем комментарии мне не сказано, что есть что! Так что вместо этого вы можете сказать
# one extra push to preserve 16-byte stack alignment
pushq $0
# push the result of `exp`
pushq %rax
Вы можете уменьшить размер сгенерированного кода, исключив незначительную константу $0:
# push the result of `exp`, plus one extra push to preserve 16-byte stack alignment
pushq %rax
pushq %rax
Теперь читателю даже не нужно выяснять, какой толчок является значительным, потому что оба толчка делают одно и то же!
Но почему важно сохранять 16-байтовое выравнивание вызовов? Это не требование машины . Похоже, вы пытаетесь следовать определенному ABI , например, для взаимодействия с C или C ++. В вашей внешней документации должно быть более четко указано, какой ABI вы пытаетесь придерживаться.
И потом, если будут пытаться взаимодействовать с кодом C, вы можете улучшить свой код, указав , какие из его меток подразумеваются внешними точками входа и какими из них являются только внутренними локальными метками. Похоже, вы намереваетесь expбыть вызванным из другого кода - это точка входа - но, например exp_op, не exp_retвызывается и технически вызывается, но просто действует как бездействие . Вы можете как-то пометить их как «детали местной реализации, а не для внешнего потребления».
Да, технически вы уже делаете это путем экспорта, .globl _startа не путем экспорта, .globl expно все же существует большая разница между вызываемой функцией expи локальной меткой, exp_opкоторая не отражена в вашей схеме именования. Если бы я делал это, я бы добавил .globl expи переименовал exp_op, exp_retбы что-нибудь вроде Lexp1, Lexp2или L1_looptop, L2_loopend.