Byte-machine para o forte (e não apenas) no nativo americano (parte 2)

imagem

Vamos continuar os experimentos com o bytecode. Esta é uma continuação do artigo sobre byte-machine no assembler, aqui está a primeira parte .

Em geral, planejei na segunda parte fazer um intérprete de forte e, na terceira, um compilador de forte para essa máquina de bytes. Mas o volume obtido para o artigo foi muito grande. Para criar o intérprete, você precisa expandir o kernel (um conjunto de comandos de bytes) e implementar: variáveis, analisando cadeias, inserindo cadeias, dicionários, pesquisando dicionários ... Bem, pelo menos a saída de números deve funcionar. Como resultado, decidi dividir o artigo sobre o intérprete em dois. Portanto, neste artigo, expandiremos o kernel, determinaremos as variáveis, desenharemos a saída dos números. A seguir, é apresentado um exemplo de plano: a 3ª parte é o intérprete, a 4ª parte é o compilador. E, claro, testes de desempenho. Eles estarão no 4º ou 5º artigo. Estes artigos serão após o ano novo.

E quem ainda não teve medo do terrível montador e bytecode - bem-vindo ao corte! :)

Primeiro, corrija os erros. Vamos definir a extensão do arquivo .s, como é habitual no GAS (obrigado mistergrim ). Em seguida, substitua int 0x80 por syscall e use registradores de 64 bits (obrigado qw1 ). No começo, não li atentamente a descrição da chamada e corrigi apenas os registros ... e obtive uma falha de segmentação. Acontece que tudo mudou para o syscall, incluindo números de chamada. sys_write para syscall é o número 1 e sys_exit é 60. Como resultado, os comandos bad, type e bye assumiram o seguinte formato:

b_bad = 0x00 bcmd_bad: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout mov rsi, offset msg_bad_byte #     mov rdx, msg_bad_byte_len #   syscall #   mov rax, 60 #   № 1 - sys_exit mov rbx, 1 #    1 syscall #   b_bye = 0x01 bcmd_bye: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout mov rsi, offset msg_bye #     mov rdx, msg_bye_len #   syscall #   mov rax, 60 #   № 60 - sys_exit mov rdi, 0 #    0 syscall #   b_type = 0x80 bcmd_type: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout pop rdx pop rsi push r8 syscall #   pop r8 jmp _next 

E mais uma coisa. Com razão, nos comentários do último artigo, berez e fpauk escreveram que, se os endereços do processador forem usados ​​no bytecode, o bytecode depende da plataforma. E nesse exemplo, o endereço da linha para “Olá, mundo!” Foi definido no bytecode por valor (com o comando lit64). Claro, isso não é necessário. Mas essa foi a maneira mais fácil de verificar a máquina de bytes. Não vou mais fazer isso, mas vou obter os endereços das variáveis ​​por outros meios: em particular, com o comando var (mais sobre isso mais tarde).

Aquecer


E agora, como aquecimento, faremos todas as operações aritméticas inteiras básicas (+, -, *, /, mod, / mod, abs). Nós precisaremos deles.

O código é tão simples que eu o trago em um spoiler sem comentar.

Aritmética
 b_add = 0x21 bcmd_add: pop rax add [rsp], rax jmp _next b_sub = 0x22 bcmd_sub: pop rax sub [rsp], rax jmp _next b_mul = 0x23 bcmd_mul: pop rax pop rbx imul rbx push rax jmp _next b_div = 0x24 bcmd_div: pop rbx pop rax cqo idiv rbx push rax jmp _next b_mod = 0x25 bcmd_mod: pop rbx pop rax cqo idiv rbx push rdx jmp _next b_divmod = 0x26 bcmd_divmod: pop rbx pop rax cqo idiv rbx push rdx push rax jmp _next b_abs = 0x27 bcmd_abs: mov rax, [rsp] or rax, rax jge _next neg rax mov [rsp], rax jmp _next 

Tradicionalmente, no forte, operações de precisão dupla são adicionadas às operações aritméticas e de pilha usuais. As palavras para essas operações geralmente começam com um "2": 2DUP, 2SWAP, etc. Mas já temos aritmética padrão de 64 bits e definitivamente não faremos 128 hoje :)

Em seguida, adicionamos as operações básicas da pilha (soltar, trocar, root, -root, over, pick, roll).

Operações de pilha
 b_drop = 0x31 bcmd_drop: add rsp, 8 jmp _next b_swap = 0x32 bcmd_swap: pop rax pop rbx push rax push rbx jmp _next b_rot = 0x33 bcmd_rot: pop rax pop rbx pop rcx push rbx push rax push rcx jmp _next b_mrot = 0x34 bcmd_mrot: pop rcx pop rbx pop rax push rcx push rax push rbx jmp _next b_over = 0x35 bcmd_over: push [rsp + 8] jmp _next b_pick = 0x36 bcmd_pick: pop rcx push [rsp + 8*rcx] jmp _next b_roll = 0x37 bcmd_roll: pop rcx mov rbx, [rsp + 8*rcx] roll1: mov rax, [rsp + 8*rcx - 8] mov [rsp + 8*rcx], rax dec rcx jnz roll1 push rbx jmp _next 

E também faremos comandos para ler e escrever na memória (palavras de Fort @ e!). Bem como suas contrapartes em uma profundidade de bits diferente.

Leitura e gravação na memória
 b_get = 0x40 bcmd_get: pop rcx push [rcx] jmp _next b_set = 0x41 bcmd_set: pop rcx pop rax mov [rcx], rax jmp _next b_get8 = 0x42 bcmd_get8: pop rcx movsx rax, byte ptr [rcx] push rax jmp _next b_set8 = 0x43 bcmd_set8: pop rcx pop rax mov [rcx], al jmp _next b_get16 = 0x44 bcmd_get16: pop rcx movsx rax, word ptr [rcx] push rax jmp _next b_set16 = 0x45 bcmd_set16: pop rcx pop rax mov [rcx], ax jmp _next b_get32 = 0x46 bcmd_get32: pop rcx movsx rax, dword ptr [rcx] push rax jmp _next b_set32 = 0x47 bcmd_set32: pop rcx pop rax mov [rcx], eax jmp _next 

Ainda podemos precisar de equipes de comparação, também as faremos.

Comandos de comparação
 # 0= b_zeq = 0x50 bcmd_zeq: pop rax or rax, rax jnz rfalse rtrue: push -1 jmp _next rfalse: push 0 jmp _next # 0< b_zlt = 0x51 bcmd_zlt: pop rax or rax, rax jl rtrue push 0 jmp _next # 0> b_zgt = 0x52 bcmd_zgt: pop rax or rax, rax jg rtrue push 0 jmp _next # = b_eq = 0x53 bcmd_eq: pop rbx pop rax cmp rax, rbx jz rtrue push 0 jmp _next # < b_lt = 0x54 bcmd_lt: pop rbx pop rax cmp rax, rbx jl rtrue push 0 jmp _next # > b_gt = 0x55 bcmd_gt: pop rbx pop rax cmp rax, rbx jg rtrue push 0 jmp _next # <= b_lteq = 0x56 bcmd_lteq: pop rbx pop rax cmp rax, rbx jle rtrue push 0 jmp _next # >= b_gteq = 0x57 bcmd_gteq: pop rbx pop rax cmp rax, rbx jge rtrue push 0 jmp _next 

Não testaremos operações. O principal é que o assembler não emitiu erros ao compilar. A depuração estará no processo de usá-los.

Faça imediatamente a profundidade da palavra (profundidade da pilha). Para fazer isso, na inicialização, salve os valores iniciais da pilha de dados e da pilha de retorno. Esses valores ainda podem ser úteis ao reiniciar o sistema.

 init_stack: .quad 0 init_rstack: .quad 0 _start: mov rbp, rsp sub rbp, stack_size lea r8, start mov init_stack, rsp mov init_rstack, rbp jmp _next b_depth = 0x38 bcmd_depth: mov rax, init_stack sub rax, rsp shr rax, 3 push rax jmp _next 

Saída de número


Bem, o aquecimento acabou e você precisa suar um pouco. Ensine nosso sistema a produzir números. A palavra "." (ponto). Vamos fazê-lo da maneira que é feito nas implementações padrão do Fort, usando as palavras <#, hold, #, #s, #>, base. Tem que perceber todas essas palavras. Para formar um número, um buffer e um ponteiro para o caractere a ser formado são usados, essas serão as palavras holdbuf e holdpoint.

Então, precisamos destas palavras:

  • holdbuf - buffer para gerar uma representação do número, a formação ocorre a partir do final
  • holdpoint - endereço do último caractere exibido (em holdbuf)
  • <# - o começo da formação de um número; define o ponto de retenção como byte, após o último byte holdbuf
  • hold - reduz o holdpoint em 1 e salva o caractere da pilha no buffer no endereço recebido
  • # - divide a palavra no topo da pilha na base do sistema numérico, converte o restante da divisão em um caractere e salva no buffer usando hold
  • #s - converte a palavra inteira; na verdade chama a palavra # em um loop até que 0 seja deixado na pilha
  • #> - conclusão da conversão; empurra o início da corda formada e seu comprimento para a pilha

Faremos todas as palavras no bytecode, mas primeiro, vamos lidar com as variáveis.

Variáveis


E aqui haverá um pouco de magia fortiana. O fato é que, em um forte, uma variável é uma palavra. Quando essa palavra é executada, o endereço da célula de memória que armazena o valor da variável está na pilha. Você pode ler ou escrever neste endereço. Por exemplo, para escrever o valor 12345 na variável A, é necessário executar os seguintes comandos: "12345 A!". Neste exemplo, 12345 é empurrado para a pilha, a variável A envia seu endereço e a palavra "!" remove dois valores da pilha e grava 12345 no endereço da variável A. Em implementações típicas do forte (com código direto), as variáveis ​​são um comando do microprocessador CALL com o endereço _next, após o qual um local é reservado para armazenar o valor da variável. Ao executar essa palavra, o microprocessador transfere o controle para _next e empurra o endereço de retorno (via RSP) para a pilha. Mas no forte, a pilha de microprocessadores é aritmética e não voltaremos a lugar algum. Como resultado disso, a execução continua e na pilha está o endereço da variável. E tudo isso com uma equipe de processadores! No assembler, ficaria assim:

  call _next #   _next,      ,   12345 .quad 12345 

Mas nós temos um bytecode e não podemos usar esse mecanismo! Eu não descobri imediatamente como criar esse mecanismo no bytecode. Mas, se você pensar logicamente, isso não interfere na implementação de algo muito semelhante. Lembre-se de que este não será um comando do processador, mas um bytecode, mais precisamente, uma "sub-rotina" em um bytecode. Aqui está a declaração do problema:

  • este é um código de bytes, ao transferir o controle para o qual ele deve retornar imediatamente
  • após o retorno, o endereço em que o valor da variável está armazenado deve permanecer na pilha aritmética

Temos um comando de saída byte. Vamos fazer uma palavra no bytecode contendo um único comando de saída. Então este comando retornará dele. Resta fazer o mesmo comando, que empurra adicionalmente o endereço do próximo byte na pilha (registrador R8). Faremos isso como um ponto de entrada adicional para sair e economizar na transição:

 b_var0 = 0x28 bcmd_var0: push r8 b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] 

Agora a variável base ficará assim:
 base: .byte b_var0 .quad 10 

A propósito, por que exatamente var0, e não apenas var? O fato é que haverá outros comandos para identificar palavras mais avançadas que contenham dados. Descreverei com mais detalhes nos seguintes artigos.

Agora estamos prontos para desenhar números. Vamos começar!

Palavras base, holdbuf, holdpoint


Como as variáveis ​​serão organizadas já foram decididas. Portanto, as palavras base, holdbuf, holdpoint são obtidas da seguinte maneira:

 base: .byte b_var0 .quad 10 holdbuf_len = 70 holdbuf: .byte b_var0 .space holdbuf_len holdpoint: .byte b_var0 .quad 0 

O tamanho do buffer holdbuf é selecionado 70. O número máximo de bits de um número é 64 (isto é, se você selecionar um sistema binário). Outra reserva de vários caracteres foi feita para colocar, por exemplo, o sinal de um número e um espaço depois dele. Vamos verificar o estouro do buffer, mas por enquanto não colocaremos caracteres extras no buffer. Então será possível fazer outro diagnóstico.

segurar


Agora você pode fazer a palavra valer. No forte, seu código fica assim:

 : hold holdpoint @ 1- dup holdbuf > if drop drop else dup holdpoint ! c! then ; 

Para quem vê o forte pela primeira vez, analisarei o código em detalhes. Para as próximas palavras, não farei isso.

No início, há uma palavra para definição de novas palavras e um nome para uma nova palavra: ": segure". Depois disso, vem o código que termina com a palavra ";". Vamos analisar o código da palavra. Vou dar o comando e o estado da pilha depois de executar o comando. Antes da palavra ser chamada, existe um código de caractere na pilha, que é colocado no buffer (indicado por <caractere>). Além disso, acontece assim:

 holdpoint <> <  holdpoint> @ <> <  holdpoint> 1- <> <  holdpoint  1> dup <> <  holdpoint  1> <  holdpoint  1> holdbuf <> <  holdpoint  1> <  holdpoint  1> <  holdbuf> > <> <  holdpoint  1> <,    holdpoint  1    holdbuf> 

Depois disso, é o comando if, que é compilado em um salto condicional para uma sequência de comandos entre else e then. Uma ramificação condicional remove o resultado da comparação da pilha e executa a transição se houver uma mentira na pilha. Se não houve transição, o ramo entre if e else é executado, no qual existem dois comandos de remoção que removem o caractere e o endereço. Caso contrário, a execução continuará. A palavra "!" salva o novo valor no ponto de espera (o endereço e o valor são removidos da pilha). E a palavra "c!" escreve um caractere no buffer, este é o comando set8 byte (o endereço e o valor do caractere são removidos da pilha).

 dup <> <  holdpoint  1> <  holdpoint  1> holdpoint <> <  holdpoint  1> <  holdpoint  1> <  holdpoint> ! <> <  holdpoint  1> c! ,  ,   ! :) 

Aqui está quantas ações essa curta sequência de comandos faz! Sim, o forte é conciso. E agora ativamos o manual “compilador” na cabeça :) E compilamos tudo isso no bytecode:
 hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_get # @ .byte b_wm # 1- .byte b_dup # dup .byte b_call8 .byte holdbuf - . - 1 # holdbuf .byte b_gt # > .byte b_qbranch8 # if .byte 0f - . .byte b_drop # drop .byte b_drop # drop .byte b_branch8 #     ( then) .byte 1f - . 0: .byte b_dup # dup .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_set # ! .byte b_set8 # c! 1: .byte b_exit # ; 

Aqui eu usei etiquetas locais (0 e 1). Esses rótulos podem ser acessados ​​por nomes especiais. Por exemplo, o rótulo 0 pode ser acessado pelos nomes 0f ou 0b. Isso significa um link para o rótulo 0 mais próximo (para frente ou para trás). Bastante conveniente para etiquetas usadas localmente, para não aparecer nomes diferentes.

Palavra #


Vamos fazer a palavra #. No forte, seu código ficará assim:

 : # base /mod swap dup 10 < if c″ 0 + else 10 - c″ A + then hold ; 

A condição aqui é usada para verificar: o valor recebido é menor que dez? Se menor, os dígitos de 0 a 9 são usados, caso contrário, são usados ​​caracteres começando com "A". Isso permitirá trabalhar com um sistema numérico hexadecimal. A sequência c ″ 0 empurra o código de caractere 0. para a pilha. Ativamos o “compilador”:

 conv: .byte b_call16 .word base - . - 2 # base .byte b_get # @ .byte b_divmod # /mod .byte b_swap # swap .byte b_dup # dup .byte b_lit8 .byte 10 # 10 .byte b_lt # < .byte b_qnbranch8 # if .byte 0f - . .byte b_lit8 .byte '0' # c″ 0 .byte b_add # + .byte b_branch8 # else .byte 1f - . 0: .byte b_lit8 .byte 'A' # c″ A .byte b_add # + 1: .byte b_call16 .word hold - . - 2 # hold .byte b_exit # ; 

Palavra <#


A palavra <# é muito simples:

 : <# holdbuf 70 + holdpoint ! ; 

Bytecode:

 conv_start: .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_call16 .word holdpoint - . - 2 .byte b_set .byte b_exit 

Palavra #>


A palavra #> para concluir a conversão é assim:

 : #> holdpoint @ holdbuf 70 + over - ; 

Bytecode:

 conv_end: .byte b_call16 .word holdpoint - . - 2 .byte b_get .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_over .byte b_sub .byte b_exit 

Word #


E, finalmente, a palavra #s:

 : #s do # dup 0= until ; 

Bytecode:

 conv_s: .byte b_call8 .byte conv - . - 1 .byte b_dup .byte b_qbranch8 .byte conv_s - . .byte b_exit 

Qualquer pessoa cuidadosa notará uma pequena discrepância entre o bytecode e o código forte :)

Está tudo pronto


Agora, nada o impedirá de criar a palavra ".", Que exibe um número:

 : . <# #s drop #> type ; 

Bytecode:

 dot: .byte b_call8 .byte conv_start - . - 1 .byte b_call8 .byte conv_s - . - 1 .byte b_drop .byte b_call8 .byte conv_end - . - 1 .byte b_type .byte b_exit 

Vamos fazer um bytecode de teste que verifique nosso ponto:

 start: .byte b_lit16 .word 1234 .byte b_call16 .word dot - . - 2 .byte b_bye 

Claro, não funcionou de uma só vez. Mas, após a depuração, o seguinte resultado foi obtido:

 $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth 1234bye! 

O batente é visível imediatamente. Após o número, o forte deve exibir um espaço. Adicione após o conv_start (<#) chamar o comando 32 hold.

Também tiramos uma conclusão do sinal. No início, adicione dup abs e, no final, verifique o sinal da cópia esquerda e coloque o sinal de menos se o número for negativo (0 <se c ″ - mantenha pressionado). Como resultado, a palavra "." assume este formulário:

 : . dup abs <# 32 hold #s drop #> 0< if c″ - hold then type ; 

Bytecode:

 dot: .byte b_dup .byte b_abs .byte b_call8 .byte conv_start - . - 1 .byte b_lit8 .byte ' ' .byte b_call16 .word hold - . - 2 .byte b_call8 .byte conv_s - . - 1 .byte b_drop .byte b_zlt .byte b_qnbranch8 .byte 1f - . .byte b_lit8 .byte '-' .byte b_call16 .word hold - . - 2 1: .byte b_call8 .byte conv_end - . - 1 .byte b_type .byte b_exit 

Na sequência inicial dos comandos de byte, coloque um número negativo e verifique:

 $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth -1234 bye! 

Há uma conclusão de números!

Fonte completa
 .intel_syntax noprefix stack_size = 1024 .section .data init_stack: .quad 0 init_rstack: .quad 0 msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte #  len    msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_call8, bcmd_call16, bcmd_call32, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_qnbranch8, bcmd_qnbranch16,bcmd_bad, bcmd_exit # 0x10 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_add, bcmd_sub, bcmd_mul, bcmd_div, bcmd_mod, bcmd_divmod, bcmd_abs # 0x20 .quad bcmd_var0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_dup, bcmd_drop, bcmd_swap, bcmd_rot, bcmd_mrot, bcmd_over, bcmd_pick, bcmd_roll # 0x30 .quad bcmd_depth, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_get, bcmd_set, bcmd_get8, bcmd_set8, bcmd_get16, bcmd_set16, bcmd_get32, bcmd_set32 # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_zeq, bcmd_zlt, bcmd_zgt, bcmd_eq, bcmd_lt, bcmd_gt, bcmd_lteq, bcmd_gteq #0x50 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit16 .word -1234 .byte b_call16 .word dot - . - 2 .byte b_bye base: .byte b_var0 .quad 10 holdbuf_len = 70 holdbuf: .byte b_var0 .space holdbuf_len holdpoint: .byte b_var0 .quad 0 # : hold holdpoint @ 1- dup holdbuf > if drop drop else dup holdpoint ! c! then ; hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_get # @ .byte b_wm # 1- .byte b_dup # dup .byte b_call8 .byte holdbuf - . - 1 # holdbuf .byte b_gt # > .byte b_qbranch8 # if .byte 0f - . .byte b_drop # drop .byte b_drop # drop .byte b_branch8 #     ( then) .byte 1f - . 0: .byte b_dup # dup .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_set # ! .byte b_set8 # c! 1: .byte b_exit # ; # : # base /mod swap dup 10 < if c" 0 + else 10 - c" A + then hold ; conv: .byte b_call16 .word base - . - 2 # base .byte b_get # @ .byte b_divmod # /mod .byte b_swap # swap .byte b_dup # dup .byte b_lit8 .byte 10 # 10 .byte b_lt # < .byte b_qnbranch8 # if .byte 0f - . .byte b_lit8 .byte '0' # c" 0 .byte b_add # + .byte b_branch8 # else .byte 1f - . 0: .byte b_lit8 .byte '?' # c" A .byte b_add # + 1: .byte b_call16 .word hold - . - 2 # hold .byte b_exit # ; # : <# holdbuf 70 + holdpoint ! ; conv_start: .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_call16 .word holdpoint - . - 2 .byte b_set .byte b_exit # : #s do # dup 0=until ; conv_s: .byte b_call8 .byte conv - . - 1 .byte b_dup .byte b_qbranch8 .byte conv_s - . .byte b_exit # : #> holdpoint @ holdbuf 70 + over - ; conv_end: .byte b_call16 .word holdpoint - . - 2 .byte b_get .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_over .byte b_sub .byte b_exit dot: .byte b_dup .byte b_abs .byte b_call8 .byte conv_start - . - 1 .byte b_lit8 .byte ' ' .byte b_call16 .word hold - . - 2 .byte b_call8 .byte conv_s - . - 1 .byte b_drop .byte b_zlt .byte b_qnbranch8 .byte 1f - . .byte b_lit8 .byte '-' .byte b_call16 .word hold - . - 2 1: .byte b_call8 .byte conv_end - . - 1 .byte b_type .byte b_exit .section .text .global _start #     _start: mov rbp, rsp sub rbp, stack_size lea r8, start mov init_stack, rsp mov init_rstack, rbp jmp _next b_var0 = 0x28 bcmd_var0: push r8 b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_dup = 0x30 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_add = 0x21 bcmd_add: pop rax add [rsp], rax jmp _next b_sub = 0x22 bcmd_sub: pop rax sub [rsp], rax jmp _next b_mul = 0x23 bcmd_mul: pop rax pop rbx imul rbx push rax jmp _next b_div = 0x24 bcmd_div: pop rbx pop rax cqo idiv rbx push rax jmp _next b_mod = 0x25 bcmd_mod: pop rbx pop rax cqo idiv rbx push rdx jmp _next b_divmod = 0x26 bcmd_divmod: pop rbx pop rax cqo idiv rbx push rdx push rax jmp _next b_abs = 0x27 bcmd_abs: mov rax, [rsp] or rax, rax jge _next neg rax mov [rsp], rax jmp _next b_drop = 0x31 bcmd_drop: add rsp, 8 jmp _next b_swap = 0x32 bcmd_swap: pop rax pop rbx push rax push rbx jmp _next b_rot = 0x33 bcmd_rot: pop rax pop rbx pop rcx push rbx push rax push rcx jmp _next b_mrot = 0x34 bcmd_mrot: pop rcx pop rbx pop rax push rcx push rax push rbx jmp _next b_over = 0x35 bcmd_over: push [rsp + 8] jmp _next b_pick = 0x36 bcmd_pick: pop rcx push [rsp + 8*rcx] jmp _next b_roll = 0x37 bcmd_roll: pop rcx mov rbx, [rsp + 8*rcx] roll1: mov rax, [rsp + 8*rcx - 8] mov [rsp + 8*rcx], rax dec rcx jnz roll1 push rbx jmp _next b_depth = 0x38 bcmd_depth: mov rax, init_stack sub rax, rsp shr rax, 3 push rax jmp _next b_get = 0x40 bcmd_get: pop rcx push [rcx] jmp _next b_set = 0x41 bcmd_set: pop rcx pop rax mov [rcx], rax jmp _next b_get8 = 0x42 bcmd_get8: pop rcx movsx rax, byte ptr [rcx] push rax jmp _next b_set8 = 0x43 bcmd_set8: pop rcx pop rax mov [rcx], al jmp _next b_get16 = 0x44 bcmd_get16: pop rcx movsx rax, word ptr [rcx] push rax jmp _next b_set16 = 0x45 bcmd_set16: pop rcx pop rax mov [rcx], ax jmp _next b_get32 = 0x46 bcmd_get32: pop rcx movsx rax, dword ptr [rcx] push rax jmp _next b_set32 = 0x47 bcmd_set32: pop rcx pop rax mov [rcx], eax jmp _next # 0= b_zeq = 0x50 bcmd_zeq: pop rax or rax, rax jnz rfalse rtrue: push -1 jmp _next rfalse: push 0 jmp _next # 0< b_zlt = 0x51 bcmd_zlt: pop rax or rax, rax jl rtrue push 0 jmp _next # 0> b_zgt = 0x52 bcmd_zgt: pop rax or rax, rax jg rtrue push 0 jmp _next # = b_eq = 0x53 bcmd_eq: pop rbx pop rax cmp rax, rbx jz rtrue push 0 jmp _next # < b_lt = 0x54 bcmd_lt: pop rbx pop rax cmp rax, rbx jl rtrue push 0 jmp _next # > b_gt = 0x55 bcmd_gt: pop rbx pop rax cmp rax, rbx jg rtrue push 0 jmp _next # <= b_lteq = 0x56 bcmd_lteq: pop rbx pop rax cmp rax, rbx jle rtrue push 0 jmp _next # >= b_gteq = 0x57 bcmd_gteq: pop rbx pop rax cmp rax, rbx jge rtrue push 0 jmp _next b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next b_qnbranch8 = 0x14 bcmd_qnbranch8: pop rax or rax, rax jz bcmd_branch8 inc r8 jmp _next b_qnbranch16 = 0x15 bcmd_qnbranch16:pop rax or rax, rax jz bcmd_branch16 add r8, 2 jmp _next b_bad = 0x00 bcmd_bad: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 — stdout mov rsi, offset msg_bad_byte #     mov rdx, msg_bad_byte_len #   syscall #   mov rax, 60 #   № 1 - sys_exit mov rbx, 1 #    1 syscall #   b_bye = 0x01 bcmd_bye: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 — stdout mov rsi, offset msg_bye #     mov rdx, msg_bye_len #   syscall #   mov rax, 60 #   № 60 - sys_exit mov rdi, 0 #    0 syscall #   b_type = 0x80 bcmd_type: mov rax, 1 #   № 1 - sys_write mov rdi, 1 #  № 1 - stdout pop rdx pop rsi push r8 syscall #   pop r8 jmp _next 

Sumário


Agora temos um núcleo decente de comandos de bytes: todas as operações aritméticas básicas, operações de pilha, operações de comparação, trabalho com memória, variáveis. Além disso, já existe a saída de números, totalmente implementada no bytecode. Está tudo pronto para o intérprete, o que faremos no próximo artigo!

Feliz Ano Novo a todos!

Críticas são bem-vindas! :)

Continuação: Byte-machine para o forte (e não apenas) no nativo americano (parte 3)

Source: https://habr.com/ru/post/pt433836/


All Articles