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 eaxcon 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 eaxcon -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/ popdefault 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 eaxpush imm8/pop reges de 3 bytes, y es fantástico para constantes de 64 bits en x86-64, dondedec/inces de 2 bytes. Ypush r64/pop 64(2 bytes) puede incluso reemplazar un byte demov r64, r643 (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)AXcomo operando de destino) son 1 byte más cortas que las instrucciones de caso general; vea esta pregunta en StackOverflow.fuente
al, imm8casos especiales, comoor al, 0x20/sub al, 'a'/cmp al, 'z'-'a'/ queja .non_alphabeticson 2 bytes cada uno, en lugar de 3. El usoalde datos de caracteres también permitelodsby / ostosb. O usealpara probar algo sobre el byte bajo de EAX, comolodsd/test al, 1/setnz clhace 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/raxsi 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
st0es razonable, pero regresarst3con basura en otro registro x87 no lo es. La persona que llama tendría que limpiar la pila x87. Incluso regresarst0con 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/retx86 usando registro de enlace comolea rbx, [ret_addr]/jmp functiony 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
xmm0paramovlps [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=0significa 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
jnelo 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
movzxomovsxpara 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_toint64_ten su prototipo si lo desea. por ejemplo, puede usar unaloopinstrucció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
longsolo es un tipo de 32 bits en Windows ABI de 64 bits y Linux x32 ABI ;uint64_tes 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 enteroecxyedx.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
memcpyo memset con la mismarep movsbfacilidad: los primeros 6 argumentos enteros / puntero se pasan en RDI, RSI, RDX, RCX, R8, R9.Si su función usa
lodsd/stosddentro de un ciclo que ejecutarcxtiempos (con laloopinstrucció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 raxes 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/decun registro (que no sea de 8 bits):inc eax/dec ebp. (No x86-64: los0x4xbytes del código de operación se reutilizaron como prefijos REX, por lo queinc r/m32es la única codificación).8 bits
inc bles de 2 bytes, utilizando elinc r/m8código de operación + Modr / M operando codifica . Así que usainc ebxpara 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,scasbpuede 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 dosxchginstrucciones 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 /idivinc ecxlooptest ecx,ecxjnzcdq: 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:cltqvs.movslq, y también AT&T vs. Intel mnemonics para esto y lo relacionadocdqe.lodsb / d : como
mov eax, [rsi]/rsi += 4sin 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 rdies de 2 bytes, peromov rdi, rspnecesita un prefijo REX y es de 3 bytes.xlatbexiste, 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/sahfrara vez son útiles. Usted pudelahf/and ah, 1como una alternativa asetc ah, pero no es generalmente útil.Y para CF específicamente, hay
sbb eax,eaxque obtener un 0 / -1, o incluso 1-byte no documentado pero universalmente compatiblesalc(establecer AL desde Carry) que efectivamente lo hacesbb al,alsin 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 lacmcadició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,eaxborra 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, 0pueden agregarlo a AL en 2 bytes, y mencioné anteriormente el SALC indocumentado.std/cldParecer rara vez vale la pena . Especialmente en el código de 32 bits, es mejor usarlodecen un puntero y unmovoperando fuente de memoria para una instrucción ALU en lugar de configurar DF asílodsb/stosbir 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 unostdyclden toda la función para usarlods/stospara 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/divy 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/movzxpara cargar o mover + signo-extender, o 2 y 3 operandosimul cx, bx, 1234que 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,imm8oAX,imm16o (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,4es 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/wtodas 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
cdqEs útil para lodivque necesita ceroedxen muchos casos.cdqantes de no firmardivsi 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 establecereaxun valor potencialmente grande. Normalmente (fuera del código de golf) usaríacdqcomo configuración paraidiv, yxor edx,edxantesdivUsar
fastcallconvencionesla 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
fastcallen plataformas de 32 bits, generalmente se pasan 2 primeros parámetrosecxyedx. Si su función tiene 3 parámetros, puede considerar implementarla en una plataforma de 64 bits.Prototipos de función C para
fastcallconvenció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
< 128en<= 127reducir 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/decpara 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
LOOPfinalice 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
incodecinstrucciones que son más pequeñas que las instrucciones de sumar y sub multibyte.fuente
inc/dec r32con el número de registro codificado en el código de operación. Entoncesinc ebxes 1 byte, peroinc bles 2. Todavía más pequeño queadd bl, 1, por supuesto, para registros distintos deal. También tenga en cuenta queinc/decdeje CF sin modificar, pero actualice las otras banderas.leapara las matemáticasEsta es probablemente una de las primeras cosas que uno aprende sobre x86, pero lo dejo aquí como recordatorio.
lease puede usar para multiplicar por 2, 3, 4, 5, 8 o 9 y agregar un desplazamiento.Por ejemplo, para calcular
ebx = 9*eax + 3en una instrucción (en modo de 32 bits):Aquí está sin compensación:
¡Guauu! Por supuesto, también
lease puede utilizar para hacer cálculos matemáticosebx = edx + 8*eax + 3para 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 ECXyjnz <label>, ylodsbes más pequeño quemov al,[esi]yinc si.fuente
movlos 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/poppara imm8 a cero bits superioresCrédito a Peter Cordes.
xor/moves 4 bytes, peropush/popes solo 3!fuente
mov al, 0xaes 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 oleadesde otra constante conocida. Esto podría ser útil en combinación conmulcero 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, 0x10después de unaloopinstrucción, porque la única forma deloopno 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-1024en ebx y usa bl.xchg eax, r32), por ejemplo,mov bl, 10/dec bl/jnzpara 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,1para 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/jrcxzantes de un bucle funciona muy bienlooppara manejar las "necesidades de ejecutar cero veces" caso "de manera eficiente" (en las CPU raras dondeloopno es lento).jecxztambién se puede usar dentro del bucle para implementar awhile(ecx){}, conjmpen 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, yraxcomo valor de retorno, pero es perfectamente razonable utilizar su propia convención de llamada. __fastcall usaecxyedxcomo 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 0x80que requiere un montón de configuración.int 0x80en código de 32 bits, osyscallen 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, 4en código de 32 bits. También podríacall printfoputs, 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*bufy 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
CMOVccy conjuntos condicionalesSETccEsto 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
cmovtiene 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,setcces ú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, 32Es 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
jmpbytes 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
sublatencia 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 demovzxtodos modos va a necesitar un al final, entoncessub $imm, %alno use EAX para aprovechar la codificación de 2 bytes sin modrmop $imm, %al.cmphaciendosub $'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, puedepushrecuperarlos 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 dondepopobtendría 8 bytes? Por cierto, incluso puede usarpoppara 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 rdxen 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, úselosmovporque 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,r64omov r64, -128..127Ejemplos:
La
xchgparte 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