
Ha llegado el año 2019. Las vacaciones de año nuevo están llegando a su fin. Es hora de comenzar a recordar bytes, comandos, variables, bucles ...
Algo que ya he olvidado con estas vacaciones. Hay que recordar juntos!
Hoy haremos un intérprete para nuestra máquina de bytes. Este es el tercer artículo, las primeras partes están aquí:
parte 1 ,
parte 2 .
¡Feliz año nuevo a todos y bienvenidos al corte!
Para empezar, responderé preguntas de
fpauk . Estas preguntas son absolutamente correctas. Ahora la arquitectura de esta máquina de bytes es tal que trabajamos con direcciones de procesador directo. Pero en el código de bytes estas direcciones no están, se forman después del inicio del sistema. Una vez que se inicia el sistema, podemos crear cualquier puntero, y este código funcionará correctamente en cualquier plataforma. Por ejemplo, la dirección de una variable o matriz se puede obtener con el comando var0. Este comando funcionará en cualquier plataforma y devolverá la dirección correcta específica de esta plataforma. Entonces puedes trabajar con esta dirección como quieras.
Pero aún así,
fpauk tiene razón. La dirección no se puede almacenar en bytecode. Resulta que podemos escribir código independiente de la plataforma, pero para esto debemos hacer algunos esfuerzos. En particular, asegúrese de que las direcciones no estén en el código de bytes. Y pueden ingresar, por ejemplo, si guarda el código compilado en un archivo. Contendrá datos y puede ser direcciones. Por ejemplo, los valores de las variables aquí, contexto y otros.
Para deshacerse de tal problema, debe hacer que las direcciones sean virtuales. El direccionamiento del procesador x86 es bastante potente y, en la mayoría de los casos, ni siquiera agregará comandos adicionales. Pero aún así, continuaré en la arquitectura actual, con direcciones absolutas. Y luego, cuando lleguemos a las pruebas, será posible rehacer las direcciones en virtuales, y ver cómo esto afectará el rendimiento. Esto es interesante
Calentar
Y ahora un poco de ejercicio. Hagamos otra porción de comandos de bytes pequeños pero útiles. Estos serán los comandos nip, emit, 1+, +!, -!, Count, palabras de trabajo con la pila de retorno r>,> r, r @, un literal de cadena (") y palabras constantes 1, 2, 3, 4, 8. No olvide incluirlos en la tabla de comandos.
Aquí está el código para estos comandosb_nip = 0x39 bcmd_nip: pop rax mov [rsp], rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf # mov [rsi], al mov rax, 1 # № 1 - sys_write mov rdi, 1 # № 1 - stdout mov rdx, 1 # push r8 syscall # pop r8 jmp _next b_wp = 0x18 bcmd_wp: incq [rsp] jmp _next b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 jmp _next
El comando nip elimina la palabra debajo de la parte superior de la pila. Es equivalente a intercambiar comandos de soltar. Esto a veces puede ser útil.
El comando emitir empuja a un personaje de la pila. Utiliza la misma llamada al sistema número 1, el carácter se coloca en un búfer con una longitud de 1.
El comando de conteo es muy simple: toma la dirección de la línea con el contador de la pila y la convierte en dos valores: la dirección de la línea sin el contador y la longitud.
Los comandos b_2r, b_r2, b_rget son las palabras Fort r>,> r, r @. El primero toma la palabra de la pila de retorno y la coloca en la pila aritmética. El segundo lleva a cabo la operación opuesta. El tercero copia la palabra de la pila de retorno, la coloca en la aritmética, la pila de retorno no cambia.
Los comandos b_setp y b_setm son las palabras +! y -! .. Toman el valor y la dirección de la pila, y modifican la palabra en la dirección especificada, agregando o eliminando el valor de la pila.
El comando b_str tiene un parámetro de longitud arbitraria: una línea con un contador. Esta línea está en el bytecode después del byte de comando, y el comando simplemente empuja la dirección de esta línea en la pila. De hecho, este es un literal de cadena.
El resto del equipo, creo, no necesita comentarios.
También haremos un comando para imprimir una cadena constante (. "). Lo implementaremos como un punto de entrada para escribir, de la siguiente manera:
b_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push rax push r8 add r8, rax 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
Este comando está estructurado de manera similar a b_str. Solo que ella no pone nada en la pila. La línea ubicada detrás de este comando como parámetro simplemente se muestra al usuario.
El calentamiento ha terminado, ha llegado el momento de algo más serio. Tratemos con los generadores de palabras y otros comandos var.
Palabras generadoras
Recordemos las variables. Sabemos cómo están organizados a nivel de bytecode (comando var0). Para crear una nueva variable, el fuerte utiliza la siguiente construcción:
variable < >
Después de realizar esta secuencia, se crea una nueva palabra <nombre de variable>. La ejecución de esta nueva palabra empuja la dirección en la pila para almacenar el valor de la variable. También hay constantes en el fuerte, se crean así:
<> constant < >
Después de crear la constante, la ejecución de la palabra <nombre constante> se coloca en la pila <valor>.
Entonces, tanto la palabra variable como la palabra constante son palabras generadoras. Están diseñados para crear nuevas palabras. En un fuerte, tales palabras se describen usando create ... does> construct.
Las variables y las constantes se pueden definir de la siguiente manera:
: variable create 0 , does> ; : constant create , does> @ ;
¿Qué significa todo esto?
La palabra crear, cuando se ejecuta, crea una nueva palabra con el nombre que tomará cuando se ejecute desde la secuencia de entrada. Después de la creación, se ejecuta una secuencia de palabras antes de que la palabra lo haga>. Pero en el momento de la ejecución de esta palabra, lo que se escribe después de> se ejecuta. Al mismo tiempo, la dirección de datos ya estará en la pila (como se dice en el fuerte, "campos de datos").
Por lo tanto, cuando se crea una variable, se ejecuta la secuencia "0": esta es la reserva de una palabra de máquina con relleno cero. Y cuando se ejecuta la palabra creada, no se hace nada (después de hacer> no hay nada). La dirección de memoria donde se almacena el valor simplemente permanece en la pila.
En la definición de una constante, se reserva una palabra con un valor que se llena en la pila. Cuando se ejecuta la palabra creada, se ejecuta "@", que recupera el valor en la dirección especificada.
Ahora pensemos en cómo se puede organizar la palabra que creamos. Empuja la dirección de datos en la pila (como var0) y luego transfiere el control a una dirección específica, bytecode. El comando var0 vuelve inmediatamente. Pero en este caso, necesitamos hacer no un retorno, sino, de hecho, una transición.
Una vez más, formularé lo que hay que hacer:
- poner la dirección de datos en la pila
- saltar a un fragmento de código después de>
Resulta que solo necesita transferir el control a otra dirección de bytecode, pero primero coloque la dirección del siguiente byte (R8) en la pila.
¡Es casi un comando de rama! Y aquí ella no está sola. Ya tenemos branch8 y branch16. Nombraremos los nuevos comandos var8 y var16, y dejaremos que estos sean solo los puntos de entrada a los comandos de bifurcación. Ahorramos en la transición al equipo de transición :) Entonces, será así:
b_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next
En el buen sentido, el comando var32 seguirá funcionando, y var64 también. No tenemos transiciones tan largas, ya que las transiciones ordinarias no son tan largas. Pero para el comando var, este es un caso muy realista. Pero por ahora, no haremos estos comandos. Lo haremos más tarde, si es necesario.
Con los generadores de palabras resueltos. Era el turno de decidir sobre el diccionario.
Vocabulario
Por lo general, cuando hablan de manera simplista sobre el diccionario fort, se presenta en forma de una lista unidireccional de entradas de diccionario. De hecho, todo es un poco más complicado, ya que el fuerte admite muchos diccionarios. De hecho, son un árbol. La búsqueda de una palabra en dicho árbol comienza con una "hoja": esta es la última palabra en el diccionario actual. El diccionario actual está definido por la variable de contexto, y la dirección de la última palabra está en la palabra del diccionario. Se usa otra variable para administrar los diccionarios: define un diccionario donde se agregarán nuevas palabras. Por lo tanto, se puede instalar un diccionario para una búsqueda y otro para incluir palabras nuevas.
Para nuestro caso simple, sería posible no hacer el soporte de muchos diccionarios, pero decidí no simplificar nada. De hecho, para comprender el código de bytes, la máquina de bytes, no es necesario saber lo que se describe en esta sección. Por lo tanto, a quienes no les interese, simplemente pueden omitir esta sección. Bueno, quién quiere saber los detalles, ¡adelante!
Inicialmente, hay un diccionario básico llamado adelante. Esto significa que existe tal palabra. Esta palabra también se llama "diccionario", hay cierta confusión. Por lo tanto, cuando se trata de una palabra, la llamaré una palabra del diccionario.
Se crean nuevos diccionarios usando esta construcción:
vocabulary < >
Esto crea una palabra con el nombre <nombre del diccionario creado>. Cuando se ejecuta, esta palabra establecerá el diccionario creado como el diccionario inicial para la búsqueda.
De hecho, en la palabra del diccionario hay un enlace al último artículo de este diccionario, con el que comienza la búsqueda. Y en el momento de la ejecución, esta palabra del diccionario escribe un enlace a su campo de datos en la variable de contexto.
Más adelante será posible crear el vocabulario de palabras, que en el fuerte, en la implementación actual, se describe de manera bastante simple:
: vocabulary create context @ , does> context ! ;
Entonces, crea la palabra adelante. Usaremos el comando var8. Bytecode "contexto!" colocar justo después del campo de datos:
forth: .byte b_var8 .byte does_voc - . - 1 .quad 0 # <-- . , - . does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
Ahora volvamos a crear el diccionario en sí.
En general, en una fortaleza, una descripción de una palabra en la memoria se denomina "entrada de diccionario". En términos ordinarios, diría que hay un título de artículo y su código. Pero no todo es habitual en un fuerte, allí se llama "campo de nombre", "campo de comunicación", "campo de código" y "campo de datos". Trataré de decirte qué significa todo esto en términos tradicionales.
El campo de nombre es el nombre de la palabra, "línea con un contador". Es como en el viejo pascal: byte de longitud de cadena, luego cadena. El campo de enlace es un enlace al artículo anterior. Anteriormente, solo había una dirección, pero tendremos un código independiente de la plataforma, y esto será un desplazamiento. El campo de código, tradicionalmente en el fuerte, es el código de máquina (cuando la implementación está en una línea directa), para las palabras fuera del núcleo se llamaba _call. Solo tendremos un bytecode. Y el campo de datos es para palabras que contienen datos, por ejemplo, para variables o constantes. Por cierto, el diccionario de palabras también se refiere a él.
Para el compilador, todavía necesitamos banderas. Por lo general, un fuerte solo necesita una bandera, inmediata, y se coloca en un byte largo (a veces hay otra, oculta). Pero esto es para el código cosido directo, donde el control del procesador se transfiere cuando se llama al campo de código. Y tenemos diferentes palabras: código de bytes y código de máquina, y se necesitan al menos dos, o incluso tres, banderas.
¿Cuánto se necesita para el campo de la comunicación? Al principio, quería usar 16 bits. Este es un enlace a la palabra anterior, y la palabra definitivamente tiene menos de 64 Kb. Pero luego recordé que la palabra puede contener datos de casi cualquier tamaño. Y además, en presencia de varios diccionarios, el enlace puede pasar por muchas palabras. Resulta que en la mayoría de los casos, 8 bits son suficientes, pero puede haber 16 y 32. E incluso 64 bits, si hay datos de más de 4 GB. Bueno, hagamos soporte para todas las opciones. Qué opción se utiliza: poner en las banderas. Resulta al menos 4 banderas: el atributo inmediato, el atributo de la palabra central y 2 bits por variante del campo de comunicación utilizado. Es necesario usar un byte separado para las banderas, de ninguna otra manera.
Definimos las banderas de la siguiente manera:
f_code = 0x80 f_immediate = 0x60
El indicador f_code será para las palabras del núcleo escritas en ensamblador, el indicador f_immediate será útil para el compilador, sobre esto en el próximo artículo. Y los dos bits menos significativos determinarán la longitud del campo de comunicación (1, 2, 4 u 8 bytes).
Entonces, el título del artículo será así:
- banderas (1 byte)
- campo de comunicación (1-8 bytes)
- nombre longitud byte
- nombre (1-255 bytes)
Hasta este punto, no he usado las capacidades del ensamblador "macro". Y ahora los necesitamos. Así es como obtuve una macro con el elemento de nombre para formar el título de la palabra:
.macro item name, flags = 0 link = . - p_item 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word . - p_item .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int . - p_item .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad . - p_item .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm
Esta macro utiliza el valor p_item: esta es la dirección de la entrada anterior del diccionario. Este valor al final se actualiza para uso futuro: p_item = 9b. Aquí 9b es una etiqueta, no un número, no confunda :) La macro tiene dos parámetros: el nombre de la palabra y las banderas (opcional). Al comienzo de la macro, se calcula el desplazamiento de la palabra anterior. Luego, dependiendo del tamaño del desplazamiento, se compilan las banderas y el campo de comunicación del tamaño deseado. Luego, el byte de la longitud del nombre y el nombre mismo.
Defina antes de la primera palabra p_item de la siguiente manera:
p_item = .
El punto es la dirección de compilación actual en ensamblador. Como resultado de esta definición, la primera palabra se referirá a sí misma (el campo de comunicación será 0). Esta es una señal del fin de los diccionarios.
Por cierto, ¿qué habrá en el campo de código de las palabras del núcleo? Como mínimo, debe guardar el código de comando en alguna parte. Decidí seguir el camino más simple. Para las palabras del núcleo también habrá un código de bytes. Para la mayoría de los equipos, esto será solo un comando de byte, seguido de b_exit. Por lo tanto, para el intérprete, el indicador f_code no necesita ser analizado, y los comandos para él no diferirán de ninguna manera. Solo necesita llamar al código de bytes para todos.
Hay otra ventaja de esta opción. Para los comandos con parámetros, puede especificar parámetros seguros. Por ejemplo, si invoca el comando iluminado en implementaciones de Fort con código cosido directo, el sistema se bloqueará. Y aquí se escribirá allí, por ejemplo, iluminado 0, y esta secuencia simplemente pondrá 0 en la pila. Incluso para la rama se puede hacer de forma segura!
.byte branch8 .byte 0f - . 0: .byte b_exit
Con tal llamada habrá algunos gastos generales, pero para el intérprete no serán significativos. Y el compilador analizará las banderas y compilará el código correcto y rápido.
La primera palabra, por supuesto, será la palabra "adelante", el vocabulario básico que estamos creando. Aquí, solo ingrese el comando var útil con un enlace al código después de hacer>. Ya cité este código en la sección anterior, pero lo repetiré nuevamente, con el título:
p_item = . item forth .byte b_var8 .byte does_voc - . - 1 .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
E inmediatamente crearemos las variables de contexto y las necesitamos para buscar palabras:
item .byte b_var0 .quad 0 item context context: .byte b_var0 .quad 0
Y ahora, debe ser paciente y escribir un título para cada palabra que escribimos en ensamblador con el indicador f_code:
item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit ... item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit
Y así sucesivamente ...
Con los equipos escritos en bytecode es aún más fácil. Es suficiente agregar solo un encabezado antes del código de bytes, al igual que la palabra, por ejemplo:
item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint ...
Para comandos con parámetros, haremos parámetros seguros. Por ejemplo, deje que los comandos lite devuelvan el número Pi, si alguien los llama de forma interactiva, habrá una pascua :)
item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926535 .byte b_exit
La última palabra en la lista hará que la palabra adiós simbólicamente. Pero aún necesitamos inicializar la dirección de esta palabra en el campo de datos. Para obtener la dirección de esta palabra, use el comando var0:
last_item: .byte b_var0 item bye, f_code .byte b_bye
En este diseño, si llamamos a la dirección last_item en el bytecode, obtendremos la dirección de la palabra bye. Para escribirlo en los campos de datos de la palabra adelante, ejecútelo y la dirección deseada estará en contexto. Por lo tanto, el código de inicialización del sistema será así:
forth last_item context @ !
Y ahora procedamos directamente al intérprete. Primero, necesitamos trabajar con el búfer de entrada y extraer palabras de él. Déjame recordarte que el intérprete en el fuerte es muy simple. Extrae las palabras del búfer de entrada en secuencia, trata de encontrarlas. Si se encuentra la palabra, el intérprete la inicia para su ejecución.
Búfer de entrada y extracción de palabras
Para ser sincero, no quiero pasar mucho tiempo estudiando los estándares del fuerte. Pero aún así intentaré hacerlo lo más cerca posible de ellos, principalmente de memoria. Si los expertos de fort verán una fuerte discrepancia aquí, escriba, la solucionaré.
El fuerte tiene tres variables para trabajar con el búfer: tib, #tib y> in. La variable tib empuja la dirección del búfer de entrada en la pila. La variable #tib empuja el número de caracteres que están en el búfer a la pila. Y la variable> in contiene el desplazamiento en el búfer de entrada, más allá del cual se encuentra el texto sin formato. Define estas variables.
item tib .byte b_var0 v_tib: .quad 0 item #tib .byte b_var0 v_ntib: .quad 0 item >in .byte b_var0 v_in: .quad 0
A continuación hacemos la palabra blword. Esta palabra, usando las variables especificadas, obtiene la siguiente palabra del flujo de entrada. Un espacio se usa como delimitadores y todos los caracteres con un código menor que un espacio. Esta palabra estará en ensamblador. Después de la depuración, resultó así:
b_blword = 0xF0 bcmd_blword: mov rsi, v_tib # mov rdx, rsi # RDX mov rax, v_in # mov rcx, v_ntib # add rsi, rax # RSI - sub rcx, rax # jz 3f word2: lodsb # AL RSI cmp al, ' ' ja 1f # ( ) dec rcx jnz word2 # 3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 2: mov rax, rsi sub rsi, rdx # ( ) mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi # word1: push rax # jmp _next
Esta palabra es similar a la palabra estándar, pero, a diferencia de ella, tiene en cuenta todos los delimitadores y no copia la palabra en el búfer. Devuelve solo dos valores en la pila: dirección y longitud. Si la palabra no se puede recuperar, devuelve 0. Ha llegado el momento de comenzar a escribir el intérprete.
Búsqueda de palabras e intérprete
Para comenzar, hagamos que la palabra interprete. Esta palabra selecciona una nueva palabra del búfer usando blworld, la busca en el diccionario y la ejecuta. Y así se repite hasta que se agota el búfer. Todavía no tenemos la capacidad de buscar una palabra, por lo que escribiremos un comprobante de prueba que simplemente imprima la palabra desde el búfer usando type. Esto nos dará la oportunidad de verificar y depurar blworld:
# : interpret begin blword dup while type repeat drop ; item interpret 1: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_type .byte b_branch8 .byte 1b - . 0: .byte b_drop .byte b_exit
Ahora haz que la palabra salga. Por lo general, hacen esto cuando implementan sistemas fuertes: usan la palabra salir o abortar para ingresar al modo de intérprete. La palabra salir elimina las pilas e inicia un ciclo interminable de entrada e interpretación del búfer. Con nosotros será solo un llamado a interpretar. El código para esta palabra constará de dos partes. La primera parte estará en ensamblador, la segunda parte estará en bytecode. La primera parte:
b_quit = 0xF1 bcmd_quit: lea r8, quit mov sp, init_stack mov bp, init_rstack jmp _next
La segunda parte:
quit: .byte b_call16 .word interpret - . - 2 .byte b_bye
Como de costumbre, el código del ensamblador se encuentra en la sección .text, el código de bytes está en la sección .data.
Y finalmente, cambie el bytecode de inicio. Solo se inicializará el diccionario, se establecerá un búfer en la línea de inicio y se llamará para salir.
# forth last_item context @ ! start_code tib ! < > #tib ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_call8 .byte start_code - . - 1 .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .world 1f - 0f .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_quit start_code: .byte b_var0 0: .ascii "word1 word2 word3" 1:
Compilar, vincular, correr!
$ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth word1word2wordBye!
Es un poco como gachas, pero este es exactamente el resultado. Salimos sin delimitadores. Por cierto, ponga el avance de línea antes de comprar para el futuro, esto no va a doler.
Por supuesto, tuve que jugar con la depuración. Además del ya mencionado "Fallo de segmentación (núcleo descargado)", a veces se obtuvieron resultados interesantes. Por ejemplo, esto:
$ ./forth word1word2word3forth)%60Acurrent(context(%600lit8lit16zlit32v%5E%DF%80lit64v%5E%DF%80call8call16call32branch8branch16qbranch8qbranch16exit1-+!-%22*#/$mod%25/mod&abs'dup0drop1swap2rot3-rot4over5pick6roll7depth8@@!Ac@Bc!Cw@Dw!Ei@Fi!G0=P0%3CQ0%3ER=S%3CT%3EU%3C=V%3E=Wvar8)var160base(holdbuf(Qholdpoint(hold@0U110ACp@&20T0!?!%3CgF!A0@RF!5%220'%DE%A61Q-%DD%80:tib(%7F%60(%3Ein(%20%20%20%20%20%20%20interpret01('byeSegmentation%20fault%20(core%20dumped)
Este parece ser nuestro diccionario binario completo con texto cortado en delimitadores :) Sucedió cuando olvidé "dec rcx" antes de word3 en el comando b_blword.
Podemos elegir palabras de la secuencia de entrada, hay un diccionario. Ahora necesita implementar una búsqueda en el diccionario y lanzar palabras para su ejecución. Esto requerirá las palabras find, cfa y execute.
La palabra find tomará la dirección de la palabra y su longitud de la pila. Esta palabra será devuelta por la dirección de la entrada del diccionario o 0 si no se encuentra.
La palabra cfa en la dirección del artículo calculará la dirección del bytecode ejecutable.
Y la palabra ejecutar ejecutará el código de bytes.
Comencemos con find. En los estándares fuertes, se necesita una dirección: una línea con un contador. Pero no quiero copiar una vez más la cadena al búfer, así que me desviaré un poco de los estándares. La palabra find tomará dos parámetros en la pila: la dirección y la longitud de la cadena (de hecho, eso devuelve la palabra blword). Después de la depuración, esta palabra tomó la siguiente forma:
b_find = 0xF2 bcmd_find: pop rbx # pop r9 # mov rdx, v_context mov rdx, [rdx] # # find0: mov al, [rdx] # and al, 3 # - , , or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] # 64 lea rsi, [rdx + 9] # jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] # 32 lea rsi, [rdx + 5] # jmp find1 find_l16: movsx r10, word ptr [rdx + 1] # 16 lea rsi, [rdx + 3] # jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] # 8 lea rsi, [rdx + 2] # find1: movzx rax, byte ptr [rsi] # cmp rax, rbx jz find2 # find3: or r10, r10 jz find_notfound # , add rdx, r10 # jmp find0 # , find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 # push rdx jmp _next find_notfound: push r10 jmp _next
Quizás esta sea la palabra más difícil para hoy. Ahora modificamos la palabra interpretar, reemplazando el tipo con "buscar": # : interpret begin blword dup while find . repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_call16 .word dot - . - 2 .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
En la línea de prueba, debe poner las palabras que están en el diccionario, por ejemplo, "0 1- dup +".¡Todo está listo para lanzar! $ ld forth.o -o forth $ ./forth 6297733 6297898 6298375 Bye!
Genial, la búsqueda funciona. Estas son las direcciones de las palabras (en decimal). Ahora la palabra cfa. Deje que también esté en ensamblador, es muy simple, trabajar con banderas es similar a encontrar: b_cfa = 0xF3 bcmd_cfa: pop rdx # mov al, [rdx] # and al, 3 # - , , or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] # (64 ) jmp cfa1 find_l32: lea rsi, [rdx + 5] # (32 ) jmp cfa1 find_l16: lea rsi, [rdx + 3] # (16 ) jmp cfa1 find_l8: lea rsi, [rdx + 2] # (8 ) xor rax, rax lodsb add rsi, rax push rsi jmp _next
Y finalmente, la palabra ejecutar, es aún más simple: b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 # pop r8 # - jmp _next
Corregir la palabra interpretar y correr! # : interpret begin blword dup while find cfa execute repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_cfa .byte b_execute .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
Lanzamiento $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth -2 Bye!
Urrra, ganado! (C) Cat MatroskinDe hecho, si restas 1 de 0 y te sumas el resultado, será -2 :)Esto es genial, pero aún quiero escribir los comandos desde el teclado. Y hay un problema más: nuestro intérprete solo entiende los números 0, 1, 2, 3, 4 y 8 (que se definen como constantes). ¿Qué aprendería él a entender cualquier número? ¿Necesita la palabra "número"? De la misma manera que para la palabra buscar, no usaré el búfer. La palabra "número"? tomará dos parámetros en la pila: la dirección de la cadena y la longitud. Si tiene éxito, devolverá el número recibido y la marca 1. Si la conversión no tiene éxito, habrá un número en la pila: 0.El código resultó ser largo, pero bastante simple y lineal: b_number = 0xF5 bcmd_number: pop rcx # pop rsi # xor rax, rax # xor rbx, rbx # mov r9, v_base # xor r10, r10 # or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' ja num_false cmp bl, '9' jae num_09 cmp bl, 'A' ja num_false cmp bl, 'Z' jae num_AZ cmp bl, 'a' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false add rax, rbx mul r9 inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next
Modificar interpretar. Si la palabra no está en el diccionario, intentaremos interpretarla como un número: # : interpret # begin # blword dup # while # over over find dup # if -rot drop drop cfa execute else number? drop then # repeat # drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye
Y aquí lo tengo! Depure dicho bytecode en ensamblador, sin puntos de interrupción en el bytecode, sin la capacidad de simplemente "caminar" a lo largo del bytecode ... Además, sin los movimientos más fáciles en la pila, y sin la capacidad simple de ver el contenido de la pila ... Y en GDB, donde solo la línea de comando ... Te diré: ¡es solo una explosión cerebral! No peor ¡Esta es una EXPLOSIÓN CEREBRAL !Pero ... somos indios, siempre encontraremos soluciones :)En general, encontré esta solución: implementé un comando para mostrar el contenido de la pila: "s". El comando no es el más fácil, pero aún más fácil de interpretar. Y, como se vio después, ochchchen útil. Aquí esta: # : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_lit8 .byte '(' .byte b_emit .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit
A la derecha, di un ejemplo del contenido de la pila, después de la ejecución de cada comando. Por supuesto, hay un ciclo, y este es solo el primer paso. Pero el resto son muy similares, solo cambia el valor en la parte superior de la pila. Después de tal "rastro", ¡el equipo ganó inmediatamente!Para la depuración, creé las siguientes macros: .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm
Utilizado insertando en los lugares correctos de esta manera: item interpret interpret: .byte b_blword prs .byte b_dup prs .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over ......
Como resultado, el primer lanzamiento produjo el siguiente resultado: $ ./forth (2 ): 6297664 1 (3 ): 6297664 1 1 (3 ): 2 6297666 1 (4 ): 2 6297666 1 1 (4 ): 2 3 6297668 1 (5 ): 2 3 6297668 1 1 (3 ): 6 6297670 2 (4 ): 6 6297670 2 2 (4 ): 6 6297670 6297673 1 (5 ): 6 6297670 6297673 1 1 6297670 (2 ): 6 0 (3 ): 6 0 0 Bye!
Cada movimiento en la pila se puede ver claramente. Era necesario hacer esto antes :)Fui más allá al hacer otra macro de depuración: .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm
Como resultado, se hizo posible hacerlo: item interpret interpret: .byte b_blword pr blworld prs .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over prs .byte b_find pr find prs .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa pr execute prs .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq pr numberq prs .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
Y entiende esto: $ ./forth blworld(2 ): 6297664 2 (4 ): 6297664 2 6297664 2 find(3 ): 6297664 2 0 numberq(2 ): 6297664 0 blworld(3 ): 6297664 6297667 2 (5 ): 6297664 6297667 2 6297667 2 find(4 ): 6297664 6297667 2 0 numberq(3 ): 6297664 6297667 0 blworld(4 ): 6297664 6297667 6297670 1 (6 ): 6297664 6297667 6297670 1 6297670 1 find(5 ): 6297664 6297667 6297670 1 6297958 execute(3 ): 6297664 6297667 6297962 blworld(3 ): 39660590749888 6297672 1 (5 ): 39660590749888 6297672 1 6297672 1 find(4 ): 39660590749888 6297672 1 6298496 execute(2 ): 39660590749888 6298500 39660590749888 blworld(1 ): 0 Bye!
Fue un intento de interpretar la cadena "20 30 *".Y puede mostrar los números de la línea de origen ... bueno, tal vez entonces ...Por supuesto, esta es una técnica de registro clásica para la depuración, pero algo que no recordaba de inmediato.En general, como resultado de la depuración, encontré una pila yendo al extranjero. Esto es lo opuesto al desbordamiento cuando intentan tomar más de lo que ponen. Agregó su control a ".s".Con la ayuda de nuevas macros, la depuración fue rápida. Por cierto, antes de eso publiqué un bytecode por línea. Pero el ensamblador le permite colocar varios bytes en una cadena, por qué no usarlo.Concluyamos la palabra interpretar agregando dos cheques: que la palabra no se ha convertido a un número y que salga de la pila al extranjero. Como resultado, interpretar es el siguiente: item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # , .byte b_qbranch8, 3f - . # 0, , 3 .byte b_type # .byte b_strp # .byte 19 # .ascii " : word not found!\n" .byte b_quit # 3: .byte b_nip, b_nip # , ( b_over, b_over) 2: # .byte b_depth # .byte b_zlt # , 0 ( 0<) .byte b_qnbranch8, interpret_ok - . # , , .byte b_strp # .byte 14 .ascii "\nstack fault!\n" .byte b_quit # interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
Por cierto, vale la pena señalar que ahora el comando quit vacía las pilas y comienza la interpretación nuevamente sin cambiar el estado del búfer. Por lo tanto, la interpretación continúa, pero con pilas "frescas". Lo arreglaremos un poco más tarde.Lo único que queda es organizar la entrada del teclado.Entrada de teclado
La entrada del teclado en el fuerte es simple. Existe la palabra esperar, toma dos parámetros: la dirección del búfer y su tamaño. Esta palabra realiza la entrada del teclado. El número real de caracteres ingresados se coloca en la variable span. Hagamos estas palabras. Entraremos desde la entrada estándar. .data item span span: .byte b_var0 v_span: .quad 0 .text b_expect = 0x88 bcmd_expect: mov rax, 0 # № 1 - sys_read mov rdi, 0 # № 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next
Ahora necesitamos crear un búfer de entrada de teclado. Que tenga 256 caracteres de longitud.Hagámoslo en lugar de la línea de prueba anterior. inbuf_size = 256 inbuf: .byte b_var0 .space inbuf_size
Y modificamos salir, así como el código de bytes inicial. Establezca la variable tib en el búfer de entrada inbuf, llame a wait, luego copie el valor de span a #tib. La variable> en se anula; llamamos interpretar. Y así lo repetimos en un ciclo. Hay adornos: para agregar una solicitud de entrada y sería bueno mostrar el estado de la pila (¡y ya tenemos un comando listo para esto!). Después de varias iteraciones, obtuvimos el siguiente código (comando iniciar y salir): # forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - .
Y aquí está el resultado: $ ./forth ( 0 ): > 60 ( 1 ): 60 > 60 24 ( 3 ): 60 60 24 > rot ( 3 ): 60 24 60 > -rot ( 3 ): 60 60 24 > swap ( 3 ): 60 24 60 > * * . 86400 ( 0 ): > 200 30 /mod ( 2 ): 20 6 > bye Bye! $
Todo después del símbolo ">" es mi entrada de teclado. El resto es la respuesta del sistema. Jugué un poco con comandos, escribiendo desde el teclado. Realizó varias operaciones de apilamiento, calculó la cantidad de segundos en días.Resumen
El intérprete está completo y trabajando. Y cortésmente se despide - a él "adiós" y él "adiós" :)Como invitación - el contenido de la pila aritmética. El primer número entre paréntesis es el tamaño de la pila, luego el contenido y la solicitud para ingresar ">". Puede ingresar cualquier comando implementado (conté 76 comandos). Es cierto que muchos tienen sentido solo para el compilador, por ejemplo, literales, transiciones, comandos de invocación.Fuente completa (aproximadamente 1300 líneas) .intel_syntax noprefix stack_size = 1024 f_code = 0x80 f_immediate = 0x60 .macro item name, flags = 0 link = p_item - . 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word link .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int link .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad link .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm .section .data init_stack: .quad 0 init_rstack: .quad 0 emit_buf: .byte 0 inbuf_size = 256 msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "\nBye!\n" msg_bye_len = . - msg_bye bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_num1, bcmd_num2, bcmd_num3, bcmd_num4, bcmd_num8 # 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_wp, 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_var8, bcmd_var16, 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_nip, 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_setp, bcmd_setm, 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_2r, bcmd_r2, bcmd_rget, 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_emit, bcmd_str, bcmd_strp, bcmd_count, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_expect, 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 # 0x90 .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_blword, bcmd_quit, bcmd_find, bcmd_cfa, bcmd_execute, bcmd_numberq, bcmd_bad, bcmd_bad # 0xF0 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - . p_item = . item forth forth: .byte b_var8 .byte does_voc - . .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit item current .byte b_var0 .quad 0 item context context: .byte b_var0 v_context: .quad 0 item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit item 2, f_code .byte b_num2 .byte b_exit item 3, f_code .byte b_num3 .byte b_exit item 4, f_code .byte b_num4 .byte b_exit item 8, f_code .byte b_num8 .byte b_exit item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926 .byte b_exit item call8, f_code .byte b_call8 .byte 0f - . - 1 0: .byte b_exit item call16, f_code .byte b_call16 .word 0f - . - 2 0: .byte b_exit item call32, f_code .byte b_call32 .int 0f - . - 4 0: .byte b_exit item branch8, f_code .byte b_branch8 .byte 0f - . 0: .byte b_exit item branch16, f_code .byte b_branch16 .word 0f - . 0: .byte b_exit item qbranch8, f_code .byte b_qbranch8 .byte 0f - . 0: .byte b_exit item qbranch16, f_code .byte b_qbranch16 .word 0f - . 0: .byte b_exit item exit, f_code .byte b_exit item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit item /, f_code .byte b_div .byte b_exit item mod, f_code .byte b_mod .byte b_exit item /mod, f_code .byte b_divmod .byte b_exit item abs, f_code .byte b_abs .byte b_exit item dup, f_code .byte b_dup .byte b_exit item drop, f_code .byte b_drop .byte b_exit item swap, f_code .byte b_swap .byte b_exit item rot, f_code .byte b_rot .byte b_exit item -rot, f_code .byte b_mrot .byte b_exit item over, f_code .byte b_over .byte b_exit item pick, f_code .byte b_pick .byte b_exit item roll, f_code .byte b_roll .byte b_exit item depth, f_code .byte b_depth .byte b_exit item @, f_code .byte b_get .byte b_exit item !, f_code .byte b_set .byte b_exit item c@, f_code .byte b_get8 .byte b_exit item c!, f_code .byte b_set8 .byte b_exit item w@, f_code .byte b_get16 .byte b_exit item w!, f_code .byte b_set16 .byte b_exit item i@, f_code .byte b_get32 .byte b_exit item i!, f_code .byte b_set32 .byte b_exit item +!, f_code .byte b_setp .byte b_exit item -!, f_code .byte b_setm .byte b_exit item >r, f_code .byte b_2r .byte b_exit item r>, f_code .byte b_r2 .byte b_exit item r@, f_code .byte b_rget .byte b_exit item "0=", f_code .byte b_zeq .byte b_exit item 0<, f_code .byte b_zlt .byte b_exit item 0>, f_code .byte b_zgt .byte b_exit item "=", f_code .byte b_eq .byte b_exit item <, f_code .byte b_lt .byte b_exit item >, f_code .byte b_gt .byte b_exit item "<=", f_code .byte b_lteq .byte b_exit item ">=", f_code .byte b_gteq .byte b_exit item type, f_code .byte b_type .byte b_exit item expect, f_code .byte b_expect .byte b_exit item emit, f_code .byte b_emit .byte b_exit item count, f_code .byte b_count .byte b_exit item "(\")", f_code .byte b_str .byte b_exit item "(.\")", f_code .byte b_strp .byte b_exit item var8, f_code .byte b_var8 .byte 0f - . 0: .byte b_exit item var16, f_code .byte b_var16 .word 0f - . 0: .byte b_exit item base base: .byte b_var0 v_base: .quad 10 holdbuf_len = 70 item holdbuf holdbuf: .byte b_var0 .space holdbuf_len item holdpoint holdpoint: .byte b_var0 .quad 0 item span span: .byte b_var0 v_span: .quad 0 # : hold holdpoint @ 1- dup holdbuf > if drop drop else dup holdpoint ! c! then ; item hold 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 ; item # 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 ! ; item <# 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 ; item #s conv_s: .byte b_call8 .byte conv - . - 1 .byte b_dup .byte b_qbranch8 .byte conv_s - . .byte b_exit # : #> holdpoint @ holdbuf 70 + over - ; item #> 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 item . 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 item tib tib: .byte b_var0 v_tib: .quad 0 item #tib ntib: .byte b_var0 v_ntib: .quad 0 item >in bin: .byte b_var0 v_in: .quad 0 # : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_strp .byte 2 .ascii "( " .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " .byte b_dup, b_zlt .byte b_qnbranch8, 1f - . .byte b_strp .byte 14 .ascii "\nStack fault!\n" .byte b_quit 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # , .byte b_qbranch8, 3f - . # 0, , 3 .byte b_type # .byte b_strp # .byte 19 # .ascii " : word not found!\n" .byte b_quit # 3: .byte b_nip, b_nip # , ( b_over, b_over) 2: # .byte b_depth # .byte b_zlt # , 0 ( 0<) .byte b_qnbranch8, interpret_ok - . # , , .byte b_strp # .byte 14 .ascii "\nstack fault!\n" .byte b_quit # interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye .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_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 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_wp = 0x18 bcmd_wp: incq [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 sar rax, 3 push rax jmp _next b_nip = 0x39 bcmd_nip: pop rax mov [rsp], 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 b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] 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_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 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_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push r8 add r8, rax push rax 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 b_expect = 0x88 bcmd_expect: mov rax, 0 # 1 - sys_read mov rdi, 0 # 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf # mov [rsi], al mov rax, 1 # 1 - sys_write mov rdi, 1 # 1 - stdout mov rdx, 1 # push r8 syscall # pop r8 jmp _next b_blword = 0xF0 bcmd_blword: mov rsi, v_tib # mov rdx, rsi # RDX mov rax, v_in # mov rcx, v_ntib # mov rbx, rcx add rsi, rax # RSI - sub rcx, rax # jz 3f word2: lodsb # AL RSI cmp al, ' ' ja 1f # ( ) dec rcx jnz word2 # 3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx jz word9 word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 word9: inc rsi 2: mov rax, rsi sub rsi, rdx # ( ) cmp rsi, rbx jle 4f mov rsi, rbx 4: mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi # word1: push rax # jmp _next b_quit = 0xF1 bcmd_quit: lea r8, quit mov rsp, init_stack mov rbp, init_rstack jmp _next b_find = 0xF2 bcmd_find: pop rbx # pop r9 # mov rdx, v_context mov rdx, [rdx] # # find0: mov al, [rdx] # and al, 3 # - , , or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] # 64 lea rsi, [rdx + 9] # jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] # 32 lea rsi, [rdx + 5] # jmp find1 find_l16: movsx r10, word ptr [rdx + 1] # 16 lea rsi, [rdx + 3] # jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] # 8 lea rsi, [rdx + 2] # find1: movzx rax, byte ptr [rsi] # cmp rax, rbx jz find2 # find3: or r10, r10 jz find_notfound # , add rdx, r10 # jmp find0 # , find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 # push rdx jmp _next find_notfound: push r10 jmp _next b_cfa = 0xF3 bcmd_cfa: pop rdx # mov al, [rdx] # and al, 3 # - , , or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] # (64 ) jmp cfa1 cfa_l32: lea rsi, [rdx + 5] # (32 ) jmp cfa1 cfa_l16: lea rsi, [rdx + 3] # (16 ) jmp cfa1 cfa_l8: lea rsi, [rdx + 2] # (8 ) cfa1: xor rax, rax lodsb add rsi, rax push rsi jmp _next b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 # pop r8 # - jmp _next b_numberq = 0xF5 bcmd_numberq: pop rcx # pop rsi # xor rax, rax # xor rbx, rbx # mov r9, v_base # xor r10, r10 # or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' jb num_false cmp bl, '9' jbe num_09 cmp bl, 'A' jb num_false cmp bl, 'Z' jbe num_AZ cmp bl, 'a' jb num_false cmp bl, 'z' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false mul r9 add rax, rbx inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next
El código fuente se está haciendo más grande, así que lo traigo aquí por última vez.Ahora su lugar de residencia estará en el github: https://github.com/hal9000cc/forth64En el mismo lugar, en la carpeta bin puede encontrar la versión ya compilada para Linux x64. Quién tiene Linux, puede descargarlo y ejecutarlo.Y quién tiene Windows: puede instalar WSL (Windows Subsystem for Linux). Me iba para las vacaciones e hice exactamente eso. Resultó ser muy simple, tomó alrededor de 5 minutos. Hubo solo un momento, no comenzó de inmediato, el subsistema tuvo que "encenderse" a través del comando PowerShell. Seguí el enlace del mensaje de error, ejecuté el comando y funcionó.Pero también hay una manera para que los indios reales lo ejecuten todo en Windows :) No es difícil hacer esto, solo rehaga algunas palabras que interactúen con el sistema.Eso es todo! La próxima vez, ejecutaremos el compilador.Habrá una oportunidad para compilar nuevas palabras, habrá condiciones, ciclos. En realidad, será posible escribir en un fuerte más o menos estándar, compilarlo en código de bytes y ejecutarlo. Bueno, será posible realizar pruebas más serias, verificar el rendimiento de la máquina de bytes.Continuación: Byte-máquina para el fuerte (y no solo) en nativos americanos (parte 4)