2 524224 instruções
Meu programa:
_start:
mov bl, _end
stc
iloop: adc [bx], al
inc bx
jnz iloop
jnc _start
a32
loop _start
hlt
_end:
(Nota técnica: isso é escrito para nasm. a32
É a sintaxe do nasm para o byte de prefixo de tamanho de endereço alternativo. Para masm, você substitui a32
por defb 0x67
.)
Para maior clareza, aqui está o resultado da listagem:
1 _start:
2 0000 B310 mov bl, _end
3 0002 F9 stc
4 0003 1007 iloop: adc [bx], al
5 0005 43 inc bx
6 0006 75F9 jnz iloop
7 0008 73F4 jnc _start
8 000A 67 a32
9 000B E2F1 loop _start
10 000D F4 hlt
11 _end:
O programa assume que o processador está no modo real e que o programa está localizado na parte inferior de um segmento de 64k de memória que, de outra forma, seria inicializado com zero de todos os bits. Seu design é simples: trate a memória como um único inteiro gigante não assinado e a incremente através de todos os valores possíveis, até reverter para todos os zeros. Repita isso 2 32 vezes. Então pare.
O loop mais interno (linhas 4-6) é responsável por incrementar o número inteiro enorme. Cada iteração adiciona zero ou um a um único byte, dependendo da execução do byte anterior. Observe que cada byte no número inteiro enorme é acessado, independentemente de ter sido alterado ou não, portanto esse loop sempre itera 2 16 a 14 vezes.
A propósito, no caso de você estar se perguntando, esse código ilustra o motivo pelo qual as instruções x86 inc
/ dec
não afetam o sinalizador de transporte: apenas para simplificar esse tipo de padrão de transporte de vários bytes. (Esse padrão surgiu com mais frequência nos dias de microprocessadores de 8 bits, quando o conjunto de instruções 8080 original foi definido.)
A linha 7 faz com que o processo de incremento se repita até que um seja executado no último byte, indicando que o número inteiro enorme foi redefinido para todos os bits-zero. Isso leva muito tempo.
As linhas 8‒9 marcam o loop mais externo e fazem com que esse processo se repita 2 32 vezes, até o ecx
registro rolar para zero. Isso é efetivamente equivalente a adicionar outros 32 bits ao número inteiro gigante.
Seria possível adicionar outro loop externo e fazer isso novamente usando (digamos) o edx
registrador e, talvez, usando esi
e edi
para ainda mais repetições. No entanto, não vale a pena fazer. As instruções para incrementar e fazer loop exigem quatro bytes. Esses quatro bytes são retirados do número inteiro gigante. Portanto, perdemos 32 bits no contador de RAM, apenas para adicionar 32 bits através de um registro: acaba sendo uma lavagem. O único motivo ecx
é uma exceção: ele possui uma loop
instrução especializada que se encaixa em apenas três bytes. Assim, o programa troca 24 bits por 32, um ganho pequeno, mas ainda positivo, de 8 bits.
Não é muito difícil calcular diretamente o número de instruções que o programa executa antes de parar. No entanto, há uma maneira muito mais simples de estimar esse número. Os modifica programa de toda a sua memória, menos os 14 bytes que contêm o programa, e os bx
e ecx
registros. Isso adiciona até 2 16 - 14 + 2 + 4 = 65528 bytes, para um total de 524224 bits. O atalho envolve perceber que, no processo de execução, todos os padrões possíveis de 524224 bits aparecem exatamente uma vez. Para a RAM e o ecx
registro, é fácil ver isso, pois o programa aumenta em cada valor. Parabx
isso é um pouco menos óbvio, pois está sendo alterado ao mesmo tempo em que o valor na memória está sendo atualizado. No entanto, pode-se mostrar que, dada a estrutura do programa, se um padrão de bits completo realmente aparecer duas vezes, o programa deverá estar em um loop infinito. Como esse não é o caso, cada padrão de bits deve ser visitado apenas uma vez. (A prova completa é deixada como exercício para o leitor, naturalmente.)
Como todo padrão de bits possível aparece no decorrer do programa, o programa deve executar pelo menos 2 instruções 524224 , o que é aproximadamente igual a 1,4 × 10 157807 . (O número acutal é muito ligeiramente maior, por causa das instruções de salto, mas a diferença nessa magnitude é insignificante.)
Obviamente, isso poderia ser significativamente aprimorado usando mais de 64k de RAM. Estou adiando a próxima versão do código até descobrir exatamente quanta RAM pode ser acessada.