Classificação - JIT compilado (quanto mais baixo, melhor)
- es1024 - 81,2 pontos (incluindo um compilador funcional!)
- Kieth Randall - 116 pontos
- Ell - 121 pontos
Tabela de classificação - Interpretada (quanto mais baixo, melhor)
- Martin Büttner - 706654 pontos (algo em torno de 2 horas).
- criptych - 30379 pontos (97 segundos)
Sua missão, se você aceitar, é escrever o menor intérprete / VM de bytecode possível. O VM / intérprete usa uma pequena arquitetura CISC (as operações podem variar em tamanho), com o idioma especificado abaixo. Após a conclusão, você deve imprimir o valor dos 3 registros da CPU para provar que a saída correta é impressa (3.126.900.366).
Compilador
Se você quiser fazer seus próprios testes, um compilador será publicado abaixo. Sinta-se livre para publicar seus testes com sua resposta.
Especificações "VM"
A VM possui 3 registradores integrais não assinados de 32 bits: R0, R1, R2. Eles são representados em hexadecimal como 0x00, 0x01 e 0x02.
As seguintes operações devem ser suportadas:
O formato é [nome] [... operandos ...], [código op hexadecimal] [... operandos repetidos ...]
- LOAD [registro] [valor de 4 bytes], 0x00 [registro] [valor de 4 bytes]
- PUSH [registro], 0x02 [registro]
- POP [registro], 0x03 [registro]
- ADD [registro, 1 byte] [registro, 1 byte], 0x04 [registro] [registro]
- SUB [registro, 1 byte] [registro, 1 byte], 0x05 [registro] [registro]
- MUL [registro, 1 byte] [registro, 1 byte], 0x06 [registro] [registro]
- DIV [registro, 1 byte] [registro, 1 byte], 0x07 [registro] [registro]
- JMP [linha de código, 4 bytes], 0x08 [número da linha de código de 4 bytes]
- CMP [registro, 1 byte] [registro, 1 byte], 0x09 [registro] [registro]
- BRANCHLT [linha de código, 4 bytes], 0x0a [número da linha de código de 4 bytes]
Algumas notas:
- As operações matemáticas acima adicionam os valores de 2 registros, colocando a saída no primeiro registro.
- O CMP, o operador de comparação, deve comparar os valores de 2 registros e armazenar a saída em algum sinalizador interno (isso pode ser específico da implementação) para uso futuro em instruções de ramificação.
- Se BRANCH for chamado antes do CMP, a menos que BRANCHEQ seja chamado, a "VM" não deverá ramificar.
- PUSH / POP, sem surpresa, empurra ou abre números da pilha.
- Os operadores de salto e ramificação saltam para uma operação específica (linha de código), não para um endereço binário.
- As operações da filial não fazem a comparação. Em vez disso, eles usam a saída da última comparação para executar.
- Os operadores de desvio e salto usam um sistema de indexação de número de linha baseado em zero. (Por exemplo, o JMP 0 salta para a primeira linha)
- Todas as operações devem ser executadas em números não assinados que transbordam para zero e não lançam uma exceção em um estouro inteiro.
- A divisão por zero não é permitida e, como tal, o comportamento do programa não está definido. Você pode (por exemplo) ...
- Falha no programa.
- Finalize a execução da VM e retorne seu estado atual.
- Mostrar uma mensagem "ERR: Divisão por 0".
- A finalização do programa é definida como quando o ponteiro da instrução chega ao final do programa (um programa não vazio pode ser assumido).
Saída A saída deve ser exatamente isso (novas linhas incluídas)
R0 3126900366
R1 0
R2 10000
Pontos Os
pontos são calculados com base na seguinte fórmula:Number Of Characters * (Seconds Needed To Run / 2)
Para evitar diferenças de hardware que causam tempos diferentes, cada teste será executado no meu computador (i5-4210u, 8 GB de RAM) no servidor ubuntu ou no Windows 8, portanto, tente não usar algum tempo de execução exótico insano que compila apenas em um Dual G5 Mac Pro com exatamente 762,66 mb de RAM livre.
Se você estiver usando um tempo de execução / idioma especializado, poste um link para ele.
- Para as partes interessadas, publiquei o código de teste (escrito em C #) aqui: http://pastebin.com/WYCG5Uqu
Programa de teste
A ideia veio daqui , então usaremos uma versão um pouco modificada do programa deles.
A saída correta para o programa é: 3.126.900.366
Em C:
int s, i, j;
for (s = 0, i = 0; i < 10000; i++) {
for (j = 0; j < 10000; j++)
s += (i * j) / 3;
}
No código: [R0 é representativo de s, R1 de j, R2 de i]
LOAD R0 0
LOAD R2 0 <--outer loop value
LOAD R1 0 <--inner loop value
--Begin inner loop--
PUSH R1 <--push inner loop value to the stack
MUL R1 R2 <--(i*j)
PUSH R2
LOAD R2 3
DIV R1 R2 <-- / 3
POP R2
ADD R0 R1 <-- s+=
POP R1
PUSH R2
LOAD R2 1
ADD R1 R2 <--j++
POP R2
PUSH R2
LOAD R2 10000
CMP R1 R2 <-- j < 10000
POP R2
BRANCHLT 3 <--Go back to beginning inner loop
--Drop To outer loop--
LOAD R1 1
ADD R2 R1 <--i++
LOAD R1 10000
CMP R2 R1 <-- i < 10000
LOAD R1 0 <--Reset inner loop
BRANCHLT 2
Em binário / hexadecimal:
0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x02 0x00 0x00 0x00 0x00
0x00 0x01 0x00 0x00 0x00 0x00
0x02 0x01
0x06 0x01 0x02
0x02 0x02
0x00 0x02 0x00 0x00 0x00 0x03
0x07 0x01 0x02
0x03 0x02
0x04 0x00 0x01
0x03 0x01
0x02 0x02
0x00 0x02 0x00 0x00 0x00 0x01
0x04 0x01 0x02
0x03 0x02
0x02 0x02
0x00 0x02 0x00 0x00 0x27 0x10
0x09 0x01 0x02
0x03 0x02
0x0a 0x00 0x00 0x00 0x03
0x00 0x01 0x00 0x00 0x00 0x01
0x04 0x02 0x01
0x00 0x01 0x00 0x00 0x27 0x10
0x09 0x02 0x01
0x00 0x01 0x00 0x00 0x00 0x00
0x0a 0x00 0x00 0x00 0x02
Pontos de bônus (os efeitos são aplicados de forma multiplicativa) Por exemplo, se você se qualificar para os três, seria ((caracteres * 0,50) * 0,75) * 0,90
- Redução de 50% se o intérprete for realmente um compilador JIT
- Redução de 25% se aplicar qualquer tipo de desenrolar / otimização significativa do loop.
- 10% de redução se você estender a VM com
- BRANCHEQ [linha de código, 4 bytes] (Ramificação se igual - opcode 0x0b)
- BRANCHGT [linha de código, 4 bytes] (Ramificar se for maior que - opcode 0x0c)
- FILIAL [linha de código, 4 bytes] (Ramificação se não for igual - opcode 0x0d)
- RLOAD [registro 1] [registro 2] (mova o valor do registro 2 para o registro 1 - código de operação 0x01).
Não permitido
- É pré-compilado o caso de teste no programa. Você deve aceitar o bytecode de STDIN ou de um arquivo (não importa qual).
- Retornando a saída sem executar o programa.
- Qualquer outra maneira de enganar o requisito da VM.
fonte
CMP
seleção para menor ou igualdade? E o que acontece com o resultado?MUL
eDIV
também são subespecificados. Eles devem ser assinados ou não assinados? O que acontece no estouro de multiplicação?Respostas:
C, 752 (589 + 163 para sinalizadores de definição) * 0,5 (JIT) * 0,9 (extensões) * (otimização de 0,75) * (0,64 segundos / 2) = 81,216
Recebe código (
LOAD R0
, etc), sem caracteres à direita, espaço único, sem linhas em branco no meio, sem comentários, etc. Nova linha de rastreamento necessária.Este é então convertido para 80386 bytecode e executado.
Carregando
0
para um registo é substituído porxor
ing o registo com ele próprio em vez demov
ing0
para o registo, o qual é três bytes mais curto no código de bytes gerado, e pode ser muito marginalmente mais rapidamente.Ajuntar com:
É necessário sistema operacional compatível com POSIX.
A entrada é lida no STDIN (use
./bytecode < file
para canalizar de um arquivo).Bytecode resultante para o programa de teste:
Ungolfed:
fonte
C, Pontuação = 854 bytes × (~ 0,8 seg / 2) × 0,5 [JIT] × 0,9 [Extensões] = ~ 154 bytes por segundo
Compile
gcc vm.c -ovm -m32 -w
em um sistema operacional compatível com x86 POSIX.Execute com
./vm < program
, ondeprogram
está um arquivo de programa binário.Indo para a velocidade. O programa executa uma tradução bastante direta do programa de entrada para o código de máquina x86 e permite que a CPU faça o resto.
Por exemplo, aqui está a tradução do programa de teste.
ecx
,esi
Eedi
correspondem aR0
,R1
eR2
, respectivamente;bh
mantém os sinalizadores de status;eax
eedx
são registros de rascunho; a pilha de chamadas corresponde à pilha da VM:Ungolfed
Mostrar snippet de código
fonte
CJam,
222187185 bytes * (muito lento / 2)Eu só queria ver o quão curto eu posso obter uma VM de bytecode escrevendo-a no CJam. Menos de 200 bytes parece bastante decente. É muito lento, porque o próprio CJam é interpretado. Demora muito tempo para executar o programa de teste.
Para executá-lo, faça o download do interpretador Java neste link do sourceforge , salve o código
vm.cjam
e execute-o comO programa espera o bytecode no STDIN. Ainda não encontrei uma maneira de canalizar dados binários em um programa, sem o PowerShell adicionar uma quebra de linha à direita e converter
0x0a
para0x0d 0x0a
, o que é realmente irritante. O código inclui 4 bytes para corrigir isso (D-);
), que eu não incluí na contagem total, porque não é algo que o programa precise fazer se realmente recebeu o bytecode no STDIN, em vez de uma versão estranhamente codificada dele . Se alguém souber uma solução para isso, entre em contato.Ligeiramente não destruído:
Vou adicionar uma explicação adequada amanhã.
Em resumo, estou armazenando todos os registradores, o ponteiro de instruções e o sinalizador de comparação em variáveis, para que eu possa manter a pilha do CJam livre para usar como a pilha da VM.
fonte
python / c ++, pontuação = 56,66
1435 caracteres * .234 / 2 segundos * .5 [JIT] * .75 [Otimização] * .90 [Instruções adicionais]
Compila o programa de entrada para c ++, executa o gcc e, em seguida, executa o resultado. A maior parte do tempo é gasta dentro do gcc.
A única otimização que faço é reduzir as operações da pilha para variáveis explícitas, se for permitido semanticamente. Isso ajuda bastante, cerca de 10 vezes melhor tempo de execução do código compilado (cerca de 0,056 s para realmente executar o binário resultante). Não sei ao certo o que o gcc está fazendo para obter essa melhoria, mas é bom.
Certamente poderia ser jogado um pouco mais.
fonte
Lua 5.2 (ou LuaJIT), 740 bytes
Primeira tentativa, apenas minimamente golfe. Esta versão funciona (pelo menos no programa de teste) e implementa os códigos de operação extras, mas não mantém os requisitos matemáticos não assinados e não é particularmente rápido. Como um bônus, porém, é uma VM em execução em uma VM e é escrita de tal forma que pode ser interpretada (executada com PUC-Lua) ou classificada como JIT (executada com LuaJIT; ainda interpretada, mas o intérprete está agora JITted).
Edição: Golfed melhor, ainda grande.
EDIT: corrigido um erro grave e agora restringe a aritmética ao
unsigned long
intervalo. De alguma forma, conseguiu manter o tamanho fora de controle, mas ainda está dando a resposta errada.EDIT: Acontece que o resultado estava correto, mas a saída não estava. Mudou para impressão com em
%u
vez de%d
e está tudo bem. Também trocamos os registros baseados em tabela para variáveis para melhorar um pouco o tamanho e a velocidade.EDIT: Usando a
goto
declaração de Lua 5.2 (também disponível em LuaJIT), substituí o interpretador por "JIT-para-Lua", gerando código que é executado diretamente pela própria Lua VM. Não tenho certeza se isso realmente conta como JIT, mas melhora a velocidade.Aqui está a versão original legível.
fonte
<
nos meus loops em vez de<=
, então a instrução final de ramificação foi deixada de lado. Ainda recebe a resposta errada, mas agora leva alguns minutos para fazê-lo. :)C #
15051475 bytesEsta é a minha versão do intérprete, escrita em C #, que poderia ser otimizada / aprimorada, acho, mas eu realmente não sei onde;)
versão golfed:
editar
removeu alguns desnecessários
public
eprivate
modificadores:chame-o com
executable.exe filename
ondefilename
está o arquivo que contém o código a ser interpretadoMeu "programa de teste":
Intérprete ungolfed com nomes mais longos variáveis, classes, ...
fonte