programa x86 para hacer un exponente
Para continuar, mi última pregunta fue sumar números junto con una llamada de función: Ejemplo de programa x86-64 . En este, intenté aplicar las lecciones señaladas en la respuesta anterior (alineación de la pila, usando en edxlugar de rdxdonde encajarán los resultados). Intenté comentar en línea con el código:
# 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
Tengo algunas preguntas específicas sobre el código:
- ¿Está haciendo un raw
push/popcommon? O es la convención siempre que hacerpush rbpmov rsp rbp...pop rbp. ¿Por qué se prefiere un método sobre el otro? - ¿Qué pasa si quisiera almacenar el resultado del primer cálculo en un registro: hay registros que se garantice que se conservarán entre las llamadas a funciones? ¿O es por eso
push...popque se usa tanto el método? - Finalmente, ¿
expparece estar bien? Se siente un poco extraño tener tres etiquetas en lo que equivale a una función. ¿Cómo podría mejorarse eso?
Respuestas
No soy un experto, pero qué diablos, aquí hay un comentario de revisión:
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax
Este comentario es bueno, pero lo reformularía. Dos empujes de ocho bytes generan 16 bytes y no cambian la alineación de la pila. Por lo tanto, infiero que uno de estos empujones es significativo y el otro es insignificante , ¡pero su comentario no me dice cuál es cuál! Entonces podrías decir en su lugar
# one extra push to preserve 16-byte stack alignment
pushq $0
# push the result of `exp`
pushq %rax
Puede hacer que el código generado sea más pequeño eliminando la constante insignificante $0:
# push the result of `exp`, plus one extra push to preserve 16-byte stack alignment
pushq %rax
pushq %rax
Ahora el lector ni siquiera necesita averiguar qué empujón es el significativo, ¡porque ambos empujes hacen lo mismo!
Pero, ¿por qué es importante preservar la alineación de 16 bytes en las llamadas? Eso no es un requisito de la máquina . Parece que intenta seguir alguna ABI específica , como quizás para la interoperabilidad con C o C ++. Su documentación externa debería ser más clara sobre qué ABI está tratando de seguir.
Y luego, si está intentando interoperar con el código C, podría mejorar su código indicando cuáles de sus etiquetas son puntos de entrada externos y cuáles son solo etiquetas locales internas. Parece que tiene la intención expde que lo llamen desde otro código, es un punto de entrada, pero, por ejemplo, exp_opno se puede llamar y exp_rettécnicamente se puede llamar, pero solo actúa como una operación no operativa . Puede marcarlos de alguna manera como "detalles de implementación local, no para consumo externo".
Sí, técnicamente ya lo hace exportando .globl _starty no .globl exp, pero todavía hay una gran diferencia entre la función invocable expy la etiqueta local exp_opque no se refleja en su esquema de nombres. Si estuviera haciendo esto, agregaría .globl expy cambiaría el nombre exp_op, exp_reta algo como Lexp1, Lexp2o L1_looptop, L2_loopend.