mov
-inmediato es caro para las constantes
Esto puede ser obvio, pero aún lo pondré aquí. En general, vale la pena pensar en la representación a nivel de bit de un número cuando necesita inicializar un valor.
Inicializando eax
con 0
:
b8 00 00 00 00 mov $0x0,%eax
debe acortarse ( para el rendimiento y el tamaño del código ) a
31 c0 xor %eax,%eax
Inicializando eax
con -1
:
b8 ff ff ff ff mov $-1,%eax
se puede acortar a
31 c0 xor %eax,%eax
48 dec %eax
o
83 c8 ff or $-1,%eax
O, más generalmente, cualquier valor de signo extendido de 8 bits se puede crear en 3 bytes con push -12
(2 bytes) / pop %eax
(1 byte). Esto incluso funciona para registros de 64 bits sin prefijo REX adicional; push
/ pop
default operand-size = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
O dada una constante conocida en un registro, puede crear otra constante cercana usando lea 123(%eax), %ecx
(3 bytes). Esto es útil si necesita un registro a cero y una constante; xor-zero (2 bytes) + lea-disp8
(3 bytes).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Consulte también Establecer todos los bits en el registro de CPU en 1 de manera eficiente
push 200; pop edx
- 3 bytes para la inicialización.dec
, por ejemploxor eax, eax; dec eax
push imm8
/pop reg
es de 3 bytes, y es fantástico para constantes de 64 bits en x86-64, dondedec
/inc
es de 2 bytes. Ypush r64
/pop 64
(2 bytes) puede incluso reemplazar un byte demov r64, r64
3 (3 bytes con REX). Consulte también Establecer todos los bits en el registro de la CPU en 1 de manera eficiente para cosas comolea eax, [rcx-1]
un valor conocido dadoeax
(por ejemplo, si necesita un registro a cero y otra constante, solo use LEA en lugar de push / popEn muchos casos, las instrucciones basadas en acumuladores (es decir, las que toman
(R|E)AX
como operando de destino) son 1 byte más cortas que las instrucciones de caso general; vea esta pregunta en StackOverflow.fuente
al, imm8
casos especiales, comoor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ queja .non_alphabetic
son 2 bytes cada uno, en lugar de 3. El usoal
de datos de caracteres también permitelodsb
y / ostosb
. O useal
para probar algo sobre el byte bajo de EAX, comolodsd
/test al, 1
/setnz cl
hace que cl = 1 o 0 para impar / par. Pero en el raro caso de que necesite una respuesta inmediata de 32 bits, entonces seguroop eax, imm32
, como en mi respuesta de clave de cromaElija su convención de llamadas para colocar los argumentos donde desee.
El lenguaje de su respuesta es asm (en realidad código de máquina), así que trátelo como parte de un programa escrito en asm, no C-compilado-para-x86. Su función no tiene que ser fácilmente invocable desde C con ninguna convención de llamada estándar. Sin embargo, es una buena ventaja si no le cuesta bytes adicionales.
En un programa asm puro, es normal que algunas funciones auxiliares utilicen una convención de llamadas que sea conveniente para ellos y para su interlocutor. Dichas funciones documentan su convención de llamada (entradas / salidas / clobbers) con comentarios.
En la vida real, incluso los programas asm (creo) tienden a usar convenciones de llamadas consistentes para la mayoría de las funciones (especialmente en diferentes archivos fuente), pero cualquier función importante podría hacer algo especial. En code-golf, está optimizando la basura de una sola función, por lo que obviamente es importante / especial.
Para probar su función desde un programa en C, puede escribir una envoltura que coloque los argumentos en los lugares correctos, guarde / restaure cualquier registro adicional que haya marcado y coloque el valor de retorno
e/rax
si aún no estaba allí.Los límites de lo que es razonable: cualquier cosa que no imponga una carga irrazonable a la persona que llama:
Requerir que DF (indicador de dirección de cadena para
lods
/stos
/ etc.) esté despejado (hacia arriba) en la llamada / ret es normal. Dejarlo sin definir en call / ret estaría bien. Requerir que se borre o establecer en la entrada, pero luego dejarlo modificado cuando regrese sería extraño.Devolver los valores de FP en x87
st0
es razonable, pero regresarst3
con basura en otro registro x87 no lo es. La persona que llama tendría que limpiar la pila x87. Incluso regresarst0
con registros de pila más altos no vacíos también sería cuestionable (a menos que esté devolviendo valores múltiples).call
, al igual[rsp]
que su dirección de devolución. Usted puede evitarcall
/ret
x86 usando registro de enlace comolea rbx, [ret_addr]
/jmp function
y de retorno conjmp rbx
, pero eso no es "razonable". Eso no es tan eficiente como call / ret, por lo que no es algo que posiblemente encuentre en el código real.Casos límite: escriba una función que produzca una secuencia en una matriz, dados los primeros 2 elementos como argumentos de función . Elegí que la persona que llama almacenara el inicio de la secuencia en la matriz y simplemente pasara un puntero a la matriz. Esto definitivamente está doblando los requisitos de la pregunta. Consideré tomar los argumentos empaquetados en
xmm0
paramovlps [rdi], xmm0
, que también sería una convención de llamada extraña.Devuelve un booleano en BANDERAS (códigos de condición)
Las llamadas al sistema OS X hacen esto (
CF=0
significa que no hay error): ¿se considera una mala práctica usar el registro de banderas como un valor de retorno booleano? .Cualquier condición que pueda verificarse con un JCC es perfectamente razonable, especialmente si puede elegir una que tenga alguna relevancia semántica para el problema. (por ejemplo, una función de comparación podría establecer marcas, por
jne
lo que se tomarán si no fueran iguales).Exigir args estrechos (como a
char
) para ser signo o cero extendido a 32 o 64 bits.Esto no es irrazonable; usar
movzx
omovsx
para evitar ralentizaciones de registro parcial es normal en el x86 asm moderno. De hecho, clang / LLVM ya crea un código que depende de una extensión no documentada de la convención de llamadas del Sistema V x86-64: los argumentos más estrechos que 32 bits son signos o cero extendido a 32 bits por el llamante .Puede documentar / describir la extensión a 64 bits escribiendo
uint64_t
oint64_t
en su prototipo si lo desea. por ejemplo, puede usar unaloop
instrucción, que usa los 64 bits completos de RCX a menos que use un prefijo de tamaño de dirección para anular el tamaño hasta ECX de 32 bits (sí, realmente, el tamaño de la dirección no es el tamaño del operando).Tenga en cuenta que
long
solo es un tipo de 32 bits en Windows ABI de 64 bits y Linux x32 ABI ;uint64_t
es inequívoco y más corto de escribir queunsigned long long
.Convenciones de llamadas existentes:
Windows de 32 bits
__fastcall
, ya sugerido por otra respuesta : arger enteroecx
yedx
.x86-64 System V : pasa muchos argumentos en los registros y tiene muchos registros de llamadas que puede usar sin prefijos REX. Más importante aún, en realidad se eligió para permitir que los compiladores en línea
memcpy
o memset con la mismarep movsb
facilidad: los primeros 6 argumentos enteros / puntero se pasan en RDI, RSI, RDX, RCX, R8, R9.Si su función usa
lodsd
/stosd
dentro de un ciclo que ejecutarcx
tiempos (con laloop
instrucción), puede decir "invocable desde C comoint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
con la convención de llamadas del sistema V x86-64". ejemplo: chromakey .GCC de 32 bits
regparm
: argumentos enteros en EAX , ECX, EDX, retorno en EAX (o EDX: EAX). Tener el primer argumento en el mismo registro que el valor de retorno permite algunas optimizaciones, como este caso con un llamador de ejemplo y un prototipo con un atributo de función . Y, por supuesto, AL / EAX es especial para algunas instrucciones.El Linux x32 ABI utiliza punteros de 32 bits en modo largo, por lo que puede guardar un prefijo REX al modificar un puntero (por ejemplo, caso de uso ). Todavía puede usar un tamaño de dirección de 64 bits, a menos que tenga un entero negativo de 32 bits con cero extendido en un registro (por lo que sería un gran valor sin signo si lo hiciera
[rdi + rdx]
).Tenga en cuenta que
push rsp
/pop rax
es de 2 bytes, y equivalente amov rax,rsp
, por lo que aún puede copiar registros completos de 64 bits en 2 bytes.fuente
ret 16
; no muestran la dirección de retorno, empujan una matriz, luegopush rcx
/ret
. La persona que llama tendría que conocer el tamaño de la matriz o haber guardado RSP en algún lugar fuera de la pila para encontrarse.Utilice codificaciones de forma corta de casos especiales para AL / AX / EAX y otras formas cortas e instrucciones de un solo byte
Los ejemplos suponen el modo de 32/64 bits, donde el tamaño de operando predeterminado es de 32 bits. Un prefijo de tamaño de operando cambia la instrucción a AX en lugar de EAX (o al revés en modo de 16 bits).
inc/dec
un registro (que no sea de 8 bits):inc eax
/dec ebp
. (No x86-64: los0x4x
bytes del código de operación se reutilizaron como prefijos REX, por lo queinc r/m32
es la única codificación).8 bits
inc bl
es de 2 bytes, utilizando elinc r/m8
código de operación + Modr / M operando codifica . Así que usainc ebx
para incrementarbl
, si es seguro. (por ejemplo, si no necesita el resultado ZF en los casos en que los bytes superiores pueden ser distintos de cero).scasd
:e/rdi+=4
, requiere que el registro apunte a memoria legible. A veces es útil incluso si no te importa el resultado de FLAGS (comocmp eax,[rdi]
/rdi+=4
). Y en el modo de 64 bits,scasb
puede funcionar como un byteinc rdi
, si lodsb o stosb no son útiles.xchg eax, r32
: Aquí es donde 0x90 NOP vino de:xchg eax,eax
. Ejemplo: reorganice 3 registros con dosxchg
instrucciones en un buclecdq
/ para GCD en 8 bytes, donde la mayoría de las instrucciones son de un solo byte, incluido un abuso de / en lugar de /idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: firma-extiende EAX en EDX: EAX, es decir, copia el bit alto de EAX a todos los bits de EDX. Para crear un cero con no negativo conocido, o para obtener un 0 / -1 para agregar / sub o enmascarar. Lección de historia x86:cltq
vs.movslq
, y también AT&T vs. Intel mnemonics para esto y lo relacionadocdqe
.lodsb / d : como
mov eax, [rsi]
/rsi += 4
sin banderas de golpeteo. (Suponiendo que DF es claro, qué convenciones de llamada estándar requieren en la entrada de funciones). También stosb / d, a veces scas, y más raramente movs / cmps.push
/pop reg
. por ejemplo, en modo de 64 bits,push rsp
/pop rdi
es de 2 bytes, peromov rdi, rsp
necesita un prefijo REX y es de 3 bytes.xlatb
existe, pero rara vez es útil. Una tabla de búsqueda grande es algo que debe evitarse. Tampoco he encontrado un uso para AAA / DAA u otras instrucciones BCD empaquetadas o de 2 dígitos ASCII.1 byte
lahf
/sahf
rara vez son útiles. Usted pudelahf
/and ah, 1
como una alternativa asetc ah
, pero no es generalmente útil.Y para CF específicamente, hay
sbb eax,eax
que obtener un 0 / -1, o incluso 1-byte no documentado pero universalmente compatiblesalc
(establecer AL desde Carry) que efectivamente lo hacesbb al,al
sin afectar a las banderas. (Eliminado en x86-64). Usé SALC en el Desafío de apreciación del usuario # 1: Dennis ♦ .1 byte
cmc
/clc
/stc
(flip ("complemento"), clear o set CF) rara vez son útiles, aunque encontré un uso para lacmc
adición de precisión extendida con trozos de base 10 ^ 9. Para configurar / borrar incondicionalmente la CF, generalmente haga los arreglos para que eso suceda como parte de otra instrucción, por ejemplo,xor eax,eax
borra CF y EAX. No hay instrucciones equivalentes para otros indicadores de condición, solo DF (dirección de la cadena) e IF (interrupciones). La bandera de transporte es especial para muchas instrucciones; los cambios lo establecen,adc al, 0
pueden agregarlo a AL en 2 bytes, y mencioné anteriormente el SALC indocumentado.std
/cld
Parecer rara vez vale la pena . Especialmente en el código de 32 bits, es mejor usarlodec
en un puntero y unmov
operando fuente de memoria para una instrucción ALU en lugar de configurar DF asílodsb
/stosb
ir hacia abajo en lugar de hacia arriba. Por lo general, si necesita algo hacia abajo, todavía tiene otro puntero hacia arriba, por lo que necesitaría más de unostd
ycld
en toda la función para usarlods
/stos
para ambos. En cambio, solo use las instrucciones de la cuerda para la dirección hacia arriba. (Las convenciones de llamada estándar garantizan DF = 0 en la entrada de función, por lo que puede suponer que es gratis sin usarcld
).Historia de 8086: por qué existen estas codificaciones
En el original 8086, AX fue muy especial: instrucciones como
lodsb
/stosb
,cbw
,mul
/div
y otros lo utilizan de forma implícita. Ese sigue siendo el caso, por supuesto; x86 actual no ha eliminado ninguno de los códigos de operación de 8086 (al menos ninguno de los documentados oficialmente). Pero las CPU posteriores agregaron nuevas instrucciones que dieron formas mejores / más eficientes de hacer las cosas sin copiarlas o cambiarlas primero a AX. (O a EAX en modo de 32 bits).por ejemplo, 8086 careció de adiciones posteriores como
movsx
/movzx
para cargar o mover + signo-extender, o 2 y 3 operandosimul cx, bx, 1234
que no producen un resultado de mitad alta y no tienen ningún operando implícito.Además, el principal cuello de botella de 8086 era la búsqueda de instrucciones, por lo que la optimización del tamaño del código era importante para el rendimiento en ese momento . El diseñador ISA de 8086 (Stephen Morse) gastó mucho espacio de codificación de código de operación en casos especiales para AX / AL, incluyendo códigos de operación especiales (E) AX / AL-destino para todas las instrucciones básicas de ALU de src inmediato inmediato , solo código de operación + inmediato sin byte ModR / M. 2 bytes
add/sub/and/or/xor/cmp/test/... AL,imm8
oAX,imm16
o (en modo de 32 bits)EAX,imm32
.Pero no hay un caso especial
EAX,imm8
, por lo que la codificación ModR / M normal deadd eax,4
es más corta.La suposición es que si va a trabajar en algunos datos, lo querrá en AX / AL, por lo que intercambiar un registro con AX es algo que quizás desee hacer, tal vez incluso con más frecuencia que copiar un registro en AX con
mov
.Todo lo relacionado con la codificación de instrucciones 8086 admite este paradigma, desde instrucciones como
lodsb/w
todas las codificaciones de casos especiales para inmediatos con EAX hasta su uso implícito incluso para multiplicar / dividir.No te dejes llevar; No es automáticamente una victoria cambiar todo a EAX, especialmente si necesita usar inmediatos con registros de 32 bits en lugar de 8 bits. O si necesita intercalar operaciones en múltiples variables en registros a la vez. O si está utilizando instrucciones con 2 registros, no inmediatamente.
Pero siempre tenga en cuenta: ¿estoy haciendo algo que sería más corto en EAX / AL? ¿Puedo reorganizar para que tenga esto en AL, o estoy aprovechando mejor AL con lo que ya estoy usando?
Mezcle operaciones de 8 bits y 32 bits libremente para aprovechar cada vez que sea seguro hacerlo (no es necesario llevarlo a cabo en el registro completo o lo que sea).
fuente
cdq
Es útil para lodiv
que necesita ceroedx
en muchos casos.cdq
antes de no firmardiv
si sabe que su dividendo está por debajo de 2 ^ 31 (es decir, no negativo cuando se trata como firmado), o si lo usa antes de establecereax
un valor potencialmente grande. Normalmente (fuera del código de golf) usaríacdq
como configuración paraidiv
, yxor edx,edx
antesdiv
Usar
fastcall
convencionesla plataforma x86 tiene muchas convenciones de llamadas . Debe usar aquellos que pasan parámetros en registros. En x86_64, los primeros parámetros se pasan de todos modos en los registros, por lo que no hay problema. En las plataformas de 32 bits, la convención de llamada predeterminada (
cdecl
) pasa los parámetros en la pila, lo que no es bueno para el golf: el acceso a los parámetros en la pila requiere instrucciones largas.Cuando usas
fastcall
en plataformas de 32 bits, generalmente se pasan 2 primeros parámetrosecx
yedx
. Si su función tiene 3 parámetros, puede considerar implementarla en una plataforma de 64 bits.Prototipos de función C para
fastcall
convención (tomado de esta respuesta de ejemplo ):fuente
Resta -128 en lugar de sumar 128
Del mismo modo, agregue -128 en lugar de restar 128
fuente
< 128
en<= 127
reducir la magnitud de un operando inmediato paracmp
, o gcc siempre prefiere la reordenación se compara para reducir la magnitud incluso si no es -129 frente a -128.Cree 3 ceros con
mul
(luegoinc
/dec
para obtener +1 / -1 y cero)Puede cero eax y edx multiplicando por cero en un tercer registro.
dará como resultado que EAX, EDX y EBX sean cero en solo cuatro bytes. Puede poner a cero EAX y EDX en tres bytes:
Pero desde ese punto de partida no puede obtener un tercer registro a cero en un byte más, o un registro +1 o -1 en otros 2 bytes. En su lugar, use la técnica mul.
Ejemplo de caso de uso: concatenación de los números de Fibonacci en binario .
Tenga en cuenta que después de que
LOOP
finalice un bucle, ECX será cero y puede usarse para cero EDX y EAX; no siempre tiene que crear el primer cero conxor
.fuente
Los registros y las banderas de la CPU están en estados de inicio conocidos
Podemos suponer que la CPU está en un estado predeterminado conocido y documentado basado en la plataforma y el sistema operativo.
Por ejemplo:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
fuente
_start
. Entonces sí, es un juego justo aprovechar eso si estás escribiendo un programa en lugar de una función. Lo hice en Extreme Fibonacci . (En un ejecutable enlazado dinámicamente, ld.so carreras antes de saltar a tu_start
, y lo hace de basura licencia en los registros, pero estática es sólo el código.)Para sumar o restar 1, use un byte
inc
odec
instrucciones que son más pequeñas que las instrucciones de sumar y sub multibyte.fuente
inc/dec r32
con el número de registro codificado en el código de operación. Entoncesinc ebx
es 1 byte, peroinc bl
es 2. Todavía más pequeño queadd bl, 1
, por supuesto, para registros distintos deal
. También tenga en cuenta queinc
/dec
deje CF sin modificar, pero actualice las otras banderas.lea
para las matemáticasEsta es probablemente una de las primeras cosas que uno aprende sobre x86, pero lo dejo aquí como recordatorio.
lea
se puede usar para multiplicar por 2, 3, 4, 5, 8 o 9 y agregar un desplazamiento.Por ejemplo, para calcular
ebx = 9*eax + 3
en una instrucción (en modo de 32 bits):Aquí está sin compensación:
¡Guauu! Por supuesto, también
lea
se puede utilizar para hacer cálculos matemáticosebx = edx + 8*eax + 3
para calcular la indexación de matrices.fuente
lea eax, [rcx + 13]
es la versión sin prefijos adicionales para el modo de 64 bits. Tamaño de operando de 32 bits (para el resultado) y tamaño de dirección de 64 bits (para las entradas).Las instrucciones de bucle y cadena son más pequeñas que las secuencias de instrucciones alternativas. Lo más útil es
loop <label>
cuál es más pequeño que la secuencia de dos instruccionesdec ECX
yjnz <label>
, ylodsb
es más pequeño quemov al,[esi]
yinc si
.fuente
mov
los pequeños aparecen inmediatamente en los registros inferiores cuando correspondeSi ya sabe que los bits superiores de un registro son 0, puede usar una instrucción más corta para mover un inmediato a los registros inferiores.
versus
Use
push
/pop
para imm8 a cero bits superioresCrédito a Peter Cordes.
xor
/mov
es 4 bytes, peropush
/pop
es solo 3!fuente
mov al, 0xa
es bueno si no lo necesita cero extendido al registro completo. Pero si lo hace, xor / mov es 4 bytes vs. 3 para push imm8 / pop olea
desde otra constante conocida. Esto podría ser útil en combinación conmul
cero 3 registros en 4 bytes , ocdq
, si necesita muchas constantes, sin embargo.[0x80..0xFF]
, que no son representables como un imm8 con signo extendido. O si ya conoce los bytes superiores, por ejemplo,mov cl, 0x10
después de unaloop
instrucción, porque la única forma deloop
no saltar es cuando se hizorcx=0
. (Supongo que dijiste esto, pero tu ejemplo usa unxor
). Incluso puede usar el byte bajo de un registro para otra cosa, siempre que la otra cosa lo vuelva a poner a cero (o lo que sea) cuando haya terminado. por ejemplo, mi programa Fibonacci se mantiene-1024
en ebx y usa bl.xchg eax, r32
), por ejemplo,mov bl, 10
/dec bl
/jnz
para que su código no se preocupe por los altos bytes de RBX.Las banderas se configuran después de muchas instrucciones.
Después de muchas instrucciones aritméticas, el indicador de transporte (sin firmar) y el indicador de desbordamiento (firmado) se configuran automáticamente ( más información ). El indicador de signo y el indicador de cero se establecen después de muchas operaciones aritméticas y lógicas. Esto se puede usar para la ramificación condicional.
Ejemplo:
ZF se establece mediante esta instrucción, por lo que podemos usarlo para la ramificación condicional.
fuente
test al,1
; generalmente no obtienes eso gratis. (Oand al,1
para crear un número entero 0/1 dependiendo de impar / par.)test
/cmp
", entonces eso sería bastante básico para principiantes x86, pero aún así merece un voto positivo.Use bucles do-while en lugar de bucles while
Esto no es específico para x86, pero es una sugerencia de ensamblaje para principiantes ampliamente aplicable. Si sabe que un ciclo while se ejecutará al menos una vez, reescribiendo el ciclo como un ciclo do-while, con la comprobación de la condición del ciclo al final, a menudo guarda una instrucción de salto de 2 bytes. En un caso especial, incluso podría usarlo
loop
.fuente
do{}while()
es el idioma natural en bucle en el ensamblaje (especialmente para la eficiencia). Tenga en cuenta también que 2 bytesjecxz
/jrcxz
antes de un bucle funciona muy bienloop
para manejar las "necesidades de ejecutar cero veces" caso "de manera eficiente" (en las CPU raras dondeloop
no es lento).jecxz
también se puede usar dentro del bucle para implementar awhile(ecx){}
, conjmp
en la parte inferior.Use las convenciones de llamadas convenientes
Sistema V x 86 utiliza el sistema de pila y V x86-64 usos
rdi
,rsi
,rdx
,rcx
, etc., para los parámetros de entrada, yrax
como valor de retorno, pero es perfectamente razonable utilizar su propia convención de llamada. __fastcall usaecx
yedx
como parámetros de entrada, y otros compiladores / sistemas operativos usan sus propias convenciones . Use la pila y lo que sea que se registre como entrada / salida cuando sea conveniente.Ejemplo: el contador de bytes repetitivo , utilizando una convención de llamada inteligente para una solución de 1 byte.
Meta: escritura de entrada en registros , escritura de salida en registros
Otros recursos: notas de Agner Fog sobre convenciones de llamadas
fuente
int 0x80
que requiere un montón de configuración.int 0x80
en código de 32 bits, osyscall
en código de 64 bits, invocarsys_write
, es la única buena manera. Es para lo que solía Extreme Fibonacci . En código de 64 bits__NR_write = 1 = STDOUT_FILENO
, para que puedasmov eax, edi
. O si los bytes superiores de EAX son cero,mov al, 4
en código de 32 bits. También podríacall printf
oputs
, supongo, y escribir una respuesta "x86 asm for Linux + glibc". Creo que es razonable no contar el espacio de entrada PLT o GOT, o el código de la biblioteca en sí.char*buf
y produjera la cadena en eso, con formato manual. p. ej. de esta manera (torpemente optimizado para la velocidad) asm FizzBuzz , donde puse los datos de la cadena en el registro y luego los almacenémov
, porque las cadenas eran cortas y de longitud fija.Usa movimientos
CMOVcc
y conjuntos condicionalesSETcc
Esto es más un recordatorio para mí, pero existen instrucciones de conjuntos condicionales y existen instrucciones de movimiento condicionales en los procesadores P6 (Pentium Pro) o posteriores. Hay muchas instrucciones que se basan en uno o más de los indicadores establecidos en EFLAGS.
fuente
cmov
tiene un código de operación de 2 bytes (0F 4x +ModR/M
), por lo que tiene un mínimo de 3 bytes. Pero la fuente es r / m32, por lo que puede cargar condicionalmente en 3 bytes. Aparte de la ramificación,setcc
es útil en más casos quecmovcc
. Aún así, considere todo el conjunto de instrucciones, no solo las instrucciones de referencia 386. (Aunque las instrucciones SSE2 y BMI / BMI2 son tan grandes que rara vez son útiles.rorx eax, ecx, 32
Es de 6 bytes, más largo que mov + ror. Agradable para el rendimiento, no para el golf a menos que POPCNT o PDEP salven muchos isns)setcc
.Ahorrar en
jmp
bytes organizando en if / then en lugar de if / then / elseEsto es ciertamente muy básico, solo pensé en publicar esto como algo en lo que pensar al jugar golf. Como ejemplo, considere el siguiente código directo para decodificar un carácter de dígito hexadecimal:
Esto puede acortarse en dos bytes dejando que un caso "entonces" caiga en un caso "else":
fuente
sub
latencia adicional en la ruta crítica para un caso no forma parte de una cadena de dependencia transportada por bucle (como aquí, donde cada dígito de entrada es independiente hasta que se fusionan fragmentos de 4 bits ) Pero supongo que +1 de todos modos. Por cierto, su ejemplo tiene una optimización perdida por separado: si demovzx
todos modos va a necesitar un al final, entoncessub $imm, %al
no use EAX para aprovechar la codificación de 2 bytes sin modrmop $imm, %al
.cmp
haciendosub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
. (Creo que tengo la lógica correcta). Tenga en cuenta que'A'-10 > '9'
no hay ambigüedad. Restar la corrección de una letra envolverá un dígito decimal. Así que esto es seguro si asumimos que nuestra entrada es hexadecimal válida, al igual que la suya.Puede obtener objetos secuenciales de la pila configurando esi en esp, y realizando una secuencia de lodsd / xchg reg, eax.
fuente
pop eax
/pop edx
/ ...? Si necesita dejarlos en la pila, puedepush
recuperarlos todos después para restaurar ESP, aún 2 bytes por objeto sin necesidadmov esi,esp
. ¿O quiso decir para objetos de 4 bytes en código de 64 bits dondepop
obtendría 8 bytes? Por cierto, incluso puede usarpop
para recorrer un búfer con un mejor rendimiento quelodsd
, por ejemplo, para la adición de precisión extendida en Extreme FibonacciPara codegolf y ASM: use las instrucciones, use solo registros, presione pop, minimice la memoria de registro o la memoria inmediata
fuente
Para copiar un registro de 64 bits, use
push rcx
;pop rdx
en lugar de un 3 bytemov
.El tamaño de operando predeterminado de push / pop es de 64 bits sin necesidad de un prefijo REX.
(Un prefijo de tamaño de operando puede anular el tamaño push / pop a 16 bits, pero el tamaño de operando push / pop de 32 bits no se puede codificar en modo de 64 bits, incluso con REX.W = 0).
Si uno o ambos registros son
r8
...r15
, úselosmov
porque push y / o pop necesitarán un prefijo REX. En el peor de los casos, esto realmente pierde si ambos necesitan prefijos REX. Obviamente, normalmente debe evitar r8..r15 de todos modos en el código de golf.Puede mantener su fuente más legible mientras se desarrolla con esto macro NASM . Solo recuerda que pisa los 8 bytes debajo de RSP. (En la zona roja en x86-64 System V). Pero en condiciones normales es un reemplazo directo para 64 bits
mov r64,r64
omov r64, -128..127
Ejemplos:
La
xchg
parte del ejemplo es porque a veces necesita obtener un valor en EAX o RAX y no le importa preservar la copia anterior. Sin embargo, push / pop no te ayuda a intercambiar.fuente