mov
-imediato é caro para constantes
Isso 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.
Inicializando 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
Inicializando 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
push 200; pop edx
- 3 bytes para inicialização.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, ondedec
/inc
é de 2 bytes. Epush r64
/pop 64
(2 bytes) pode até substituir 3 bytesmov r64, r64
(3 bytes por REX). Ver também Definir todos os bits no registrador CPU de 1 de forma eficiente para coisas comolea eax, [rcx-1]
dado um valor conhecido noeax
(por exemplo, se for necessário um registo zerada e outra constante, basta usar LEA em vez de push / popEm 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.fonte
al, imm8
casos especiais, comoor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ja .non_alphabetic
sendo 2 bytes cada, em vez de 3. O usoal
de dados de caracteres também permitelodsb
e / oustosb
. Ou useal
para testar algo sobre o byte baixo do EAX, comolodsd
/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 bemop eax, imm32
, como em minha resposta chroma-keyEscolha sua convenção de chamada para colocar args onde desejar.
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á.Os limites do que é razoável: qualquer coisa que não imponha um ônus irracional ao chamador:
É 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 retornarst3
com lixo em outro registro x87 não é. O chamador teria que limpar a pilha x87. Mesmo retornandost0
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 evitarcall
/ret
no x86 usando o registro de link comolea rbx, [ret_addr]
/jmp function
e retornar comjmp 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
paramovlps [rdi], xmm0
, que também seria uma convenção de chamada estranho.Retornar um booleano em FLAGS (códigos de condição)
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).Exija que args estreitos (como a
char
) sejam sinalizados ou estendidos a zero para 32 ou 64 bits.Isso não é irracional; o uso
movzx
oumovsx
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
ouint64_t
no seu protótipo, se desejar. por exemplo, para que você possa usar umaloop
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 queunsigned long long
.Convenções de chamada existentes:
Windows de 32 bits
__fastcall
, já sugerido por outra resposta : número inteiro args emecx
eedx
.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ãorep 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 executarcx
vezes (com aloop
instrução), você pode dizer "chamável a partir de C comoint 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 amov rax,rsp
, portanto, você ainda pode copiar registros completos de 64 bits em 2 bytes.fonte
ret 16
; eles não exibem o endereço de retorno, pressionam uma matriz e depoispush 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: os0x4x
bytes do código de operação foram redirecionados como prefixos REX, assiminc r/m32
como a única codificação.)8 bits
inc bl
são 2 bytes, usando ainc r/m8
codificação opcode + ModR / M operando . Então useinc ebx
para incrementarbl
, 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 (comocmp 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 duasxchg
instruções em um loopcdq
/ 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 relacionadoscdqe
.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, masmov 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ê podelahf
/and ah, 1
como alternativasetc 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 ocorresbb 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
Eucld
raramente pareço valer a pena . Especialmente no código de 32 bits, é melhor usar apenasdec
um ponteiro e ummov
operando de origem de memória em uma instrução ALU, em vez de definir DF, entãolodsb
/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 umstd
e decld
toda a função para usarlods
/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 usarcld
.)História do 8086: por que essas codificações existem
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 3imul 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
ouAX,imm16
ou (no modo de 32 bits)EAX,imm32
.Mas não há um caso especial
EAX,imm8
, portanto, a codificação ModR / M regularadd 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).
fonte
cdq
é útil para asdiv
necessidades zeradasedx
em muitos casos.cdq
antes de não assinardiv
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 definireax
um valor potencialmente grande. Normalmente (código-golfe fora) você usarcdq
como configuração paraidiv
, exor edx,edx
antesdiv
Use
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 passadosecx
eedx
. 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 ):fonte
Subtraia -128 em vez de adicionar 128
Samely, adicione -128 em vez de subtrair 128
fonte
< 128
em<= 127
para reduzir a magnitude de um operando imediatocmp
ou o gcc sempre prefere reorganizar se compara a reduzir a magnitude, mesmo que não seja -129 vs. -128.Crie 3 zeros com
mul
(entãoinc
/dec
para obter +1 / -1 e também zero)Você pode zerar eax e edx multiplicando por zero em um terceiro registro.
resultará em EAX, EDX e EBX sendo zero em apenas quatro bytes. Você pode zerar EAX e EDX em três bytes:
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 comxor
.fonte
Registradores e sinalizadores de CPU estão em estados de inicialização conhecidos
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
fonte
_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 asdec
instruções menores que as instruções de adição e sub multibyte.fonte
inc/dec r32
com o número do registro codificado no código de operação. O mesmoinc ebx
vale 1 byte, masinc bl
é 2. Ainda menor do que éadd bl, 1
claro, para registros diferentes deal
. Observe também queinc
/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):Aqui está sem um deslocamento:
Uau! Obviamente, também
lea
pode ser usado para fazer contas, comoebx = edx + 8*eax + 3
para calcular a indexação de array.fonte
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çõesdec ECX
ejnz <label>
, elodsb
é menor quemov al,[esi]
einc si
.fonte
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.
versus
Use
push
/pop
para imm8 a zero bits superioresCrédito para Peter Cordes.
xor
/mov
é de 4 bytes, maspush
/pop
é de apenas 3!fonte
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 oulea
de outra constante conhecida. Isso pode ser útil em combinação commul
zero 3 registros em 4 bytes oucdq
, 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 umaloop
instrução, porque a única maneira deloop
não pular é quando ela é feitarcx=0
. (Eu acho que você disse isso, mas seu exemplo usa umxor
). 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.Os FLAGS são configurados após muitas instruções
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:
O ZF é definido por esta instrução, para que possamos usá-lo para ramificação condicional.
fonte
test al,1
; você geralmente não recebe isso de graça. (Ouand 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.Use loops do-while em vez de loops while
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
.fonte
do{}while()
o idioma natural de loop na montagem (especialmente para eficiência). Observe também que 2 bytesjecxz
/jrcxz
um loop de antes funciona muito bemloop
para lidar com as "necessidades de executar zero vezes" case "eficientemente" (nas raras CPUs ondeloop
não é lento).jecxz
também é utilizável dentro do loop para implementar awhile(ecx){}
, comjmp
na parte inferior.Use as convenções de chamada que forem convenientes
System V x86 usa a pilha e System V x86-64 usos
rdi
,rsi
,rdx
,rcx
, etc. para parâmetros de entrada, erax
como o valor de retorno, mas é perfeitamente razoável usar sua própria convenção de chamada. __fastcall usaecx
eedx
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
fonte
int 0x80
que requer um monte de configuração.int 0x80
no código de 32 bits, ousyscall
no código de 64 bits, para invocarsys_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ê podemov 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
ouputs
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 armazeneimov
, porque as strings eram curtas e de comprimento fixo.Use movimentos
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.
fonte
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 quecmovcc
. 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 útilrorx 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
.Economize em
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:
Isso pode ser reduzido em dois bytes, deixando um caso "then" cair em um caso "else":
fonte
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 umamovzx
no final de qualquer maneira,sub $imm, %al
não use o EAX para aproveitar a codificação de 2 bytes no-modrm deop $imm, %al
.cmp
fazendosub $'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.
fonte
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 necessidademov esi,esp
. Ou você quis dizer para objetos de 4 bytes no código de 64 bits ondepop
obteria 8 bytes? BTW, você ainda pode usarpop
para fazer um loop sobre um tampão com melhor desempenho do quelodsd
, por exemplo, para além estendida de precisão em Extrema FibonacciPara codegolf e ASM: Use as instruções: use apenas registradores, pressione pop, minimize a memória de registro ou a memória imediata
fonte
Para copiar um registro de 64 bits, use
push rcx
;pop rdx
em vez de 3 bytesmov
.O tamanho padrão do operando de push / pop é de 64 bits sem a necessidade de um prefixo REX.
(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
Exemplos:
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.fonte