
L'année 2019 est arrivée, les fêtes de fin d'année s'achèvent. Il est temps de commencer à se souvenir des octets, des commandes, des variables, des boucles ...
Quelque chose que j'ai déjà oublié avec ces vacances. Faut se souvenir ensemble!
Aujourd'hui, nous allons faire un interprète pour notre machine d'octets. Ceci est le troisième article, les premières parties sont ici:
partie 1 ,
partie 2 .
Bonne année à tous et bienvenue dans la coupe!
Pour commencer, je répondrai aux questions de
fpauk . Ces questions sont absolument correctes. Maintenant, l'architecture de cette machine d'octets est telle que nous travaillons avec des adresses de processeur directes. Mais dans le bytecode, ces adresses ne le sont pas, elles sont formées après le démarrage du système. Après le démarrage du système, nous pouvons créer des pointeurs et ce code fonctionnera correctement sur n'importe quelle plate-forme. Par exemple, l'adresse d'une variable ou d'un tableau peut être obtenue avec la commande var0. Cette commande fonctionnera sur n'importe quelle plateforme et renverra l'adresse correcte spécifique à cette plateforme. Ensuite, vous pouvez travailler avec cette adresse comme vous le souhaitez.
Mais
quand même,
fpauk a raison. L'adresse ne peut pas être stockée en bytecode. Il s'avère que nous pouvons écrire du code indépendant de la plate-forme, mais pour cela, nous devons faire des efforts. Assurez-vous en particulier que les adresses ne figurent pas dans le bytecode. Et ils peuvent entrer, par exemple, si vous enregistrez le code compilé dans un fichier. Il contiendra des données et il peut s'agir d'adresses. Par exemple, les valeurs des variables ici, le contexte et autres.
Pour vous débarrasser d'un tel problème, vous devez rendre les adresses virtuelles. L'adressage du processeur x86 est assez puissant et, dans la plupart des cas, il n'ajoutera même pas de commandes supplémentaires. Mais quand même, je vais continuer dans l'architecture actuelle, avec des adresses absolues. Et puis, lorsque nous arriverons aux tests, il sera possible de refaire les adresses en adresses virtuelles, et de voir comment cela affectera les performances. C'est intéressant.
Réchauffer
Et maintenant un petit entraînement. Faisons une autre partie de commandes d'octets petites mais utiles. Ce seront les commandes nip, emit, 1+, +!, -!, Count, mots de travail avec la pile de retour r>,> r, r @, un littéral de chaîne (") et des mots constants 1, 2, 3, 4, 8. N'oubliez pas de les inclure dans le tableau des commandes.
Voici le code de ces commandesb_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
La commande nip supprime le mot sous le haut de la pile. Cela équivaut à échanger des commandes de dépôt. Cela peut parfois être utile.
La commande emit repousse un caractère de la pile. Il utilise le même numéro d'appel système 1, le caractère place dans un tampon d'une longueur de 1.
La commande count est très simple - elle prend l'adresse de la ligne avec le compteur de la pile et la transforme en deux valeurs - l'adresse de la ligne sans le compteur et la longueur.
Les commandes b_2r, b_r2, b_rget sont les mots Fort r>,> r, r @. Le premier prend le mot de la pile de retour et le place sur la pile arithmétique. Le second effectue l'opération inverse. Le troisième copie le mot de la pile de retour, le place dans l'arithmétique, la pile de retour ne change pas.
Les commandes b_setp et b_setm sont les mots +! et -! .. Ils prennent la valeur et l'adresse de la pile, et modifient le mot à l'adresse spécifiée, ajoutant ou supprimant la valeur de la pile.
La commande b_str a un paramètre de longueur arbitraire - une ligne avec un compteur. Cette ligne est dans le bytecode après l'octet de commande, et la commande pousse simplement l'adresse de cette ligne sur la pile. En fait, il s'agit d'un littéral de chaîne.
Le reste de l'équipe, je pense, n'a pas besoin de commentaires.
Nous allons également créer une commande pour imprimer une chaîne constante (. "). Nous l'implémenterons comme point d'entrée à taper, comme suit:
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
Cette commande est structurée de manière similaire à b_str. Seulement, elle ne met rien sur la pile. La ligne située derrière cette commande en tant que paramètre est simplement affichée à l'utilisateur.
L'échauffement est terminé, le moment est venu pour quelque chose de plus grave. Voyons les générateurs de mots et les autres commandes var.
Mots générateurs
Rappelez les variables. Nous savons comment ils sont organisés au niveau du bytecode (commande var0). Pour créer une nouvelle variable, le fort utilise la construction suivante:
variable < >
Après avoir exécuté cette séquence, un nouveau mot <nom de variable> est créé. L'exécution de ce nouveau mot pousse l'adresse sur la pile pour stocker la valeur de la variable. Il y a aussi des constantes dans le fort, elles sont créées comme ceci:
<> constant < >
Après avoir créé la constante, l'exécution du mot <nom constant> place sur la pile <valeur>.
Ainsi, à la fois la variable de mot et la constante de mot sont des mots générateurs. Ils sont conçus pour créer de nouveaux mots. Dans un fort, ces mots sont décrits à l'aide de la construction create ... does>.
Les variables et constantes peuvent être définies comme suit:
: variable create 0 , does> ; : constant create , does> @ ;
Qu'est-ce que tout cela signifie?
Le mot create, une fois exécuté, crée un nouveau mot avec le nom qu'il prendra lorsqu'il sera exécuté à partir du flux d'entrée. Après la création, une séquence de mots est exécutée avant le mot>. Mais au moment de l'exécution de ce mot, ce qui est écrit après est exécuté. En même temps, l'adresse de données sera déjà sur la pile (comme on dit dans le fort, «champs de données»).
Ainsi, lors de la création d'une variable, la séquence «0» est exécutée - il s'agit de la réservation d'un mot machine à remplissage nul. Et lorsque le mot créé est exécuté, rien n'est fait (après le fait> il n'y a rien). L'adresse mémoire où la valeur est stockée reste simplement sur la pile.
Dans la définition d'une constante, un mot avec une valeur de remplissage sur la pile est réservé. Lorsque le mot créé est exécuté, "@" est exécuté, ce qui récupère la valeur à l'adresse spécifiée.
Voyons maintenant comment organiser le mot que nous créons. Il pousse l'adresse de données sur la pile (comme var0), puis transfère le contrôle à une adresse spécifique, bytecode. La commande var0 revient immédiatement. Mais dans ce cas, nous devons faire non pas un retour, mais, en fait, une transition.
Encore une fois, je formulerai ce qui doit être fait:
- mettre l'adresse de données sur la pile
- passer à un morceau de code après le fait>
Il s'avère que vous avez juste besoin de transférer le contrôle vers une autre adresse de bytecode, mais mettez d'abord l'adresse de l'octet suivant (R8) sur la pile.
C'est presque une commande de branche! Et ici, elle n'est pas seule. Vous avez déjà branch8 et branch16. Nous nommerons les nouvelles commandes var8 et var16, et laisserons celles-ci être juste les points d'entrée des commandes de branchement. Nous économisons sur la transition vers l'équipe de transition :) Donc, ce sera comme ça:
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
Dans le bon sens, la commande var32 fonctionnera toujours, et var64 aussi. Nous n'avons pas de transitions aussi longues, car les transitions ordinaires ne sont pas si longues. Mais pour la commande var, c'est un cas très réaliste. Mais pour l'instant, nous ne ferons pas ces commandes. Nous ferons plus tard, si nécessaire.
Avec les mots-générateurs triés. Ce fut au tour de décider du dictionnaire.
Vocabulaire
Habituellement, lorsqu'ils parlent de façon simpliste du dictionnaire fort, il se présente sous la forme d'une liste unidirectionnelle d'entrées de dictionnaire. En fait, tout est un peu plus compliqué, car le fort prend en charge de nombreux dictionnaires. En fait, c'est un arbre. La recherche d'un mot dans un tel arbre commence par une «feuille» - c'est le dernier mot du dictionnaire actuel. Le dictionnaire courant est défini par la variable de contexte et l'adresse du dernier mot est dans le mot du dictionnaire. Une autre variable est utilisée pour gérer les dictionnaires - elle définit un dictionnaire où de nouveaux mots seront ajoutés. Ainsi, un dictionnaire peut être installé pour une recherche, et un autre pour inclure de nouveaux mots.
Pour notre cas simple, nous n'aurions pas pu prendre en charge de nombreux dictionnaires, mais j'ai décidé de ne rien simplifier. En fait, pour comprendre le code d'octets, la machine d'octets, il n'est pas nécessaire de savoir ce qui est décrit dans cette section. Par conséquent, qui ne sont pas intéressés, vous pouvez simplement ignorer cette section. Eh bien, qui veut connaître les détails - allez-y!
Initialement, il existe un dictionnaire de base nommé. Cela signifie qu'il existe un tel mot - en avant. Ce mot est aussi appelé "dictionnaire", il y a une certaine confusion. Par conséquent, quand il s'agit d'un mot, je l'appellerai un mot de dictionnaire.
De nouveaux dictionnaires sont créés en utilisant cette construction:
vocabulary < >
Cela crée un mot avec le nom <nom du dictionnaire créé>. Une fois exécuté, ce mot définira le dictionnaire créé comme dictionnaire de départ pour la recherche.
En fait, dans le mot du dictionnaire, il y a un lien vers le dernier article de ce dictionnaire, avec lequel la recherche commence. Et au moment de l'exécution, ce mot de dictionnaire écrit un lien vers son champ de données dans la variable de contexte.
Plus tard, il sera possible de faire le mot vocabulaire, qui sur le fort, dans la mise en œuvre actuelle, est décrit tout simplement:
: vocabulary create context @ , does> context ! ;
Alors, créez le mot. Nous utiliserons la commande var8. Bytecode "context!" placer juste après le champ de données:
forth: .byte b_var8 .byte does_voc - . - 1 .quad 0 # <-- . , - . does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
Revenons maintenant à la création du dictionnaire lui-même.
En général, dans un fort, la description d'un mot en mémoire est appelée «entrée de dictionnaire». En termes ordinaires, je dirais qu'il y a un titre d'article et son code. Mais tout n'est pas tout à fait habituel dans un fort, là on l'appelle «champ de nom», «champ de communication», «champ de code» et «champ de données». Je vais essayer de vous dire ce que tout cela signifie en termes traditionnels.
Le champ du nom est le nom du mot "ligne avec un compteur". C'est comme dans l'ancien pascal - octet de longueur de chaîne, puis chaîne. Le champ de lien est un lien vers l'article précédent. Auparavant, il n'y avait qu'une adresse, mais nous aurons un code indépendant de la plate-forme, et ce sera un décalage. Le champ de code, traditionnellement dans le fort, est le code machine (lorsque l'implémentation est sur une ligne directe), pour les mots en dehors du noyau, il y avait appel _call. Nous aurons juste un bytecode. Et le champ de données est pour les mots contenant des données - par exemple, pour les variables ou les constantes. Soit dit en passant, le mot dictionnaire y fait également référence.
Pour le compilateur, nous avons encore besoin de drapeaux. Habituellement, un fort n'a besoin que d'un seul drapeau - immédiat, et il est placé dans un long octet (parfois il y en a un autre - caché). Mais c'est pour le code cousu directement, où le contrôle du processeur est transféré lorsqu'il est appelé dans le champ de code. Et nous avons des mots différents - bytecode et code machine, et au moins deux, voire trois, indicateurs sont nécessaires.
Combien faut-il pour le domaine de la communication? Au début, je voulais utiliser 16 bits. Il s'agit d'un lien vers le mot précédent, et le mot est certainement inférieur à 64 Ko. Mais je me suis alors souvenu que le mot peut contenir des données de presque n'importe quelle taille. Et d'ailleurs, en présence de plusieurs dictionnaires, le lien peut passer par plusieurs mots. Il s'avère que dans la plupart des cas, 8 bits suffisent, mais il peut y en avoir 16 et 32. Et même 64 bits, s'il y a des données de plus de 4 Go. Eh bien, prenons en charge toutes les options. Quelle option est utilisée - mettez les drapeaux. Il en résulte au moins 4 drapeaux: l'attribut immédiat, l'attribut du mot noyau et 2 bits par variante du champ de communication utilisé. Il n'est pas nécessaire d'utiliser un octet distinct pour les drapeaux, d'aucune autre manière.
Nous définissons les drapeaux comme suit:
f_code = 0x80 f_immediate = 0x60
L'indicateur f_code sera pour les mots du noyau écrits en assembleur, l'indicateur f_immediate sera utile pour le compilateur, à ce sujet dans l'article suivant. Et les deux bits les moins significatifs détermineront la longueur du champ de communication (1, 2, 4 ou 8 octets).
Ainsi, le titre de l'article sera comme ceci:
- drapeaux (1 octet)
- champ de communication (1-8 octets)
- nom octet de longueur
- nom (1-255 octets)
Jusqu'à présent, je n'ai pas utilisé les capacités de l'assembleur "macro". Et maintenant, nous en avons besoin. Voici comment j'ai obtenu une macro avec l'élément de nom pour former le titre du mot:
.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
Cette macro utilise la valeur p_item - il s'agit de l'adresse de l'entrée de dictionnaire précédente. Cette valeur à la fin est mise à jour pour une utilisation future: p_item = 9b. Ici 9b est une étiquette, pas un nombre, ne confondez pas :) La macro a deux paramètres - le nom du mot et des drapeaux (facultatif). Au début de la macro, le décalage par rapport au mot précédent est calculé. Ensuite, en fonction de la taille du décalage, les drapeaux et le champ de communication de la taille souhaitée sont compilés. Puis l'octet de la longueur du nom et du nom lui-même.
Définissez avant le premier mot p_item comme suit:
p_item = .
Le point est l'adresse de compilation actuelle dans l'assembleur. À la suite de cette définition, le premier mot se référera à lui-même (le champ de communication sera 0). C'est le signe de la fin des dictionnaires.
Au fait, qu'y aura-t-il dans le champ de code des mots du noyau? Au minimum, vous devez enregistrer le code de commande quelque part. J'ai décidé de suivre le chemin le plus simple. Pour les mots du noyau, il y aura également un bytecode. Pour la plupart des équipes, ce ne sera qu'une commande d'octets, suivie de b_exit. Ainsi, pour l'interpréteur, l'indicateur f_code n'a pas besoin d'être analysé et les commandes ne diffèrent en aucune façon. Vous avez juste besoin d'appeler le bytecode pour tout le monde.
Il y a un autre avantage à cette option. Pour les commandes avec paramètres, vous pouvez spécifier des paramètres sûrs. Par exemple, si vous appelez la commande allumée dans les implémentations Fort avec du code cousu directement, le système se bloque. Et ici, il sera écrit là, par exemple, allumé 0, et cette séquence mettra simplement 0 sur la pile. Même pour la branche peut se faire en toute sécurité!
.byte branch8 .byte 0f - . 0: .byte b_exit
Avec un tel appel, il y aura des frais généraux, mais pour l'interprète, ils ne seront pas importants. Et le compilateur analysera les indicateurs et compilera le code correct et rapide.
Le premier mot sera, bien sûr, le mot «en avant» - le vocabulaire de base que nous créons. Ici, venez dans la commande var pratique avec un lien vers le code après le fait>. J'ai déjà cité ce code dans la section précédente, mais je vais le répéter à nouveau, avec le titre:
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
Et nous allons immédiatement créer les variables de contexte et, nous en avons besoin pour rechercher des mots:
item .byte b_var0 .quad 0 item context context: .byte b_var0 .quad 0
Et maintenant, vous devez être patient et écrire un titre pour chaque mot que nous avons écrit en assembleur avec l'indicateur 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
Et ainsi de suite ...
Avec des équipes écrites en bytecode, c'est encore plus simple. Il suffit d'ajouter juste un en-tête avant le bytecode, tout comme le mot suivant, par exemple:
item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint ...
Pour les commandes avec paramètres, nous créerons des paramètres sûrs. Par exemple, laissez les commandes lite retourner le nombre Pi, si quelqu'un les appelle de manière interactive, il y aura une telle Pâques :)
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
Le dernier mot de la liste fera symboliquement disparaître le mot. Mais nous devons encore initialiser l'adresse de ce mot dans le champ de données. Pour obtenir l'adresse de ce mot, utilisez la commande var0:
last_item: .byte b_var0 item bye, f_code .byte b_bye
Dans cette conception, si nous appelons l'adresse last_item dans le bytecode, nous obtiendrons l'adresse du mot bye. Pour l'écrire dans les champs de données du mot en avant, exécutez en avant, et l'adresse souhaitée sera en contexte. Ainsi, le code d'initialisation du système sera comme ceci:
forth last_item context @ !
Passons maintenant directement à l'interprète. Tout d'abord, nous devons travailler avec le tampon d'entrée et en extraire des mots. Permettez-moi de vous rappeler que l'interprète du fort est très simple. Il extrait les mots du tampon d'entrée en séquence, essaie de les trouver. Si le mot est trouvé, l'interprète le lance pour exécution.
Tampon d'entrée et extraction de mots
Pour être honnête, je ne veux pas passer beaucoup de temps à étudier les normes du fort. Mais je vais quand même essayer de le rapprocher le plus possible d'eux, principalement de mémoire. Si les experts du fort verront une forte différence ici - écrivez, je le corrigerai.
Le fort a trois variables pour travailler avec le tampon: tib, #tib et> in. La variable tib pousse l'adresse du tampon d'entrée sur la pile. La variable #tib pousse le nombre de caractères qui sont dans le tampon sur la pile. Et la variable> in contient le décalage dans le tampon d'entrée, au-delà duquel se trouve le texte brut. Définissez ces 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
Ensuite, nous faisons le mot blword. Ce mot, en utilisant les variables spécifiées, obtient le mot suivant du flux d'entrée. Un espace est utilisé comme délimiteurs et tous les caractères avec un code inférieur à un espace. Ce mot sera en assembleur. Après le débogage, il s'est avéré comme ceci:
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
Ce mot est similaire au mot standard, mais, contrairement à lui, prend en compte tous les délimiteurs et ne copie pas le mot dans le tampon. Il renvoie seulement deux valeurs sur la pile - adresse et longueur. Si le mot ne peut pas être récupéré, renvoie 0. Le moment est venu de commencer à écrire l'interpréteur.
Recherche de mots et interprète
Pour commencer, faisons interpréter le mot. Ce mot sélectionne un nouveau mot dans le tampon à l'aide de blworld, le recherche dans le dictionnaire et l'exécute. Et ainsi, il se répète jusqu'à épuisement du tampon. Nous n'avons toujours pas la possibilité de rechercher un mot, nous allons donc écrire un talon de test qui imprimera simplement le mot à partir du tampon en utilisant type. Cela nous donnera l'occasion de vérifier et de déboguer 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
Maintenant, faites cesser le mot. Habituellement, ils le font lors de la mise en œuvre de systèmes de fortification: ils utilisent le mot quitter ou abandonner pour passer en mode interprète. Le mot quit vide les piles et démarre une boucle sans fin d'entrée et d'interprétation du tampon. Avec nous, ce sera juste un appel à interpréter. Le code de ce mot comprendra deux parties. La première partie sera en assembleur, la seconde sera en bytecode. La première partie:
b_quit = 0xF1 bcmd_quit: lea r8, quit mov sp, init_stack mov bp, init_rstack jmp _next
La deuxième partie:
quit: .byte b_call16 .word interpret - . - 2 .byte b_bye
Comme d'habitude, le code assembleur se trouve dans la section .text, le code octet se trouve dans la section .data.
Et enfin, changez le bytecode de départ. Il n'y aura que l'initialisation du dictionnaire, la mise en place d'un tampon sur la ligne de départ et l'appel à quitter.
# 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:
Compilez, liez, exécutez!
$ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth word1word2wordBye!
C’est un peu comme de la bouillie, mais c’est exactement le résultat. Nous sortons sans délimiteurs. Par ailleurs, mettez le fil de ligne avant d'acheter pour l'avenir, cela ne fera pas de mal.
Bien sûr, j'ai dû bricoler avec le débogage. En plus du «défaut de segmentation (core dumped)» déjà mentionné, des résultats parfois intéressants ont été obtenus. Par exemple, ceci:
$ ./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)
Cela semble être juste notre dictionnaire entier sous forme binaire avec du texte coupé en délimiteurs :) C'est arrivé quand j'ai oublié «dec rcx» avant word3 dans la commande b_blword.
Nous pouvons choisir des mots dans le flux d'entrée, il y a un dictionnaire. Vous devez maintenant implémenter une recherche dans le dictionnaire et lancer des mots pour les exécuter. Cela nécessitera les mots find, cfa et execute.
Le mot find prendra l'adresse du mot et sa longueur dans la pile. Ce mot sera retourné par l'adresse de l'entrée du dictionnaire ou 0 s'il n'est pas trouvé.
Le mot cfa à l'adresse de l'article calculera l'adresse du bytecode exécutable.
Et le mot exécuter exécutera le bytecode.
Commençons par trouver. Dans les normes fort, il faut une seule adresse - une ligne avec un compteur. Mais je ne veux pas à nouveau copier la chaîne dans le tampon, je vais donc dévier un peu des normes. Le mot find prendra deux paramètres sur la pile - l'adresse et la longueur de la chaîne (en fait, cela renvoie le mot blword). Après le débogage, ce mot a pris la forme suivante:
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
C'est peut-être le mot le plus difficile pour aujourd'hui. Maintenant, nous modifions le mot interpréter, en remplaçant type par "find": # : 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
Dans la ligne de test, vous devez mettre les mots qui sont dans le dictionnaire, par exemple, "0 1- dup +".Tout est prêt à être lancé! $ ld forth.o -o forth $ ./forth 6297733 6297898 6298375 Bye!
Génial, la recherche fonctionne. Ce sont les adresses des mots (en décimal). Maintenant, le mot cfa. Que ce soit aussi en assembleur, c'est très simple, travailler avec des drapeaux est similaire à trouver: 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
Et enfin, le mot exécuter, c'est encore plus simple: b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 # pop r8 # - jmp _next
Corrigez le mot interpréter et courir! # : 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
Lancement: $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth -2 Bye!
Urrra, gagné! (C) Cat MatroskinEn effet, si vous soustrayez 1 de 0 et ajoutez le résultat à vous-même, ce sera -2 :)C'est super, mais je veux quand même taper les commandes au clavier. Et, il y a un autre problème - notre interprète ne comprend que les nombres 0, 1, 2, 3, 4 et 8 (qui sont définis comme des constantes). Qu'apprendrait-il pour comprendre des nombres, vous avez besoin du mot «nombre». De la même manière que pour le mot find, je n'utiliserai pas le buffer. Le mot "nombre?" prendra deux paramètres sur la pile - l'adresse de la chaîne et la longueur. En cas de succès, il renverra le numéro reçu et le drapeau 1. Si la conversion échoue, il y aura un numéro sur la pile: 0.Le code s'est avéré long, mais plutôt simple et linéaire: 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
Modifier interpréter. Si le mot n'est pas dans le dictionnaire, nous essaierons de l'interpréter comme un nombre: # : 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
Et me voilà! Déboguez un tel bytecode dans l'assembleur, sans points d'arrêt dans le bytecode, sans la possibilité de simplement «avancer» le long du bytecode ... De plus, avec pas les mouvements les plus faciles sur la pile, et sans la simple capacité de visualiser le contenu de la pile ... Et sur GDB, où juste la ligne de commande ... Je vais vous dire - c'est juste une explosion de cerveau! Pas pire. C'est une EXPLOSION DU CERVEAU !Mais ... nous sommes des Indiens, nous trouverons toujours des solutions :)En général, j'ai trouvé cette solution: j'ai implémenté une commande pour afficher le contenu de la pile - «s». La commande n'est pas la plus simple, mais toujours plus facile à interpréter. Et, comme il est apparu, ochchchen utile. Le voici: # : .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
À droite, j'ai donné un exemple du contenu de la pile, après l'exécution de chaque commande. Bien sûr, il y a un cycle, et ce n'est que la première passe. Mais les autres sont très similaires, seule la valeur en haut de la pile change. Après une telle «trace», l'équipe a immédiatement gagné!Pour le débogage, j'ai créé les macros suivantes: .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm
Utilisé en insérant aux bons endroits de cette façon: item interpret interpret: .byte b_blword prs .byte b_dup prs .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over ......
En conséquence, le premier lancement a produit la sortie suivante: $ ./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!
Chaque mouvement sur la pile peut être vu clairement. Il fallait faire ça plus tôt :)Je suis allé plus loin en faisant une autre macro de débogage: .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm
En conséquence, il est devenu possible de le faire: 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
Et obtenez ceci: $ ./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!
C'était une tentative d'interprétation de la chaîne «20 30 *».Et vous pouvez afficher les numéros de ligne source ... D'accord, peut-être alors ...Bien sûr, c'est une technique de journalisation classique pour le débogage, mais quelque chose dont je ne me souvenais pas immédiatement.En général, à la suite du débogage, j'ai trouvé une pile à l'étranger. C'est l'opposé du débordement quand ils essaient d'en prendre plus qu'ils n'en mettent. Ajout de son contrôle à ".s".Avec l'aide de nouvelles macros, le débogage a été rapide. Au fait, avant cela, j'ai publié un bytecode par ligne. Mais l'assembleur vous permet de placer plusieurs octets dans une chaîne, pourquoi ne pas l'utiliser.Terminons le mot interpréter en ajoutant deux vérifications: que le mot n'a pas été converti en nombre et quitter la pile à l'étranger. Par conséquent, interpréter est le suivant: 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
Soit dit en passant, il convient de noter que la commande quit vide désormais les piles et recommence l'interprétation sans modifier l'état du tampon. Ainsi, l'interprétation continue, mais avec des piles «fraîches». Nous allons corriger cela un peu plus tard.Il ne reste plus qu'à organiser la saisie au clavier.Entrée clavier
La saisie au clavier dans le fort est simple. Il y a le mot attend, il prend deux paramètres - l'adresse du buffer et sa taille. Ce mot effectue la saisie au clavier. Le nombre réel de caractères saisis est placé dans la variable span. Faisons ces mots. Nous allons entrer à partir de l'entrée standard. .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
Nous devons maintenant créer un tampon d'entrée clavier. Soit 256 caractères.Faisons-le à la place de la ligne de test précédente. inbuf_size = 256 inbuf: .byte b_var0 .space inbuf_size
Et nous modifions quit, ainsi que le bytecode de départ. Définissez la variable tib sur le tampon d'entrée inbuf, appelez expect, puis copiez la valeur de span dans #tib. La variable> in est annulée; nous appelons interpréter. Et donc nous répétons dans un cycle. Il y a des babioles - pour ajouter une invite de saisie et ce serait bien d'afficher l'état de la pile (et nous avons déjà une commande prête à l'emploi pour cela!). Après plusieurs itérations, nous avons obtenu le code suivant (commande start et quit): # 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 - .
Et voici le résultat: $ ./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! $
Tout ce qui se trouve après le symbole ">" est ma saisie au clavier. Le reste est la réponse du système. J'ai joué un peu avec les commandes, en tapant sur le clavier. Il a effectué plusieurs opérations de pile, calculé le nombre de secondes en jours.Résumé
L'interprète est complet et fonctionne. Et dit poliment au revoir - à lui "au revoir" et il "au revoir" :)Comme une invitation - le contenu de la pile arithmétique. Le premier nombre entre parenthèses est la taille de la pile, puis le contenu et l'invite pour entrer ">". Vous pouvez entrer toutes les commandes implémentées (j'ai compté 76 commandes). Certes, beaucoup n'ont de sens que pour le compilateur - par exemple, les littéraux, les transitions, les commandes d'invocation.Source complète (environ 1300 lignes) .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
Le code source s'agrandit, donc je l'apporte ici pour la dernière fois.Maintenant, son lieu de résidence sera sur le github: https://github.com/hal9000cc/forth64Au même endroit, dans le dossier bin, vous pouvez trouver la version déjà compilée pour Linux x64. Qui a Linux, vous pouvez télécharger et exécuter.Et qui a Windows - vous pouvez installer WSL (Windows Subsystem for Linux). Je partais pour les vacances et je l'ai fait. Cela s'est avéré être très simple, cela a pris environ 5 minutes. Il n'y a eu qu'un instant, il n'a pas démarré tout de suite, le sous-système a dû être "activé" via la commande PowerShell. Suivi le lien du message d'erreur, exécuté la commande et cela a fonctionné.Mais il y a aussi un moyen pour les vrais Indiens de tout faire fonctionner sous Windows :) Ce n'est pas difficile à faire, il suffit de refaire quelques mots qui interagissent avec le système.C’est tout! La prochaine fois, nous exécuterons le compilateur.Il y aura l'occasion de compiler de nouveaux mots, il y aura des conditions, des cycles. En fait, il sera possible d'écrire sur un fort plus ou moins standard, de le compiler en code octet et de l'exécuter. Eh bien, il sera possible d'effectuer des tests plus sérieux, de vérifier les performances de la machine à octets.Suite: Byte-machine pour le fort (et pas seulement) en amérindien (partie 4)