Respostas:
mov
-imediato é caro para constantesIsso pode ser óbvio, mas ainda vou colocá-lo aqui. Em geral, vale a pena pensar na representação no nível de bit de um número quando você precisa inicializar um valor.
eax
com 0
:b8 00 00 00 00 mov $0x0,%eax
deve ser reduzido ( para desempenho e tamanho do código ) para
31 c0 xor %eax,%eax
eax
com -1
:b8 ff ff ff ff mov $-1,%eax
pode ser reduzido para
31 c0 xor %eax,%eax
48 dec %eax
ou
83 c8 ff or $-1,%eax
Ou, geralmente, qualquer valor estendido de sinal de 8 bits pode ser criado em 3 bytes com push -12
(2 bytes) / pop %eax
(1 byte). Isso funciona mesmo para registros de 64 bits sem prefixo REX extra; push
/ pop
tamanho padrão do operando = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Ou, dada uma constante conhecida em um registro, você pode criar outra constante próxima usando lea 123(%eax), %ecx
(3 bytes). Isso é útil se você precisar de um registro zerado e uma constante; xor-zero (2 bytes) + lea-disp8
(3 bytes).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Consulte também Definir todos os bits no registro da CPU como 1 de forma eficiente
dec
, por exemploxor eax, eax; dec eax
push imm8
/ pop reg
é de 3 bytes e é fantástico para constantes de 64 bits em x86-64, onde dec
/ inc
é de 2 bytes. E push r64
/ pop 64
(2 bytes) pode até substituir 3 bytes mov r64, r64
(3 bytes por REX). Ver também Definir todos os bits no registrador CPU de 1 de forma eficiente para coisas como lea eax, [rcx-1]
dado um valor conhecido no eax
(por exemplo, se for necessário um registo zerada e outra constante, basta usar LEA em vez de push / pop
Em muitos casos, as instruções baseadas no acumulador (ou seja, aquelas que tomam (R|E)AX
como operando de destino) são 1 byte mais curto que as instruções de caso geral; veja esta pergunta no StackOverflow.
al, imm8
casos especiais, como or al, 0x20
/ sub al, 'a'
/ cmp al, 'z'-'a'
/ ja .non_alphabetic
sendo 2 bytes cada, em vez de 3. O uso al
de dados de caracteres também permite lodsb
e / ou stosb
. Ou use al
para testar algo sobre o byte baixo do EAX, como lodsd
/ test al, 1
/ setnz cl
make cl = 1 ou 0 para ímpar / par. Mas, no caso raro em que você precisa de um 32-bit imediato, então tudo bem op eax, imm32
, como em minha resposta chroma-key
O idioma da sua resposta é asm (na verdade, código de máquina), portanto, trate-o como parte de um programa escrito em asm, não em C-compilado para x86. Sua função não precisa ser fácil de chamar de C com qualquer convenção de chamada padrão. Esse é um bônus interessante, se não lhe custar bytes extras.
Em um programa asm puro, é normal que algumas funções auxiliares usem uma convenção de chamada que seja conveniente para eles e para o responsável pela chamada. Tais funções documentam sua convenção de chamada (entradas / saídas / clobbers) com comentários.
Na vida real, mesmo os programas asm (acho) tendem a usar convenções de chamada consistentes para a maioria das funções (especialmente em arquivos de origem diferentes), mas qualquer função importante pode fazer algo especial. No code-golf, você está otimizando a porcaria de uma única função, então obviamente é importante / especial.
Para testar sua função a partir de um programa em C, é possível escrever um wrapper que coloque os argumentos no lugar certo, salve / restaure os registros extras que você bloqueia e insira o valor de retorno e/rax
se já não estiver lá.
É normal exigir que DF (sinalizador de direção de seqüência de caracteres para lods
/ stos
/ etc.) seja limpo (para cima) na chamada / ret. Deixar indefinido na chamada / ret seria bom. Exigir que ele seja limpo ou definido na entrada, mas deixá-lo modificado quando você voltar seria estranho.
Retornar valores de FP em x87 st0
é razoável, mas retornar st3
com lixo em outro registro x87 não é. O chamador teria que limpar a pilha x87. Mesmo retornando st0
com registros de pilha superior não vazios também seria questionável (a menos que você retorne vários valores).
call
, assim [rsp]
como o seu endereço de retorno. Você pode evitar call
/ ret
no x86 usando o registro de link como lea rbx, [ret_addr]
/ jmp function
e retornar com jmp rbx
, mas isso não é "razoável". Isso não é tão eficiente quanto chamar / reter; portanto, não é algo que você encontraria plausivelmente em código real.Casos limítrofes: escreva uma função que produz uma sequência em uma matriz, considerando os 2 primeiros elementos como args de função . Eu escolhi fazer com que o chamador armazenasse o início da sequência no array e apenas passasse um ponteiro para o array. Isso definitivamente está dobrando os requisitos da pergunta. Eu considerei tomar os argumentos embalados em xmm0
para movlps [rdi], xmm0
, que também seria uma convenção de chamada estranho.
As chamadas do sistema OS X fazem isso ( CF=0
significa que não há erro): É uma prática recomendada usar o registro de sinalizadores como um valor de retorno booleano? .
Qualquer condição que possa ser verificada com um JCC é perfeitamente razoável, especialmente se você puder escolher um que tenha alguma relevância semântica para o problema. (por exemplo, uma função de comparação pode definir sinalizadores, então jne
será usada se não forem iguais).
char
) sejam sinalizados ou estendidos a zero para 32 ou 64 bits.Isso não é irracional; o uso movzx
ou movsx
para evitar lentidão parcial no registro é normal no x86 asm moderno. De fato, clang / LLVM já cria código que depende de uma extensão não documentada da convenção de chamada do System V x86-64: args mais estreitos que 32 bits são sinal ou zero estendidos a 32 bits pelo chamador .
Você pode documentar / descrever a extensão para 64 bits escrevendo uint64_t
ou int64_t
no seu protótipo, se desejar. por exemplo, para que você possa usar uma loop
instrução, que use os 64 bits inteiros do RCX, a menos que você use um prefixo de tamanho de endereço para substituir o tamanho de 32 bits para ECX (sim, tamanho de endereço e não operando).
Observe que long
é apenas um tipo de 32 bits na ABI de 64 bits do Windows e na ABI do Linux x32 ; uint64_t
é inequívoco e mais curto para digitar que unsigned long long
.
Windows de 32 bits __fastcall
, já sugerido por outra resposta : número inteiro args em ecx
e edx
.
x86-64 System V : passa muitos argumentos nos registros e possui muitos registros com excesso de chamadas que você pode usar sem prefixos REX. Mais importante, ele foi realmente escolhido para permitir que os compiladores incorporem memcpy
ou configurem o memset tão rep movsb
facilmente: os 6 primeiros argumentos de número inteiro / ponteiro são passados em RDI, RSI, RDX, RCX, RCX, R8, R9.
Se sua função usa lodsd
/ stosd
dentro de um loop que executa rcx
vezes (com a loop
instrução), você pode dizer "chamável a partir de C como int foo(int *rdi, const int *rsi, int dummy, uint64_t len)
na convenção de chamada do System V x86-64". exemplo: chromakey .
GCC de 32 bits regparm
: argumentos inteiros em EAX , ECX, EDX, retornam em EAX (ou EDX: EAX). Ter o primeiro argumento no mesmo registro que o valor de retorno permite algumas otimizações, como neste caso com um chamador de exemplo e um protótipo com um atributo de função . E, claro, o AL / EAX é especial para algumas instruções.
A ABI do Linux x32 usa ponteiros de 32 bits no modo longo, para que você possa salvar um prefixo REX ao modificar um ponteiro ( exemplo de caso de uso ). Você ainda pode usar o tamanho do endereço de 64 bits, a menos que tenha um número inteiro negativo de 32 bits estendido a zero em um registro (portanto, seria um valor grande sem sinal se você o fizesse [rdi + rdx]
).
Observe que push rsp
/ pop rax
é 2 bytes e equivalente a mov rax,rsp
, portanto, você ainda pode copiar registros completos de 64 bits em 2 bytes.
ret 16
; eles não exibem o endereço de retorno, pressionam uma matriz e depois push rcx
/ ret
. O chamador teria que saber o tamanho da matriz ou salvou o RSP em algum lugar fora da pilha para se encontrar.
Use codificações curtas de casos especiais para AL / AX / EAX e outras formas curtas e instruções de byte único
Os exemplos assumem o modo 32/64 bits, em que o tamanho padrão do operando é 32 bits. Um prefixo de tamanho de operando altera a instrução para AX em vez de EAX (ou o inverso no modo de 16 bits).
inc/dec
um registro (que não seja de 8 bits): inc eax
/ dec ebp
. (Não x86-64: os 0x4x
bytes do código de operação foram redirecionados como prefixos REX, assim inc r/m32
como a única codificação.)
8 bits inc bl
são 2 bytes, usando a inc r/m8
codificação opcode + ModR / M operando . Então use inc ebx
para incrementar bl
, se for seguro. (por exemplo, se você não precisar do resultado ZF nos casos em que os bytes superiores possam ser diferentes de zero).
scasd
: e/rdi+=4
, requer que o registro aponte para a memória legível. Às vezes, útil, mesmo que você não se importe com o resultado FLAGS (como cmp eax,[rdi]
/ rdi+=4
). E no modo de 64 bits, scasb
pode funcionar como um byteinc rdi
, se lodsb ou stosb não forem úteis.
xchg eax, r32
: Este é o lugar onde 0x90 NOP vieram de: xchg eax,eax
. Exemplo: reorganize 3 registros com duas xchg
instruções em um loop cdq
/ para o GCD em 8 bytes, onde a maioria das instruções é de byte único, incluindo um abuso de / em vez de /idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: estenda o sinal EAX para o EDX: EAX, ou seja, copie o bit alto do EAX para todos os bits do EDX. Para criar um zero com conhecido não negativo ou obter um 0 / -1 para adicionar / submarcar ou mascarar. lição de história x86: cltq
vs.movslq
, e também AT & T vs. mnemônicos Intel para isso e os relacionados cdqe
.
lodsb / d : como mov eax, [rsi]
/ rsi += 4
sem sinalizadores de clobber. (Supondo que o DF seja claro, quais convenções de chamada padrão requerem na entrada da função.) Também stosb / d, às vezes scas, e mais raramente mov / cmps.
push
/ pop reg
. por exemplo, no modo de 64 bits, push rsp
/ pop rdi
é de 2 bytes, mas mov rdi, rsp
precisa de um prefixo REX e de 3 bytes.
xlatb
existe, mas raramente é útil. Uma grande tabela de pesquisa é algo a evitar. Também nunca encontrei um uso para instruções AAA / DAA ou outras instruções de pacote BCD ou 2-ASCII.
1 byte lahf
/ sahf
raramente são úteis. Você pode lahf
/ and ah, 1
como alternativa setc ah
, mas normalmente não é útil.
E para o CF especificamente, é sbb eax,eax
necessário obter um byte desalc
0 / -1 ou mesmo não documentado, mas com suporte universal (conjunto AL da Carry), o que efetivamente ocorre sbb al,al
sem afetar os sinalizadores. (Removido em x86-64). Eu usei o SALC no Desafio de Apreciação do Usuário # 1: Dennis ♦ .
1 byte cmc
/ clc
/ stc
(flip ("complemento"), clear ou set CF) raramente são úteis, embora eu tenha achado um usocmc
na adição de precisão estendida com pedaços de base 10 ^ 9. Para definir / limpar incondicionalmente o CF, normalmente providencie para que isso aconteça como parte de outra instrução, por exemplo, xor eax,eax
limpa o CF e o EAX. Não há instruções equivalentes para outros sinalizadores de condição, apenas DF (direção da corda) e IF (interrupções). A bandeira de transporte é especial para muitas instruções; turnos configurá-lo, adc al, 0
pode adicioná-lo ao AL em 2 bytes, e mencionei anteriormente o SALC não documentado.
std
Eu cld
raramente pareço valer a pena . Especialmente no código de 32 bits, é melhor usar apenas dec
um ponteiro e um mov
operando de origem de memória em uma instrução ALU, em vez de definir DF, então lodsb
/ stosb
vá para baixo em vez de para cima. Normalmente, se você precisar de um modo descendente, ainda terá outro ponteiro subindo; portanto, precisará de mais de um std
e de cld
toda a função para usar lods
/ stos
para ambos. Em vez disso, basta usar as instruções da string para a direção ascendente. (As convenções de chamada padrão garantem DF = 0 na entrada da função, portanto, você pode assumir isso de graça, sem usar cld
.)
No original 8086, AX foi muito especial: instruções gosto lodsb
/ stosb
, cbw
, mul
/ div
e outros usá-lo implicitamente. Esse ainda é o caso, é claro; O x86 atual não eliminou nenhum dos opcodes do 8086 (pelo menos nenhum dos documentados oficialmente). Porém, as CPUs posteriores adicionaram novas instruções que forneceram maneiras melhores / mais eficientes de fazer as coisas sem copiá-las ou trocá-las primeiro pelo AX. (Ou para EAX no modo de 32 bits.)
por exemplo, o 8086 não possuía adições posteriores como movsx
/ movzx
para carregar ou mover + extensão de sinal ou operando 2 e 3 imul cx, bx, 1234
que não produzem um resultado de metade superior e não possuem operandos implícitos.
Além disso, o principal gargalo do 8086 era a busca de instruções, portanto a otimização do tamanho do código era importante para o desempenho naquela época . O designer de ISA do 8086 (Stephen Morse) gastou muito espaço de codificação de código de opcode em casos especiais para o AX / AL, incluindo opcodes especiais de destino (E) AX / AL para todas as instruções ALU-src imediatas básicas , apenas opcode + imediato sem byte ModR / M. 2 bytes add/sub/and/or/xor/cmp/test/... AL,imm8
ou AX,imm16
ou (no modo de 32 bits) EAX,imm32
.
Mas não há um caso especial EAX,imm8
, portanto, a codificação ModR / M regular add eax,4
é mais curta.
A suposição é que, se você trabalhar com alguns dados, desejará no AX / AL; portanto, trocar um registro com o AX é algo que você pode querer fazer, talvez com mais frequência do que copiar um registro no AX com mov
.
Tudo sobre a codificação de instruções 8086 suporta esse paradigma, desde instruções como lodsb/w
todas as codificações de casos especiais para imediatos com EAX até seu uso implícito, mesmo para multiplicar / dividir.
Não se empolgue; não é uma vitória automática trocar tudo para o EAX, especialmente se você precisar usar imediatos com registros de 32 bits em vez de 8 bits. Ou se você precisar intercalar operações em várias variáveis em registros de uma só vez. Ou, se você estiver usando instruções com 2 registros, não é de todo imediato.
Mas lembre-se sempre: estou fazendo algo que seria mais curto no EAX / AL? Posso reorganizar para que eu tenha isso no AL ou atualmente estou aproveitando melhor o AL com o que já estou usando.
Misture operações de 8 e 32 bits livremente para tirar vantagem sempre que for seguro (não é necessário realizar o registro completo ou o que for).
cdq
é útil para as div
necessidades zeradas edx
em muitos casos.
cdq
antes de não assinar div
se souber que seu dividendo está abaixo de 2 ^ 31 (ou seja, não negativo quando tratado como assinado) ou se você o usar antes de definir eax
um valor potencialmente grande. Normalmente (código-golfe fora) você usar cdq
como configuração para idiv
, e xor edx,edx
antesdiv
fastcall
convençõesA plataforma x86 possui muitas convenções de chamada . Você deve usar aqueles que passam parâmetros nos registradores. No x86_64, os primeiros parâmetros são passados nos registros de qualquer maneira, portanto não há problema nisso. Em plataformas de 32 bits, a convenção de chamada padrão ( cdecl
) passa parâmetros na pilha, o que não é bom para jogar golfe - acessar parâmetros na pilha requer instruções longas.
Ao usar fastcall
em plataformas de 32 bits, 2 primeiros parâmetros geralmente são passados ecx
e edx
. Se sua função tiver 3 parâmetros, considere implementá-la em uma plataforma de 64 bits.
Protótipos da função C para fastcall
convenção (extraídos desta resposta de exemplo ):
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU
0100 81C38000 ADD BX,0080
0104 83EB80 SUB BX,-80
Samely, adicione -128 em vez de subtrair 128
< 128
em <= 127
para reduzir a magnitude de um operando imediato cmp
ou o gcc sempre prefere reorganizar se compara a reduzir a magnitude, mesmo que não seja -129 vs. -128.
mul
(então inc
/ dec
para obter +1 / -1 e também zero)Você pode zerar eax e edx multiplicando por zero em um terceiro registro.
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
resultará em EAX, EDX e EBX sendo zero em apenas quatro bytes. Você pode zerar EAX e EDX em três bytes:
xor eax, eax
cdq
Mas a partir desse ponto inicial, você não pode obter um terceiro registro zerado em mais um byte ou um registro +1 ou -1 em outros 2 bytes. Em vez disso, use a técnica mul.
Exemplo de caso de uso: concatenando os números de Fibonacci em binário .
Observe que, após a conclusão de um LOOP
loop, o ECX será zero e poderá ser usado para zerar EDX e EAX; você nem sempre precisa criar o primeiro zero com xor
.
Podemos assumir que a CPU está em um estado padrão conhecido e documentado com base na plataforma e no SO.
Por exemplo:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
. Então, sim, é um jogo justo tirar proveito disso se você estiver escrevendo um programa em vez de uma função. Eu fiz isso em Extreme Fibonacci . (Em um executável dinamicamente vinculado, ld.so corridas antes de saltar para o seu _start
e faz lixo licença em registros, mas estática é apenas o seu código.)
Para adicionar ou subtrair 1, use o byte inc
ou as dec
instruções menores que as instruções de adição e sub multibyte.
inc/dec r32
com o número do registro codificado no código de operação. O mesmo inc ebx
vale 1 byte, mas inc bl
é 2. Ainda menor do que é add bl, 1
claro, para registros diferentes de al
. Observe também que inc
/ dec
deixe o CF sem modificação, mas atualize os outros sinalizadores.
lea
para matemáticaEssa é provavelmente uma das primeiras coisas que se aprende sobre o x86, mas deixo aqui como um lembrete. lea
pode ser usado para multiplicar por 2, 3, 4, 5, 8 ou 9 e adicionar um deslocamento.
Por exemplo, para calcular ebx = 9*eax + 3
em uma instrução (no modo de 32 bits):
8d 5c c0 03 lea 0x3(%eax,%eax,8),%ebx
Aqui está sem um deslocamento:
8d 1c c0 lea (%eax,%eax,8),%ebx
Uau! Obviamente, também lea
pode ser usado para fazer contas, como ebx = edx + 8*eax + 3
para calcular a indexação de array.
lea eax, [rcx + 13]
é a versão sem prefixos extras para o modo de 64 bits. Tamanho de operando de 32 bits (para o resultado) e tamanho de endereço de 64 bits (para as entradas).
As instruções de loop e string são menores que as seqüências de instruções alternativas. O mais útil é o loop <label>
que é menor que a sequência de duas instruções dec ECX
e jnz <label>
, e lodsb
é menor que mov al,[esi]
e inc si
.
mov
pequenos imediatos em registros mais baixos quando aplicávelSe você já sabe que os bits superiores de um registro são 0, pode usar uma instrução mais curta para mover um imediato para os registros inferiores.
b8 0a 00 00 00 mov $0xa,%eax
versus
b0 0a mov $0xa,%al
push
/ pop
para imm8 a zero bits superioresCrédito para Peter Cordes. xor
/ mov
é de 4 bytes, mas push
/ pop
é de apenas 3!
6a 0a push $0xa
58 pop %eax
mov al, 0xa
é bom se você não precisar estender zero para o registro completo. Mas se o fizer, xor / mov é de 4 bytes vs. 3 para push imm8 / pop ou lea
de outra constante conhecida. Isso pode ser útil em combinação com mul
zero 3 registros em 4 bytes ou cdq
, se você precisar de muitas constantes.
[0x80..0xFF]
, que não são representáveis como um imm8 estendido por sinal8. Ou se você já conhece os bytes superiores, por exemplo, mov cl, 0x10
após uma loop
instrução, porque a única maneira de loop
não pular é quando ela é feita rcx=0
. (Eu acho que você disse isso, mas seu exemplo usa um xor
). Você pode até usar o byte baixo de um registro para outra coisa, desde que a outra coisa volte a zero (ou o que seja) quando terminar. por exemplo, meu programa Fibonacci fica -1024
em ebx e usa bl.
xchg eax, r32
), por exemplo, mov bl, 10
/ dec bl
/ jnz
para que seu código não se importe com os altos bytes do RBX.
Após muitas instruções aritméticas, o Sinalizador de transporte (não assinado) e Sinalizador de estouro (assinado) são definidos automaticamente ( mais informações ). O Sinalizador e o Sinalizador Zero são definidos após muitas operações aritméticas e lógicas. Isso pode ser usado para ramificação condicional.
Exemplo:
d1 f8 sar %eax
O ZF é definido por esta instrução, para que possamos usá-lo para ramificação condicional.
test al,1
; você geralmente não recebe isso de graça. (Ou and al,1
para criar um inteiro 0/1 dependendo par / ímpar.)
test
/ cmp
", isso seria um iniciante bastante básico x86, mas ainda vale a pena ser votado.
Isso não é específico para x86, mas é uma dica de montagem para iniciantes amplamente aplicável. Se você souber que um loop while será executado pelo menos uma vez, reescrevendo o loop como um loop do while, com verificação da condição do loop no final, geralmente salva uma instrução de salto de 2 bytes. Em um caso especial, você pode até usar loop
.
do{}while()
o idioma natural de loop na montagem (especialmente para eficiência). Observe também que 2 bytes jecxz
/jrcxz
um loop de antes funciona muito bem loop
para lidar com as "necessidades de executar zero vezes" case "eficientemente" (nas raras CPUs onde loop
não é lento). jecxz
também é utilizável dentro do loop para implementar awhile(ecx){}
, com jmp
na parte inferior.
System V x86 usa a pilha e System V x86-64 usos rdi
, rsi
, rdx
, rcx
, etc. para parâmetros de entrada, e rax
como o valor de retorno, mas é perfeitamente razoável usar sua própria convenção de chamada. __fastcall usa ecx
e edx
como parâmetros de entrada, e outros compiladores / SOs usam suas próprias convenções . Use a pilha e quaisquer registros como entrada / saída, quando conveniente.
Exemplo: o contador de bytes repetitivos , usando uma convenção de chamada inteligente para uma solução de 1 byte.
Meta: Gravando entrada em registros , Gravando saída em registros
Outros recursos: notas de Agner Fog sobre convocar convenções
int 0x80
que requer um monte de configuração.
int 0x80
no código de 32 bits, ou syscall
no código de 64 bits, para invocar sys_write
, é a única maneira boa. É o que eu usei para o Extreme Fibonacci . No código de 64 bits __NR_write = 1 = STDOUT_FILENO
, você pode mov eax, edi
. Ou se os bytes superiores do EAX forem zero, mov al, 4
no código de 32 bits. Você também pode , call printf
ou puts
acho, e escrever uma resposta "x86 asm para Linux + glibc". Eu acho que é razoável não contar o espaço de entrada PLT ou GOT, ou o próprio código da biblioteca.
char*buf
e produzisse a string com isso, com formatação manual. por exemplo, assim (otimizado desajeitadamente para velocidade) asm FizzBuzz , onde eu coloquei os dados das strings no registro e os armazenei mov
, porque as strings eram curtas e de comprimento fixo.
CMOVcc
e conjuntos condicionaisSETcc
Isso é mais um lembrete para mim, mas existem instruções condicionais de conjunto e instruções de movimentação condicional nos processadores P6 (Pentium Pro) ou mais recente. Há muitas instruções baseadas em um ou mais dos sinalizadores definidos no EFLAGS.
cmov
possui um código de operação de 2 bytes ( 0F 4x +ModR/M
), portanto, é um mínimo de 3 bytes. Mas a fonte é r / m32, portanto, você pode carregar condicionalmente em 3 bytes. Além de ramificação, setcc
é útil em mais casos do que cmovcc
. Ainda assim, considere todo o conjunto de instruções, não apenas as instruções da linha de base 386. (. Embora SSE2 e instrução IMC / BMI2 são tão grandes que eles são raramente útil rorx eax, ecx, 32
é de 6 bytes, mais do que mov + ror agradável para o desempenho, não de golfe a menos POPCNT ou PDEP salva muitas iSNS.)
setcc
.
jmp
bytes organizando se / então em vez de se / então / outraIsso é certamente muito básico, apenas pensei em postar isso como algo para se pensar ao jogar golfe. Como exemplo, considere o seguinte código simples para decodificar um caractere de dígito hexadecimal:
cmp $'A', %al
jae .Lletter
sub $'0', %al
jmp .Lprocess
.Lletter:
sub $('A'-10), %al
.Lprocess:
movzbl %al, %eax
...
Isso pode ser reduzido em dois bytes, deixando um caso "then" cair em um caso "else":
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
latência extra no caminho crítico para um caso não faz parte de uma cadeia de dependência transportada por loop (como aqui onde cada dígito de entrada é independente até mesclar blocos de 4 bits ) Mas acho que +1 de qualquer maneira. BTW, seu exemplo tem uma otimização perdida separada: se você precisar de uma movzx
no final de qualquer maneira, sub $imm, %al
não use o EAX para aproveitar a codificação de 2 bytes no-modrm de op $imm, %al
.
cmp
fazendo sub $'A'-10, %al
; jae .was_alpha
; add $('A'-10)-'0'
. (Eu acho que entendi a lógica). Observe que, 'A'-10 > '9'
portanto, não há ambiguidade. Subtrair a correção de uma letra quebra um dígito decimal. Portanto, isso é seguro se assumirmos que nossa entrada é hexadecimal válida, assim como a sua.
Você pode buscar objetos seqüenciais da pilha configurando esi para esp e executando uma sequência de lodsd / xchg reg, eax.
pop eax
/ pop edx
/ ...? Se você precisar deixá-los na pilha, poderá push
devolvê-los depois para restaurar o ESP, ainda com 2 bytes por objeto, sem necessidade mov esi,esp
. Ou você quis dizer para objetos de 4 bytes no código de 64 bits onde pop
obteria 8 bytes? BTW, você ainda pode usar pop
para fazer um loop sobre um tampão com melhor desempenho do que lodsd
, por exemplo, para além estendida de precisão em Extrema Fibonacci
Para copiar um registro de 64 bits, use push rcx
; pop rdx
em vez de 3 bytes mov
.
O tamanho padrão do operando de push / pop é de 64 bits sem a necessidade de um prefixo REX.
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(Um prefixo de tamanho de operando pode substituir o tamanho de push / pop para 16 bits, mas tamanho de operando de push / pop de 32 bits não pode ser codificado no modo de 64 bits, mesmo com REX.W = 0.)
Se um ou ambos os registradores forem r8
.. r15
, usemov
porque push e / ou pop precisarão de um prefixo REX. Na pior das hipóteses, isso realmente perde se os dois precisarem de prefixos REX. Obviamente, você deve evitar r8..r15 de qualquer maneira no código golf.
Você pode manter sua fonte mais legível ao desenvolver com essa macro NASM . Lembre-se de que ele pisa nos 8 bytes abaixo do RSP. (Na zona vermelha no x86-64 System V). Mas, em condições normais, é um substituto para 64 bits mov r64,r64
oumov r64, -128..127
; mov %1, %2 ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
%macro MOVE 2
push %2
pop %1
%endmacro
Exemplos:
MOVE rax, rsi ; 2 bytes (push + pop)
MOVE rbp, rdx ; 2 bytes (push + pop)
mov ecx, edi ; 2 bytes. 32-bit operand size doesn't need REX prefixes
MOVE r8, r10 ; 4 bytes, don't use
mov r8, r10 ; 3 bytes, REX prefix has W=1 and the bits for reg and r/m being high
xchg eax, edi ; 1 byte (special xchg-with-accumulator opcodes)
xchg rax, rdi ; 2 bytes (REX.W + that)
xchg ecx, edx ; 2 bytes (normal xchg + modrm)
xchg rcx, rdx ; 3 bytes (normal REX + xchg + modrm)
A xchg
parte do exemplo é que, às vezes, você precisa adicionar um valor ao EAX ou RAX e não se preocupa em preservar a cópia antiga. push / pop não ajuda você realmente a trocar, no entanto.
push 200; pop edx
- 3 bytes para inicialização.