Octet-machine pour le fort (et pas seulement) en amérindien (partie 2)

image

Continuons les expériences avec le bytecode. Il s'agit d'une suite de l'article sur l'octet-machine dans l'assembleur, voici la première partie .

En général, je prévoyais de faire un interprète de fort dans la deuxième partie, et le compilateur de fort pour cette machine d'octets dans la troisième. Mais le volume obtenu pour l'article était très important. Pour faire l'interpréteur, vous devez développer le noyau (un ensemble de commandes d'octets), et implémenter: des variables, analyser des chaînes, saisir des chaînes, des dictionnaires, rechercher des dictionnaires ... Eh bien, au moins la sortie des nombres devrait fonctionner. En conséquence, j'ai décidé de diviser l'article sur l'interprète en deux. Par conséquent, dans cet article, nous allons développer le noyau, déterminer les variables, dessiner la sortie des nombres. Voici un exemple de plan: la 3ème partie est l'interpréteur, la 4ème partie est le compilateur. Et, bien sûr, des tests de performances. Ils seront dans le 4ème ou le 5ème article. Ces articles seront après la nouvelle année.

Et qui n'a pas encore eu peur du terrible assembleur et du bytecode - bienvenue dans la coupe! :)

Tout d'abord, corrigez les erreurs. Définissons l'extension de fichier .s, comme c'est la coutume pour GAS (merci mistergrim ). Ensuite, remplacez int 0x80 par syscall et utilisez des registres 64 bits (merci qw1 ). Au début, je n'ai pas lu attentivement la description de l'appel et corrigé uniquement les registres ... et j'ai eu un défaut de segmentation. Il s'avère que tout a changé pour syscall, y compris les numéros d'appel. sys_write pour syscall est le numéro 1 et sys_exit est 60. Par conséquent, les commandes bad, type et bye ont pris la forme suivante:

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

Et encore une chose. À juste titre, dans les commentaires du dernier article, berez et fpauk ont ​​écrit que si les adresses de processeur sont utilisées dans le bytecode, alors le bytecode dépend de la plate-forme. Et dans cet exemple, l'adresse de ligne pour «Hello, world!» A été définie en bytecode par valeur (avec la commande lit64). Bien sûr, ce n'est pas nécessaire. Mais c'était le moyen le plus simple de vérifier la machine d'octets. Je ne le ferai plus, mais j'obtiendrai les adresses des variables par d'autres moyens: en particulier, avec la commande var (plus à ce sujet plus tard).

RĂ©chauffer


Et maintenant, comme échauffement, nous allons faire toutes les opérations arithmétiques de base entières (+, -, *, /, mod, / mod, abs). Nous en aurons besoin.

Le code est si simple que je l'apporte dans un spoiler sans commentaire.

Arithmétique
 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 

Traditionnellement, dans le fort, les opérations de double précision s'ajoutent aux opérations arithmétiques et de pile habituelles. Les mots pour de telles opérations commencent généralement par un «2»: 2DUP, 2SWAP, etc. Mais nous avons déjà l'arithmétique standard 64 bits, et nous n'en ferons certainement pas 128 aujourd'hui :)

Ensuite, nous ajoutons les opérations de base de la pile (drop, swap, root, -root, over, pick, roll).

Opérations de pile
 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 

Et nous ferons également des commandes pour lire et écrire dans la mémoire (les mots de Fort @ et!). Et aussi leurs homologues à une profondeur de bits différente.

Lire et écrire dans la mémoire
 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 

Nous aurons peut-ĂŞtre encore besoin d'Ă©quipes de comparaison, nous les ferons aussi.

Commandes de comparaison
 # 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 

Nous ne testerons pas les opérations. L'essentiel est que l'assembleur ne donne pas d'erreurs lors de la compilation. Le débogage sera en cours d'utilisation.

Faites immédiatement le mot profondeur (profondeur de pile). Pour ce faire, au démarrage, enregistrez les valeurs initiales de la pile de données et de la pile de retour. Ces valeurs peuvent toujours être utiles lors du redémarrage du système.

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

Sortie numérique


Eh bien, l'échauffement est terminé et vous devez transpirer un peu. Apprenez à notre système à sortir des nombres. Pour afficher les chiffres dans le fort, le mot "." (période). Faisons-le comme dans les implémentations Fort standard, en utilisant les mots <#, hold, #, #s, #>, base. Je dois réaliser tous ces mots. Pour former un nombre, un tampon et un pointeur vers le caractère à former sont utilisés, ce seront les mots holdbuf et holdpoint.

Donc, nous avons besoin de ces mots:

  • holdbuf - tampon pour gĂ©nĂ©rer une reprĂ©sentation du nombre, la formation se produit Ă  partir de la fin
  • holdpoint - adresse du dernier caractère affichĂ© (dans holdbuf)
  • <# - dĂ©but de la formation d'un nombre; dĂ©finit le point d'arrĂŞt sur octet, après le dernier octet holdbuf
  • hold - rĂ©duit le point de retenue de 1 et enregistre le caractère de la pile dans le tampon Ă  l'adresse reçue
  • # - divise le mot en haut de la pile Ă  la base du système numĂ©rique, traduit le reste de la division en caractère et l'enregistre dans le tampon en utilisant hold
  • #s - convertit le mot entier; appelle en fait le mot # dans une boucle jusqu'Ă  ce qu'il reste 0 sur la pile
  • #> - achèvement de la conversion; pousse le dĂ©but de la chaĂ®ne formĂ©e et sa longueur sur la pile

Nous ferons tous les mots en bytecode, mais d'abord, traitons les variables.

Variables


Et ici, il y aura un peu de magie Fortian. Le fait est que dans un fort une variable est un mot. Lorsque ce mot est exécuté, l'adresse de la cellule mémoire stockant la valeur de la variable est sur la pile. Vous pouvez lire ou écrire à cette adresse. Par exemple, pour écrire la valeur 12345 dans la variable A, vous devez exécuter les commandes suivantes: «12345 A!». Dans cet exemple, 12345 est poussé sur la pile, puis la variable A pousse son adresse, et le mot "!" supprime deux valeurs de la pile et écrit 12345 à l'adresse de la variable A. Dans les implémentations typiques du fort (avec code direct), les variables sont une commande de microprocesseur CALL avec l'adresse _next, après quoi une place est réservée pour stocker la valeur de la variable. Lors de l'exécution d'un tel mot, le microprocesseur transfère le contrôle à _next et pousse l'adresse de retour (via RSP) sur la pile. Mais dans le fort, la pile de microprocesseurs est arithmétique, et nous ne reviendrons nulle part. Par conséquent, l'exécution continue et sur la pile se trouve l'adresse de la variable. Et tout cela avec une seule équipe de processeurs! En assembleur, cela ressemblerait à ceci:

  call _next #   _next,      ,   12345 .quad 12345 

Mais nous avons un bytecode, et nous ne pouvons pas utiliser ce mécanisme! Je n'ai pas immédiatement compris comment réaliser un tel mécanisme sur bytecode. Mais, si vous pensez logiquement, cela n'interfère pas avec la mise en œuvre de quelque chose de très similaire. Gardez à l'esprit que ce ne sera pas une commande de processeur, mais un bytecode, plus précisément, un «sous-programme» sur un bytecode. Voici l'énoncé du problème:

  • il s'agit d'un code octet, lors du transfert du contrĂ´le auquel il doit immĂ©diatement revenir
  • après le retour, l'adresse oĂą la valeur de la variable est stockĂ©e doit rester dans la pile arithmĂ©tique

Nous avons une commande exit byte. Faisons un mot sur le bytecode contenant une seule commande de sortie. Ensuite, cette commande en reviendra. Il reste à faire la même commande, qui pousse en plus l'adresse de l'octet suivant sur la pile (registre R8). Nous ferons cela comme un point d'entrée supplémentaire pour quitter pour économiser sur la transition:

 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] 

Maintenant, la variable de base ressemblera Ă  ceci:
 base: .byte b_var0 .quad 10 

Au fait, pourquoi exactement var0, et pas seulement var? Le fait est qu'il y aura d'autres commandes pour identifier les mots plus avancés qui contiennent des données. Je décrirai plus en détail dans les articles suivants.

Maintenant, nous sommes tous prêts à tirer des nombres. Commençons!

Base de mots, holdbuf, holdpoint


La façon dont les variables seront organisées a déjà été décidée. Par conséquent, les mots base, holdbuf, holdpoint sont obtenus comme suit:

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

La taille du tampon holdbuf est sélectionnée 70. Le nombre maximum de bits d'un nombre est 64 (c'est si vous sélectionnez un système binaire). Une autre réserve de plusieurs caractères a été faite pour placer, par exemple, le signe d'un nombre et d'un espace après celui-ci. Nous allons vérifier le débordement de la mémoire tampon, mais pour l'instant nous ne mettrons pas de caractères supplémentaires dans la mémoire tampon. Il sera alors possible de poser un autre diagnostic.

tenir


Vous pouvez maintenant faire tenir le mot. Sur le fort, son code ressemble Ă  ceci:

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

Pour ceux qui voient le fort pour la première fois, je vais analyser le code en détail. Pour les prochains mots, je ne le ferai pas.

Au début, il y a un mot pour la définition de nouveaux mots et un nom pour un nouveau mot: ": hold". Après cela vient le code qui se termine par le mot ";". Analysons le code du mot. Je donnerai la commande et l'état de la pile après avoir exécuté la commande. Avant l'appel du mot, il y a un code de caractère sur la pile, qui est placé dans le tampon (indiqué par <caractère>). De plus, il s'avère comme ceci:

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

Après cela se trouve la commande if, qui se compile en un saut conditionnel vers une séquence de commandes entre else et then. Une branche conditionnelle supprime le résultat de la comparaison de la pile et effectue la transition s'il y avait un mensonge sur la pile. S'il n'y a pas eu de transition, la branche entre if et else est exécutée, dans laquelle il y a deux commandes de suppression qui suppriment le caractère et l'adresse. Sinon, l'exécution continue. Le mot "!" enregistre la nouvelle valeur dans le point d'arrêt (l'adresse et la valeur sont supprimées de la pile). Et le mot "c!" écrit un caractère dans le tampon, il s'agit de la commande set8 byte (l'adresse et la valeur du caractère sont supprimées de la pile).

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

Voici combien d'actions cette courte séquence de commandes fait! Oui, le fort est concis. Et maintenant, nous activons le «compilateur» manuel dans la tête :) Et nous compilons tout cela en bytecode:
 hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_get # @ .byte b_wm # 1- .byte b_dup # dup .byte b_call8 .byte holdbuf - . - 1 # holdbuf .byte b_gt # > .byte b_qbranch8 # if .byte 0f - . .byte b_drop # drop .byte b_drop # drop .byte b_branch8 #     ( then) .byte 1f - . 0: .byte b_dup # dup .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_set # ! .byte b_set8 # c! 1: .byte b_exit # ; 

Ici, j'ai utilisé des étiquettes locales (0 et 1). Ces étiquettes sont accessibles par des noms spéciaux. Par exemple, l'étiquette 0 est accessible par les noms 0f ou 0b. Cela signifie un lien vers l'étiquette 0 la plus proche (avant ou arrière). Assez pratique pour les étiquettes utilisées localement, afin de ne pas proposer de noms différents.

Mot #


Faisons le mot #. Sur le fort, son code ressemblera Ă  ceci:

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

La condition ici est utilisée pour vérifier: le chiffre reçu est-il inférieur à dix? S'il est inférieur, les chiffres de 0 à 9 sont utilisés, sinon les caractères commençant par «A» sont utilisés. Cela permettra de travailler avec un système de nombres hexadécimal. La séquence c ″ 0 pousse le code de caractère 0 sur la pile. Nous activons le «compilateur»:

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

Mot <#


Le mot <# est très simple:

 : <# holdbuf 70 + holdpoint ! ; 

Bytecode:

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

Mot #>


Le mot #> pour terminer la conversion ressemble Ă  ceci:

 : #> holdpoint @ holdbuf 70 + over - ; 

Bytecode:

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

N ° de mot


Et enfin, le mot #s:

 : #s do # dup 0= until ; 

Bytecode:

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

Quiconque fait attention remarquera une légère différence entre le bytecode et le code fort :)

Tout est prĂŞt


Maintenant, rien ne vous empĂŞchera de faire le mot ".", Qui affiche un nombre:

 : . <# #s drop #> type ; 

Bytecode:

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

Faisons un bytecode de test qui vérifie notre point:

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

Bien sûr, cela n'a pas fonctionné d'un coup. Mais, après le débogage, le résultat suivant a été obtenu:

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

Le montant est visible immédiatement. Après le numéro, le fort doit afficher un espace. Ajoutez après conv_start (<#) appelez la commande 32 hold.

Tirons une conclusion du signe. Au début, ajoutez dup abs, et à la fin, vérifiez le signe de la copie de gauche et mettez le moins si le nombre est négatif (0 <si c ″ - maintenez alors). En conséquence, le mot "." prend cette forme:

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

Bytecode:

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

Dans la séquence de démarrage des commandes d'octets, mettez un nombre négatif et vérifiez:

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

Il y a une conclusion de chiffres!

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

Résumé


Nous avons maintenant un noyau décent de commandes d'octets: toutes les opérations arithmétiques de base, les opérations de pile, les opérations de comparaison, le travail avec la mémoire, les variables. De plus, il existe déjà la sortie des nombres, entièrement implémentée en bytecode. Tout est prêt pour l'interprète, c'est ce que nous ferons dans le prochain article!

Bonne année à tous!

La critique est la bienvenue! :)

Suite: Octet-machine pour le fort (et pas seulement) en amérindien (partie 3)

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


All Articles