Byte-machine para el fuerte (y no solo) en los nativos americanos (parte 2)

imagen

Continuemos los experimentos con bytecode. Esta es una continuación del artículo sobre byte-machine en ensamblador, aquí está la primera parte .

En general, planeé en la segunda parte hacer un intérprete de fort, y en la tercera, un compilador de fort para esta máquina de bytes. Pero el volumen que se obtuvo para el artículo fue muy grande. Para hacer el intérprete, necesita expandir el núcleo (un conjunto de comandos de bytes) e implementar: variables, analizar cadenas, ingresar cadenas, diccionarios, buscar diccionarios ... Bueno, al menos la salida de números debería funcionar. Como resultado, decidí dividir el artículo sobre el intérprete en dos. Por lo tanto, en este artículo expandiremos el núcleo, determinaremos las variables, dibujaremos la salida de números. El siguiente es un plan de ejemplo: la tercera parte es el intérprete, la cuarta parte es el compilador. Y, por supuesto, pruebas de rendimiento. Estarán en el cuarto o quinto artículo. Estos artículos serán posteriores al año nuevo.

Y quién aún no ha tenido miedo del terrible ensamblador y código de bytes: ¡bienvenido al corte! :)

Primero, corrige los errores. Configuremos la extensión de archivo .s, como es habitual en GAS (gracias mistergrim ). Luego, reemplace int 0x80 con syscall y use registros de 64 bits (gracias qw1 ). Al principio, no leí cuidadosamente la descripción de la llamada y solo corregí los registros ... y obtuve una falla de segmentación. Resulta que todo ha cambiado para syscall, incluidos los números de llamada. sys_write para syscall es el número 1 y sys_exit es 60. Como resultado, los comandos bad, type y bye tomaron la siguiente forma:

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 

Y una cosa más. Muy acertadamente, en los comentarios al último artículo, berez y fpauk escribieron que si las direcciones del procesador se usan en el código de bytes , entonces el código de bytes depende de la plataforma. Y en ese ejemplo, la dirección de línea para "¡Hola, mundo!" Se estableció en bytecode por valor (con el comando lit64). Por supuesto, esto no es necesario. Pero esa era la forma más fácil de verificar la máquina de bytes. Ya no haré esto, pero obtendré las direcciones de las variables por otros medios: en particular, con el comando var (más sobre eso más adelante).

Calentar


Y ahora, como calentamiento, haremos todas las operaciones aritméticas de enteros básicos (+, -, *, /, mod, / mod, abs). Los necesitaremos

El código es tan simple que lo traigo en un spoiler sin comentarios.

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, en fort, las operaciones de doble precisión se agregan a las operaciones aritméticas y de apilamiento habituales. Las palabras para tales operaciones generalmente comienzan con un "2": 2DUP, 2SWAP, etc. Pero ya tenemos aritmética estándar de 64 bits, y definitivamente no haremos 128 hoy :)

A continuación, agregamos las operaciones básicas de la pila (soltar, intercambiar, root, -root, over, pick, roll).

Operaciones de apilamiento
 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 

Y también haremos comandos para leer y escribir en la memoria (palabras de Fort @ y!). Y también sus contrapartes a una profundidad de bits diferente.

Leer y escribir en la memoria
 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 

Es posible que aún necesitemos equipos de comparación, también los haremos.

Comandos de comparación
 # 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 

No probaremos las operaciones. Lo principal es que el ensamblador no daría errores al compilar. La depuración estará en proceso de usarlos.

Inmediatamente haga la palabra profundidad (profundidad de pila). Para hacer esto, al inicio, guarde los valores iniciales de la pila de datos y la pila de retorno. Estos valores pueden ser útiles al reiniciar el 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 

Salida de número


Bueno, el calentamiento ha terminado y hay que sudar un poco. Enseñe a nuestro sistema a generar números. La palabra "." Se utiliza para mostrar números en el fuerte. (punto) Hagámoslo como se hace en implementaciones estándar de Fort, usando las palabras <#, hold, #, #s, #>, base. Hay que darse cuenta de todas estas palabras. Para formar un número, se utilizan un búfer y un puntero al carácter a formar, estas serán las palabras holdbuf y holdpoint.

Entonces, necesitamos estas palabras:

  • holdbuf - buffer para generar una representación del número, la formación se produce desde el final
  • punto de espera : dirección del último carácter mostrado (en holdbuf)
  • <# - el comienzo de la formación de un número; establece el punto de espera en byte, después del último byte holdbuf
  • retención : reduce el punto de retención en 1 y guarda el carácter de la pila en el búfer en la dirección recibida
  • # : divide la palabra en la parte superior de la pila en la base del sistema de números, traduce el resto de la división en un carácter y lo guarda en el búfer usando la retención
  • #s : convierte la palabra completa; en realidad llama a la palabra # en un bucle hasta que se deja 0 en la pila
  • #> - finalización de la conversión; empuja el comienzo de la cadena formada y su longitud en la pila

Haremos todas las palabras en bytecode, pero primero, tratemos con las variables.

Variables


Y aquí habrá un poco de magia fortiana. El hecho es que en un fuerte una variable es una palabra. Cuando se ejecuta esta palabra, la dirección de la celda de memoria que almacena el valor de la variable está en la pila. Puedes leer o escribir a esta dirección. Por ejemplo, para escribir el valor 12345 en la variable A, debe ejecutar los siguientes comandos: "12345 A!". En este ejemplo, 12345 se inserta en la pila, luego la variable A empuja su dirección y la palabra "!" elimina dos valores de la pila y escribe 12345 en la dirección de la variable A. En las implementaciones típicas de fort (con código directo), las variables son un comando CALL del microprocesador con la dirección _next, después de lo cual se reserva un lugar para almacenar el valor de la variable. Al ejecutar dicha palabra, el microprocesador transfiere el control a _next y empuja la dirección de retorno (a través de RSP) a la pila. Pero en el fuerte, la pila de microprocesador es aritmética, y no volveremos a ningún lado. Como resultado de esto, la ejecución continúa, y en la pila está la dirección de la variable. ¡Y todo esto con un equipo de procesador! En ensamblador, se vería así:

  call _next #   _next,      ,   12345 .quad 12345 

¡Pero tenemos un código de bytes y no podemos usar este mecanismo! No descubrí de inmediato cómo hacer tal mecanismo en bytecode. Pero, si piensas lógicamente, eso no interfiere con la implementación de algo muy similar. Solo tenga en cuenta que esto no será un comando de procesador, sino un código de bytes, más precisamente, una "subrutina" en un código de bytes. Aquí está la declaración del problema:

  • Este es un código de bytes, cuando se transfiere el control al que debe regresar inmediatamente
  • después del retorno, la dirección donde se almacena el valor de la variable debe permanecer en la pila aritmética

Tenemos un comando de byte de salida. Hagamos una palabra en el código de bytes que contenga un solo comando de salida. Entonces este comando volverá de él. Queda por hacer el mismo comando, que además empuja la dirección del siguiente byte en la pila (registro R8). Haremos esto como un punto de entrada adicional para salir y guardar en la transición:

 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] 

Ahora la variable base se verá así:
 base: .byte b_var0 .quad 10 

Por cierto, ¿por qué exactamente var0, y no solo var? El hecho es que habrá otros comandos para identificar palabras más avanzadas que contengan datos. Lo describiré con más detalle en los siguientes artículos.

Ahora estamos listos para dibujar números. ¡Empecemos!

Palabras base, holdbuf, holdpoint


La forma en que se organizarán las variables ya se ha decidido. Por lo tanto, las palabras base, holdbuf, holdpoint se obtienen de la siguiente manera:

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

El tamaño del búfer holdbuf se selecciona 70. El número máximo de bits de un número es 64 (esto es si selecciona un sistema binario). Se ha hecho otra reserva de varios caracteres para colocar, por ejemplo, el signo de un número y un espacio después. Comprobaremos el desbordamiento del búfer, pero por ahora no pondremos caracteres adicionales en el búfer. Entonces será posible hacer otro diagnóstico.

mantener


Ahora puedes hacer que la palabra se mantenga. En el fuerte, su código se ve así:

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

Para aquellos que ven el fuerte por primera vez, analizaré el código en detalle. Para las siguientes palabras no haré esto.

Al principio hay una palabra para la definición de nuevas palabras y un nombre para una nueva palabra: ": hold". Después de eso viene el código que termina con la palabra ";". Analicemos el código de la palabra. Daré el comando y el estado de la pila después de ejecutar el comando. Antes de llamar a la palabra, hay un código de caracteres en la pila que se coloca en el búfer (indicado por <carácter>). Además resulta así:

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

Después de eso está el comando if, que se compila en un salto condicional a una secuencia de comandos entre else y luego. Una rama condicional elimina el resultado de la comparación de la pila y realiza la transición si hubo una mentira en la pila. Si no hubo transición, se ejecuta la rama entre if y else, en la que hay dos comandos de eliminación que eliminan el carácter y la dirección. De lo contrario, la ejecución continúa. La palabra "!" guarda el nuevo valor en punto de espera (la dirección y el valor se eliminan de la pila). Y la palabra "c!" escribe un carácter en el búfer, este es el comando set8 byte (la dirección y el valor del carácter se eliminan de la pila).

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

¡Aquí se muestran cuántas acciones realiza esta breve secuencia de comandos! Sí, el fuerte es conciso. Y ahora activamos el "compilador" manual en la cabeza :) Y compilamos todo esto en 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 # ; 

Aquí usé etiquetas locales (0 y 1). Se puede acceder a estas etiquetas con nombres especiales. Por ejemplo, se puede acceder a la etiqueta 0 con los nombres 0f o 0b. Esto significa un enlace a la etiqueta más cercana 0 (hacia adelante o hacia atrás). Muy conveniente para las etiquetas que se usan localmente, para no tener nombres diferentes.

Palabra #


Hagamos la palabra #. En el fuerte, su código se verá así:

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

La condición aquí se usa para verificar: ¿la cifra recibida es inferior a diez? Si es menor, se usan los dígitos 0–9, de lo contrario, se usan caracteres que comienzan con “A”. Esto permitirá trabajar con un sistema de números hexadecimales. La secuencia c ″ 0 empuja el código de caracteres 0 en la pila. Activamos el "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 # ; 

Palabra <#


La palabra <# es muy simple:

 : <# 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 

Palabra #>


La palabra #> para completar la conversión se ve así:

 : #> 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 

Palabra #s


Y finalmente, la palabra #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 

Cualquiera que tenga cuidado notará una ligera discrepancia entre el código de bytes y el código de fortaleza :)

Todo esta listo


Ahora, nada le impedirá hacer la palabra ".", Que muestra un 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 

Hagamos un código de bytes de prueba que verifique nuestro punto:

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

Por supuesto, no funcionó de una vez. Pero, después de la depuración, se obtuvo el siguiente resultado:

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

La jamba es visible de inmediato. Después del número, el fuerte debería mostrar un espacio. Agregue después de conv_start (<#) llamar al comando 32 hold.

También sacamos una conclusión del signo. Al principio, agregue dup abs, y al final, verifique el signo de la copia izquierda y ponga el signo menos si el número es negativo (0 <si c ″ - mantenga presionado). Como resultado, la palabra "." toma esta forma:

 : . 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 

En la secuencia inicial de los comandos de bytes, ingrese un número negativo y verifique:

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

¡Hay una conclusión de números!

Fuente 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 

Resumen


Ahora tenemos un núcleo bastante decente de comandos de bytes: todas las operaciones aritméticas básicas, operaciones de pila, operaciones de comparación, trabajo con memoria, variables. Además, ya existe la salida de números, totalmente implementada en bytecode. ¡Todo está listo para que lo haga el intérprete, que es lo que haremos en el próximo artículo!

Feliz año nuevo a todos!

¡La crítica es bienvenida! :)

Continuación: Byte-máquina para el fuerte (y no solo) en nativos americanos (parte 3)

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


All Articles