Byte-Maschine für das Fort (und nicht nur) in Native American (Teil 2)

Bild

Lassen Sie uns die Experimente mit Bytecode fortsetzen. Dies ist eine Fortsetzung des Artikels über Byte-Machine in Assembler, hier ist der erste Teil .

Im Allgemeinen plante ich, im zweiten Teil einen Fort-Interpreter und im dritten den Fort-Compiler für diese Byte-Maschine zu erstellen. Das für den Artikel erhaltene Volumen war jedoch sehr groß. Um den Interpreter zu erstellen, müssen Sie den Kernel (eine Reihe von Bytebefehlen) erweitern und Folgendes implementieren: Variablen, Analysieren von Zeichenfolgen, Eingeben von Zeichenfolgen, Wörterbüchern, Suchen von Wörterbüchern ... Nun, zumindest sollte die Ausgabe von Zahlen funktionieren. Aus diesem Grund habe ich beschlossen, den Artikel über den Dolmetscher in zwei Teile zu teilen. Daher werden wir in diesem Artikel den Kernel erweitern, die Variablen bestimmen und die Ausgabe von Zahlen zeichnen. Das Folgende ist ein Beispielplan: Der 3. Teil ist der Interpreter, der 4. Teil ist der Compiler. Und natürlich Leistungstests. Sie werden im 4. oder 5. Artikel sein. Diese Artikel werden nach dem neuen Jahr sein.

Und wer hat noch keine Angst vor dem schrecklichen Assembler und Bytecode - willkommen zum Schnitt! :) :)

Beheben Sie zunächst die Fehler. Stellen wir die Dateierweiterung .s ein, wie es für GAS üblich ist (danke mistergrim ). Ersetzen Sie dann int 0x80 durch syscall und verwenden Sie 64-Bit-Register (danke qw1 ). Am Anfang habe ich die Anrufbeschreibung nicht sorgfältig gelesen und nur die Register korrigiert ... und einen Segmentierungsfehler erhalten. Es stellt sich heraus, dass sich für syscall alles geändert hat, einschließlich der Rufnummern. sys_write für syscall ist die Nummer 1 und sys_exit ist 60. Infolgedessen haben die Befehle bad, type und bye die folgende Form angenommen:

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 

Und noch etwas. Zu Recht haben berez und fpauk in den Kommentaren zum letzten Artikel geschrieben, dass der Bytecode von der Plattform abhängt, wenn die Prozessoradressen im Bytecode verwendet werden. In diesem Beispiel wurde die Zeilenadresse für „Hallo Welt!“ In Bytecode nach Wert festgelegt (mit dem Befehl lit64). Dies ist natürlich nicht erforderlich. Dies war jedoch der einfachste Weg, um die Byte-Maschine zu überprüfen. Ich werde das nicht mehr tun, aber ich werde die Adressen von Variablen auf andere Weise erhalten: insbesondere mit dem Befehl var (dazu später mehr).

Aufwärmen


Und jetzt werden wir zum Aufwärmen alle grundlegenden ganzzahligen arithmetischen Operationen (+, -, *, /, mod, / mod, abs) ausführen. Wir werden sie brauchen.

Der Code ist so einfach, dass ich ihn kommentarlos in einen Spoiler bringe.

Arithmetik
 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 

Traditionell werden in Fort Operationen mit doppelter Genauigkeit zu den üblichen Arithmetik- und Stapeloperationen hinzugefügt. Wörter für solche Operationen beginnen normalerweise mit einer „2“: 2DUP, 2SWAP usw. Aber wir haben bereits 64-Bit-Standardarithmetik und werden heute definitiv keine 128 machen :)

Als nächstes fügen wir die grundlegenden Stapeloperationen hinzu (Drop, Swap, Root, -root, Over, Pick, Roll).

Stapeloperationen
 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 

Und wir werden auch Befehle zum Lesen und Schreiben in das Gedächtnis geben (Fort's Wörter @ und!). Sowie ihre Gegenstücke zu einer anderen Bittiefe.

Lesen und Schreiben in Erinnerung
 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 

Möglicherweise brauchen wir noch Vergleichsteams, wir werden sie auch machen.

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

Wir werden den Betrieb nicht testen. Die Hauptsache ist, dass der Assembler beim Kompilieren keine Fehler ausgibt. Das Debuggen wird gerade verwendet.

Machen Sie sofort die Worttiefe (Stapeltiefe). Speichern Sie dazu beim Start die Anfangswerte des Datenstapels und des Rückgabestapels. Diese Werte können beim Neustart des Systems weiterhin nützlich sein.

 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 

Zahlenausgabe


Nun, das Aufwärmen ist vorbei und du musst ein wenig schwitzen. Bringen Sie unserem System bei, Zahlen auszugeben. Das Wort "." (Zeitraum). Machen wir es so, wie es in Standard-Fort-Implementierungen gemacht wird, indem wir die Wörter <#, hold, #, #s, #>, base verwenden. Muss all diese Worte erkennen. Um eine Zahl zu bilden, werden ein Puffer und ein Zeiger auf das zu bildende Zeichen verwendet. Dies sind die Wörter holdbuf und holdpoint.

Also brauchen wir diese Worte:

  • holdbuf - Puffer zur Erzeugung einer Darstellung der Zahl, die Bildung erfolgt ab dem Ende
  • Haltepunkt - Adresse zum zuletzt angezeigten Zeichen (in holdbuf)
  • <# - der Beginn der Bildung einer Zahl; setzt den Haltepunkt nach dem letzten Byte holdbuf auf Byte
  • hold - Reduziert den Holdpoint um 1 und speichert das Zeichen vom Stapel im Puffer an der empfangenen Adresse
  • # - teilt das Wort am oberen Rand des Stapels in die Basis des Zahlensystems, übersetzt den Rest der Teilung in ein Zeichen und speichert es mit hold im Puffer
  • #s - konvertiert das ganze Wort; Ruft das Wort # tatsächlich in einer Schleife auf, bis 0 auf dem Stapel verbleibt
  • #> - Abschluss der Konvertierung; schiebt den Anfang der Saite und ihre Länge auf den Stapel

Wir werden alle Wörter in Bytecode machen, aber zuerst wollen wir uns mit den Variablen befassen.

Variablen


Und hier wird es ein wenig Fortianische Magie geben. Tatsache ist, dass in einer Festung eine Variable ein Wort ist. Wenn dieses Wort ausgeführt wird, befindet sich die Adresse der Speicherzelle, in der der Wert der Variablen gespeichert ist, auf dem Stapel. Sie können an diese Adresse lesen oder schreiben. Um beispielsweise den Wert 12345 in die Variable A zu schreiben, müssen Sie die folgenden Befehle ausführen: "12345 A!". In diesem Beispiel wird 12345 auf den Stapel geschoben, dann schiebt die Variable A ihre Adresse und das Wort "!" Entfernt zwei Werte aus dem Stapel und schreibt 12345 in die Adresse der Variablen A. In typischen Implementierungen des Forts (mit direktem Code) sind die Variablen ein CALL-Mikroprozessorbefehl mit der Adresse _next, wonach ein Platz zum Speichern des Werts der Variablen reserviert wird. Wenn ein solches Wort ausgeführt wird, überträgt der Mikroprozessor die Steuerung an _next und schiebt die Rücksprungadresse (über RSP) auf den Stapel. Aber in der Festung ist der Mikroprozessorstapel arithmetisch und wir werden nirgendwo zurückkehren. Infolgedessen wird die Ausführung fortgesetzt, und auf dem Stapel befindet sich die Adresse der Variablen. Und das alles mit einem Prozessorteam! In Assembler würde es so aussehen:

  call _next #   _next,      ,   12345 .quad 12345 

Aber wir haben einen Bytecode und können diesen Mechanismus nicht verwenden! Ich habe nicht sofort herausgefunden, wie man einen solchen Mechanismus auf Bytecode macht. Wenn Sie jedoch logisch denken, beeinträchtigt dies nicht die Implementierung von etwas sehr Ähnlichem. Denken Sie daran, dass dies kein Prozessorbefehl ist, sondern ein Bytecode, genauer gesagt eine „Unterroutine“ für einen Bytecode. Hier ist die Erklärung des Problems:

  • Dies ist ein Bytecode, wenn die Steuerung übertragen wird, zu der sie sofort zurückkehren soll
  • Nach der Rückgabe sollte die Adresse, an der der Wert der Variablen gespeichert ist, im arithmetischen Stapel verbleiben

Wir haben einen Exit-Byte-Befehl. Lassen Sie uns ein Wort auf den Bytecode setzen, der einen einzelnen Exit-Befehl enthält. Dann kehrt dieser Befehl von ihm zurück. Es bleibt derselbe Befehl, der zusätzlich die Adresse des nächsten Bytes auf dem Stapel (Register R8) drückt. Wir werden dies als zusätzlichen Einstiegspunkt tun, um den Übergang zu speichern:

 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] 

Jetzt sieht die Basisvariable folgendermaßen aus:
 base: .byte b_var0 .quad 10 

Übrigens, warum genau var0 und nicht nur var? Tatsache ist, dass es andere Befehle geben wird, um fortgeschrittenere Wörter zu identifizieren, die Daten enthalten. Ich werde in den folgenden Artikeln ausführlicher beschreiben.

Jetzt sind wir alle bereit, Zahlen zu ziehen. Fangen wir an!

Wortbasis, Holdbuf, Holdpoint


Wie die Variablen angeordnet werden, wurde bereits entschieden. Daher werden die Wörter base, holdbuf, holdpoint wie folgt erhalten:

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

Die Größe des Holdbuf-Puffers wird auf 70 gewählt. Die maximale Anzahl von Bits einer Zahl beträgt 64 (wenn Sie ein Binärsystem auswählen). Eine weitere Reserve von mehreren Zeichen wurde vorgenommen, um beispielsweise das Zeichen einer Zahl und ein Leerzeichen danach zu platzieren. Wir werden nach Pufferüberlauf suchen, aber im Moment werden wir keine zusätzlichen Zeichen in den Puffer einfügen. Dann kann eine weitere Diagnose gestellt werden.

halten


Jetzt können Sie das Wort halten. Auf dem Fort sieht der Code folgendermaßen aus:

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

Für diejenigen, die das Fort zum ersten Mal sehen, werde ich den Code im Detail analysieren. Für nachfolgende Wörter werde ich dies nicht tun.

Am Anfang steht ein Wort zur Definition neuer Wörter und ein Name eines neuen Wortes: ": hold". Danach kommt der Code, der mit dem Wort ";" endet. Lassen Sie uns den Code des Wortes analysieren. Ich werde den Befehl und den Status des Stapels nach Ausführung des Befehls geben. Bevor das Wort aufgerufen wird, befindet sich auf dem Stapel ein Zeichencode, der im Puffer abgelegt wird (angezeigt durch <Zeichen>). Weiter stellt sich heraus, dass:

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

Danach folgt der Befehl if, der zu einem bedingten Sprung zu einer Folge von Befehlen zwischen else und then kompiliert wird. Ein bedingter Zweig entfernt das Vergleichsergebnis aus dem Stapel und führt den Übergang durch, wenn eine Lüge auf dem Stapel vorhanden war. Wenn es keinen Übergang gab, wird der Zweig zwischen if und else ausgeführt, in dem zwei Drop-Befehle vorhanden sind, mit denen das Zeichen und die Adresse entfernt werden. Andernfalls wird die Ausführung fortgesetzt. Wort "!" speichert den neuen Wert im Haltepunkt (die Adresse und der Wert werden vom Stapel entfernt). Und das Wort "c!" schreibt ein Zeichen in den Puffer, dies ist der Befehl set8 byte (die Adresse und der Wert des Zeichens werden vom Stapel entfernt).

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

Hier ist, wie viele Aktionen diese kurze Befehlsfolge ausführt! Ja, das Fort ist prägnant. Und jetzt schalten wir den manuellen "Compiler" im Kopf ein :) Und wir kompilieren das alles in 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 # ; 

Hier habe ich lokale Labels (0 und 1) verwendet. Auf diese Beschriftungen kann unter speziellen Namen zugegriffen werden. Zum Beispiel kann auf das Label 0 unter den Namen 0f oder 0b zugegriffen werden. Dies bedeutet eine Verknüpfung zur nächsten Bezeichnung 0 (vorwärts oder rückwärts). Sehr praktisch für Etiketten, die lokal verwendet werden, um keine unterschiedlichen Namen zu finden.

Wort #


Lassen Sie uns das Wort # machen. Auf dem Fort sieht der Code folgendermaßen aus:

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

Die Bedingung hier wird verwendet, um zu überprüfen: Ist die empfangene Zahl kleiner als zehn? Wenn weniger, werden die Ziffern 0–9 verwendet, andernfalls werden Zeichen verwendet, die mit „A“ beginnen. Dies ermöglicht die Arbeit mit einem Hexadezimalzahlensystem. Die Sequenz c "0 schiebt den Zeichencode 0 auf den Stapel. Wir schalten den" Compiler "ein:

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

Wort <#


Das Wort <# ist sehr einfach:

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

Wort #>


Das Wort #> zum Abschließen der Konvertierung sieht folgendermaßen aus:

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

Wort #s


Und schließlich das Wort #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 

Jeder, der vorsichtig ist, wird eine leichte Diskrepanz zwischen dem Bytecode und dem Fort-Code bemerken :)

Alles ist fertig


Jetzt hindert Sie nichts mehr daran, das Wort "." Zu schreiben, das eine Zahl anzeigt:

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

Lassen Sie uns einen Testbytecode erstellen, der unseren Punkt überprüft:

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

Natürlich hat es nicht auf einmal funktioniert. Nach dem Debuggen wurde jedoch das folgende Ergebnis erhalten:

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

Der Pfosten ist sofort sichtbar. Nach der Nummer sollte das Fort ein Leerzeichen anzeigen. Fügen Sie nach dem Aufruf von conv_start (<#) den Befehl 32 hold hinzu.

Lassen Sie uns eine Schlussfolgerung aus dem Zeichen ziehen. Fügen Sie zu Beginn dup abs hinzu und überprüfen Sie am Ende das Vorzeichen der linken Kopie und setzen Sie das Minus, wenn die Zahl negativ ist (0 <wenn c ″ - dann halten). Infolgedessen wird das Wort "." nimmt diese Form an:

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

Geben Sie in der Startsequenz der Bytebefehle eine negative Zahl ein und überprüfen Sie:

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

Es gibt eine Schlussfolgerung aus Zahlen!

Vollständige Quelle
 .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 

Zusammenfassung


Jetzt haben wir einen ziemlich anständigen Kern an Bytebefehlen: alle Grundrechenarten, Stapeloperationen, Vergleichsoperationen, Arbeiten mit Speicher, Variablen. Außerdem gibt es bereits die Ausgabe von Zahlen, die vollständig im Bytecode implementiert sind. Alles ist bereit für den Dolmetscher, was wir im nächsten Artikel tun werden!

Frohes Neues Jahr an alle!

Kritik ist willkommen! :) :)

Fortsetzung: Byte-Maschine für das Fort (und nicht nur) in Native American (Teil 3)

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


All Articles