ネイティブアメリカンの砦のバイトマシン(だけでなく)(パート3)

画像



2019年が来て、年末年始は終わりに近づいています。 バイト、コマンド、変数、ループを思い出し始めましょう...



これらの祝日ですでに忘れていたもの。 一緒に覚えておく必要があります!



今日は、バイトマシン用のインタープリターを作成します。 これは3番目の記事で、最初のパートはパート1パート2です。



みなさん、明けましておめでとう、猫へようこそ!



まず、 fpaukからの質問に答えます。 これらの質問は間違いなく正しいです。 現在、このバイトマシンのアーキテクチャは、ダイレクトプロセッサアドレスで動作するようになっています。 しかし、これらのアドレスはバイトコードではなく、システムの起動後に形成されます。 システムの起動後、任意のポインターを作成でき、このコードはどのプラットフォームでも正常に機能します。 たとえば、変数または配列のアドレスは、var0コマンドで取得できます。 このコマンドはどのプラットフォームでも機能し、このプラットフォーム固有の正しいアドレスを返します。 その後、このアドレスを自由に操作できます。



それでも、 fpaukは正しい。 アドレスをバイトコードに保存することはできません。 プラットフォームに依存しないコードを作成できることがわかりましたが、そのためにはいくつかの努力が必要です。 特に、アドレスがバイトコードに含まれていないことを確認してください。 また、たとえば、コンパイルされたコードをファイルに保存すると、それらが取得できます。 これにはデータが含まれ、アドレスにすることができます。 たとえば、ここの変数、コンテキストなどの値。



このような問題を取り除くには、アドレスを仮想化する必要があります。 x86プロセッサのアドレス指定は非常に強力であり、ほとんどの場合、追加のコマンドも追加されません。 それでも、絶対アドレスを使用して、現在のアーキテクチャを継続します。 そして、テストを開始すると、アドレスを仮想アドレスに再実行して、これがパフォーマンスにどのように影響するかを確認できます。 これは面白いです。



ウォームアップ



そして今、少しトレーニング。 小さくても便利なバイトコマンドのバッチをもう1つ作成しましょう。 これらは、コマンドnip、emit、1 +、+!、-!、count、リターンスタックr>、> r、r @、文字列リテラル( ")、および定数ワード1、2、3、4、 8.コマンドの表にそれらを含めることを忘れないでください。



これらのコマンドのコードは次のとおりです
b_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
      
      







nipコマンドは、スタックの最上部の下にある単語を削除します。 これは、スワップドロップコマンドと同等です。 これは役立つ場合があります。



emitコマンドは、スタックから1文字をプッシュします。 同じシステムコール番号1を使用し、文字は長さ1のバッファに配置されます。



countコマンドは非常に単純です-スタックからカウンターのある行のアドレスを取得し、2つの値に変換します-カウンターのない行のアドレスと長さ。



b_2r、b_r2、b_rgetコマンドは、フォートワードr>、> r、r @です。 最初の関数は戻りスタックから単語を取得し、算術スタックに配置します。 2番目は反対の操作を実行します。 3番目のものは戻りスタックから単語をコピーし、算術1に入れます。戻りスタックは変更されません。



b_setpおよびb_setmコマンドは+!という単語です。 および-!..スタックから値とアドレスを取得し、指定されたアドレスのワードを変更して、スタックに値を追加または削除します。



b_strコマンドには、任意の長さのパラメーター(カウンター付きの行)があります。 この行はコマンドバイトの後のバイトコードにあり、コマンドはこの行のアドレスをスタックにプッシュします。 実際、これは文字列リテラルです。



チームの他のメンバーはコメントを必要としないと思います。



また、定数文字列(。 ")を出力するコマンドを作成します。次のように、入力するエントリポイントとして実装します。



 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
      
      





このコマンドは、b_strと同様に構成されています。 彼女だけがスタックに何も置きません。 パラメータとしてこのコマンドの後ろにある行は、単にユーザーに表示されます。



ウォームアップは終了しました。もっと深刻なものの時が来ました。 ワードジェネレーターと他のvarコマンドを扱いましょう。



ワードジェネレーター



変数を思い出してください。 既知のバイトコードレベルでの配列の配置(var0コマンド)。 新しい変数を作成するために、フォートは次の構成を使用します。



 variable < >
      
      





このシーケンスを実行すると、新しい単語<変数名>が作成されます。 この新しいワードの実行により、スタック上のアドレスがプッシュされ、変数の値が保存されます。 フォートには定数もあり、次のように作成されます。



 <> constant < >
      
      





定数を作成した後、単語<定数名>を実行すると、スタック<値>に配置されます。



したがって、単語変数と単語定数はどちらも生成語です。 新しい単語を作成するように設計されています。 要塞では、そのような単語はcreate ... does>構造を使用して記述されます。



変数と定数は次のように定義できます。



 : variable create 0 , does> ; : constant create , does> @ ;
      
      





これはどういう意味ですか?



単語createは、実行されると、入力ストリームから実行されたときに取得する名前で新しい単語を作成します。 作成後、単語が実行する前に単語のシーケンスが実行されます>。 しかし、この単語の実行の瞬間に、does>の後に書かれていることが実行されます。 同時に、データアドレスは既にスタック上にあります(フォートの「データフィールド」で言うように)。



したがって、変数を作成すると、シーケンス「0」が実行されます。これは、ゼロ充填のマシンワードの予約です。 そして、作成された単語が実行されるとき、何も行われません(後は何もありません)。 値が格納されるメモリアドレスは、単にスタックに残ります。



定数の定義では、単語はスタック上にある値を埋めて予約されています。 作成された単語が実行されると、「@」が実行され、指定されたアドレスの値が取得されます。



次に、作成した単語をどのように配置できるかを考えてみましょう。 データアドレスをスタック(var0など)にプッシュし、特定のアドレスであるバイトコードに制御を移します。 var0コマンドはすぐに戻ります。 しかし、この場合、リターンではなく、実際に移行を行う必要があります。



もう一度、何をする必要があるかを定式化します。





制御を別のバイトコードアドレスに転送するだけで、最初に次のバイト(R8)のアドレスをスタックに配置する必要があることがわかります。



それはほとんど分岐コマンドです! そして、ここで彼女は一人ではありません。 すでにbranch8とbranch16があります。 新しいvar8およびvar16コマンドに名前を付け、これらを分岐コマンドへの単なるエントリポイントにします。 移行チームへの移行を保存します:)したがって、次のようになります。



 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
      
      





良い方法では、var32コマンドも動作し、var64も動作します。 通常の遷移はそれほど長くないため、このような長い遷移はありません。 しかし、varコマンドの場合、これは非常に現実的なケースです。 しかし、今のところ、これらのコマンドは実行しません。 必要に応じて、後で行います。



単語ジェネレーターが整理されています。 辞書を決める番でした。



語彙



通常、フォートディクショナリーについて単純に話すと、ディクショナリエントリの単方向リストの形式で表示されます。 実際、砦は多くの辞書をサポートしているため、すべてが少し複雑です。 実際、それらは木です。 このようなツリー内の単語の検索は「シート」で始まります。これは現在の辞書の最後の単語です。 現在の辞書はコンテキスト変数によって定義され、最後の単語のアドレスは辞書の単語にあります。 別の変数は辞書を管理するために使用されます-それは新しい単語が追加される辞書を定義します。 したがって、1つの辞書を検索用にインストールし、別の辞書に新しい単語を含めることができます。



単純な場合、多くの辞書のサポートを行わないことも可能ですが、私は何も単純化しないことにしました。 実際、バイトコード、バイトマシンを理解するために、このセクションで説明されていることを知る必要はありません。 したがって、興味のない人は、このセクションをスキップできます。 さて、誰が詳細を知りたいですか-どうぞ!



最初は、forthという名前の基本的な辞書があります。 これは、そのような言葉があることを意味します。 この単語は「辞書」とも呼ばれ、多少の混乱があります。 したがって、単語に関しては、辞書の単語と呼びます。



この辞書を使用して新しい辞書が作成されます。



 vocabulary <  >
      
      





これにより、<created dictionary name>という名前の単語が作成されます。 実行されると、この単語は作成された辞書を検索の開始辞書として設定します。



実際、辞書の単語には、この辞書の最後の記事へのリンクがあり、そこから検索が開始されます。 そして実行時に、この辞書の単語はコンテキスト変数のデータフィールドへのリンクを書き込みます。



後で、語彙を作成することが可能になります。語彙は、現在の実装では要塞で非常に簡単に説明されています。



 : vocabulary create context @ , does> context ! ;
      
      





だから、言葉を前に作成します。 var8コマンドを使用します。 「コンテキスト!」バイトコードは、データフィールドの直後に配置されます。



 forth: .byte b_var8 .byte does_voc - . - 1 .quad 0 # <--      .      ,    -    . does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
      
      





ここで、辞書自体の作成に戻ります。



一般に、砦では、メモリ内の単語の説明は「辞書エントリ」と呼ばれます。 通常の用語では、記事のタイトルとそのコードがあります。 しかし、砦ではすべてがあまり一般的ではなく、「名前フィールド」、「通信フィールド」、「コードフィールド」、「データフィールド」と呼ばれます。 これが従来の用語で何を意味するのかをお話しします。



名前フィールドは、「カウンター付きの行」という単語の名前です。 古いパスカルのようです-文字列の長さのバイト、次に文字列。 リンクフィールドは、前の記事へのリンクです。 以前は単なるアドレスでしたが、プラットフォームに依存しないコードがあり、これはオフセットになります。 従来は砦にあったコードフィールドは、マシンコード(実装が直接シャイな場合)であり、カーネル外の単語に対しては_callが呼び出されていました。 バイトコードがあります。 また、データフィールドは、データを含む単語用です。たとえば、変数や定数用です。 ちなみに、単語辞書もそれを指します。



コンパイラには、まだフラグが必要です。 通常、砦にはただ1つのフラグが必要です-即時で、長いバイトに配置されます(別のフラグがある場合があります-非表示)。 ただし、これはコードフィールドに呼び出されたときにプロセッサ制御が転送される直接縫製コード用です。 しかし、私たちには異なる言葉があります-バイトコードとマシンコード、そして少なくとも2つ、さらには3つのフラグが必要です。



通信分野にはどれくらい必要ですか? 最初は、16ビットを使用したかった。 これは前の単語へのリンクであり、単語は間違いなく64 Kb未満です。 しかし、その単語にはほとんどすべてのサイズのデータ​​を含めることができることを思い出しました。 さらに、いくつかの辞書がある場合、リンクは多くの単語を通過できます。 ほとんどの場合、8ビットで十分ですが、16ビットと32ビットでもかまいません。4GBを超えるデータがある場合は、64ビットですらあります。 さて、すべてのオプションをサポートしましょう。 使用されるオプション-フラグを設定します。 少なくとも4つのフラグが判明します。即時属性、コアワード属性、および使用される通信フィールドのバリアントごとに2ビットです。 他の方法ではなく、フラグに別のバイトを使用する必要があります。



次のようにフラグを定義します。



 f_code = 0x80 f_immediate = 0x60
      
      





f_codeフラグはアセンブラーで記述されたカーネルワード用であり、f_immediateフラグはコンパイラーにとって有用です。これについては次の記事で説明します。 また、2つの最下位ビットにより、通信フィールドの長さ(1、2、4、または8バイト)が決まります。



したがって、記事のタイトルは次のようになります。





ここまでは、「マクロ」アセンブラーの機能を使用していません。 そして今、それらが必要です。 以下は、単語のタイトルを形成するための名前項目を持つそのようなマクロです。



 .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
      
      





このマクロは値p_itemを使用します-これは前の辞書エントリのアドレスです。 最後のこの値は、将来の使用のために更新されます:p_item = 9b。 ここで9bはラベルであり、数字ではなく、混同しないでください:)マクロには2つのパラメーターがあります-単語の名前とフラグ(オプション)。 マクロの開始時に、前の単語へのオフセットが計算されます。 次に、オフセットのサイズに応じて、目的のサイズのフラグと通信フィールドがコンパイルされます。 次に、名前の長さのバイトと名前自体。



次のように、最初の単語p_itemの前に定義します。



 p_item = .
      
      





ドットは、アセンブラーの現在のコンパイラーアドレスです。 この定義の結果、最初の単語はそれ自体を指します(通信フィールドは0になります)。 これは辞書の終わりの兆候です。



ところで、カーネルワードのコードフィールドには何が入りますか? 少なくともどこかにコマンドコードを保存する必要があります。 私は最も単純な道を進むことにしました。 カーネルワードには、バイトコードもあります。 ほとんどのチームでは、これは単なるバイトコマンドで、その後にb_exitが続きます。 したがって、インタープリターの場合、f_codeフラグを分析する必要はなく、そのためのコマンドは何の違いもありません。 全員のバイトコードを呼び出すだけです。



このオプションには別の利点があります。 パラメーター付きのコマンドの場合、安全なパラメーターを指定できます。 たとえば、Fort実装で直接縫製コードを使用してlitコマンドを呼び出すと、システムがクラッシュします。 そして、ここでは、たとえば、0が点灯し、このシーケンスは、スタックに0を置くだけで書き込まれます。 ブランチでも安全に行えます!



  .byte branch8 .byte 0f - . 0: .byte b_exit
      
      





このような呼び出しではオーバーヘッドが発生しますが、インタープリターにとっては重要ではありません。 そして、コンパイラはフラグを分析し、正確で高速なコードをコンパイルします。



もちろん、最初の単語は「forth」という単語になります。これが作成する基本的な辞書です。 ここでは、dos>の後にコードへのリンクを記載した便利なvarコマンドを入力してください。 前のセクションでこのコードを引用しましたが、見出しを付けて繰り返します。



 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
      
      





そして、すぐにコンテキスト変数を作成し、単語を検索するためにそれらを必要とします。



  item .byte b_var0 .quad 0 item context context: .byte b_var0 .quad 0
      
      





そして今、あなたは我慢して、アセンブラーで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
      
      





など...



チームがバイトコードで記述されると、さらに簡単になります。 次の例のように、バイトコードの直前に見出しを追加します。



  item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint ...
      
      





パラメーター付きのコマンドの場合、安全なパラメーターを作成します。 たとえば、Liteコマンドが数値Piを返すようにします。誰かがそれらをインタラクティブに呼び出すと、そのようなイースターがあります:)



  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
      
      





リストの最後の単語は、単語byeを象徴しています。 ただし、データフィールドのこのワードのアドレスを初期化する必要があります。 この単語のアドレスを取得するには、var0コマンドを使用します。



 last_item: .byte b_var0 item bye, f_code .byte b_bye
      
      





この設計では、バイトコードでアドレスlast_itemを呼び出すと、単語byeのアドレスを取得します。 ワードのデータフィールドに書き込むには、前に実行し、目的のアドレスがコンテキスト内にあります。 したがって、システム初期化コードは次のようになります。



 forth last_item context @ !
      
      





そして、インタプリタに直接進みましょう。 まず、入力バッファを操作して、そこから単語を抽出する必要があります。 砦の通訳は非常にシンプルであることを思い出させてください。 入力バッファから単語を順番に抽出し、見つけようとします。 単語が見つかった場合、インタープリターはそれを起動して実行します。



入力バッファと単語抽出



正直に言うと、砦の標準を勉強するのに多くの時間を費やしたくありません。 しかし、それでも私は、主にメモリから可能な限りそれらに近づけようとします。 砦の専門家がここで大きな矛盾を見つけた場合-書き込み、私はそれを修正します。



フォートには、バッファーを操作するための3つの変数、tib、#tib、およびinがあります。 tib変数は、入力バッファのアドレスをスタックにプッシュします。 変数#tibは、バッファー内の文字数をスタックにプッシュします。 また、variable> inには、入力バッファのオフセットが含まれています。このオフセットを超えると、生テキストが配置されます。 これらの変数を定義します。



  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
      
      





次に、blwordという単語を作成します。 この単語は、指定された変数を使用して、入力ストリームから次の単語を取得します。 スペースは区切り文字として使用され、コードはスペースより小さいすべての文字です。 この単語はアセンブラーにあります。 デバッグ後、次のようになりました。



 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
      
      





この単語は標準の単語に似ていますが、それとは異なり、すべての区切り文字を考慮し、単語をバッファにコピーしません。 スタック上の2つの値(アドレスと長さ)のみを返します。 単語を取得できない場合、0を返します。インタープリターの作成を開始するときが来ました。



単語検索と通訳



はじめに、単語を解釈させましょう。 この単語は、blworldを使用してバッファから新しい単語を選択し、辞書で検索して実行します。 そして、バッファが使い果たされるまで繰り返されます。 単語を検索する機能はまだないため、typeを使用してバッファから単語を出力するだけのテストスタブを作成します。 これにより、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
      
      





今、言葉をやめる。 通常、彼らは砦システムを実装するときにこれを行います:彼らは、インタプリタモードに入るために、単語quitまたはabortを使用します。 quitという単語はスタックをフラッシュし、バッファの入力と解釈の無限ループを開始します。 私たちと一緒に解釈するだけの呼び出しになります。 この単語のコードは2つの部分で構成されます。 最初の部分はアセンブラーにあり、2番目の部分はバイトコードにあります。 最初の部分:



 b_quit = 0xF1 bcmd_quit: lea r8, quit mov sp, init_stack mov bp, init_rstack jmp _next
      
      





第二部:



 quit: .byte b_call16 .word interpret - . - 2 .byte b_bye
      
      





通常、アセンブラコードは.textセクションに配置され、バイトコードは.dataセクションに配置されます。



そして最後に、開始バイトコードを変更します。 辞書の初期化、開始行でのバッファーの設定、およびquitの呼び出しのみがあります。



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





コンパイル、リンク、実行!



 $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth word1word2wordBye!
      
      





おridgeに少し似ていますが、これはまさに結果になるはずです。 区切り文字なしで出力します。 ちなみに、将来のために購入する前に改行を入れてください、これは痛くないでしょう。



もちろん、デバッグをいじる必要がありました。 すでに述べた「セグメンテーションフォールト(コアダンプ)」に加えて、興味深い結果が得られることがありました。 たとえば、これ:



 $ ./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)
      
      





これは、テキストが区切り文字に分割されたバイナリ形式の辞書全体にすぎないようです:) b_blwordコマンドでword3の前に「dec rcx」を忘れたときに発生しました。



入力ストリームから単語を選択できます。辞書があります。 次に、辞書検索を実装し、実行する単語を起動する必要があります。 これには、find、cfa、およびexecuteの単語が必要です。



単語findは、スタックから単語のアドレスとその長さを取得します。 この単語は、辞書エントリのアドレスによって返されます。見つからない場合は0が返されます。



記事のアドレスにある単語cfaは、実行可能なバイトコードのアドレスを計算します。



そして、executeという語がバイトコードを実行します。



findから始めましょう。 フォート標準では、1つのアドレス、つまりカウンターのある行が必要です。 しかし、もう一度文字列をバッファにコピーしたくないので、標準から少し逸脱します。 単語findは、スタック上の2つのパラメータ-アドレスと文字列の長さ(実際には、blwordという単語を返します)を取ります。 デバッグ後、この単語は次の形式を取りました。



 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
      
      





おそらく、これは今日の最も難しい言葉です。次に、インタープリトという単語を変更し、タイプを「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
      
      





テスト行には、辞書にある単語、たとえば「0 1- dup +」を入力する必要があります。



すべてを起動する準備ができました!



 $ ld forth.o -o forth $ ./forth 6297733 6297898 6298375 Bye!
      
      





すばらしい、検索は機能します。これらは、ワードのアドレス(10進数)です。今単語cfa。それをアセンブラーに入れてみましょう、それは非常に簡単です、フラグでの作業はfindに似ています



 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
      
      





最後に、executeという単語はさらに簡単です。



 b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 #       pop r8 #  - jmp _next
      
      





単語の解釈を修正して実行します!



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





打ち上げ:



 $ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth -2 Bye!
      
      





ウッラ、稼いだ!(C)Cat Matroskin



確かに、0から1を引き、その結果を自分に追加すると、-2になります:)

これは素晴らしいことですが、キーボードからコマンドを入力したいです。そして、もう1つ問題があります-インタープリターは0、1、2、3、4、8(定数として定義されている)の数字のみを理解します。彼は数字を理解するために何を学ぶでしょう、あなたは「数字」という言葉が必要ですか? findという単語と同じように、バッファーを使用しません。単語「number?」は、スタック上の2つのパラメーター(文字列のアドレスと長さ)を取ります。成功した場合、受信した番号とフラグ1を返します。変換が失敗した場合、スタック上に1つの番号があります:0



。コードは長くなりましたが、かなり単純で線形でした。



 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
      
      





解釈を変更します。単語が辞書にない場合、数字として解釈しようとします。



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





そして、ここに私は得た!アセンブラでそのようなバイトコードをデバッグします。バイトコードにブレークポイントがなく、バイトコードに沿って単に「ステップ」する機能もありません...さらに、スタックで最も簡単な動きがなく、スタックの内容を表示する単純な機能がありません...そしてGDBでは、ただコマンドライン...私はあなたを教えます-それはただの脳爆発です!悪くない。これは脳爆発です!



しかし... ...私たちはインド人です、私は常に回避策を見つけます:)



一般に、私はこの解決策を見つけました:私はスタックの内容を表示するコマンドを実装しました-「s」。コマンドは簡単ではありませんが、それでも解釈は簡単です。そして、それは、判明したとしてochchchen便利。ここにあります:



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





右側では、各コマンドの実行後、スタックの内容の例を示しました。もちろん、サイクルがあり、これは最初のパスにすぎません。ただし、残りは非常に似ており、スタックの一番上の値のみが変更されます。そのような「トレース」の後、チームはすぐに獲得しました!



デバッグ用に、次のマクロを作成しました。



 .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm
      
      





このように正しい場所に挿入して使用します:



  item interpret interpret: .byte b_blword prs .byte b_dup prs .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over ......
      
      





その結果、最初の起動で次の出力が生成されました。



 $ ./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!
      
      





スタック上の各動きを明確に見ることができます。これを以前に行う必要がありました:)



さらにデバッグマクロを作成していきました:



 .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm
      
      





その結果、次のことが可能になりました。



  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
      
      





そしてこれを入手してください:



 $ ./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!
      
      





文字列「20 30 *。」を解釈しようとしました。



そして、ソース行番号を表示することができます...大丈夫、多分...



もちろん、これはデバッグのための古典的なロギング技術ですが、私はすぐにそれについて覚えていませんでした。



一般的に、デバッグの結果、スタックが海外に行くことがわかりました。これは、彼らが置くよりも多くを取得しようとする場合のオーバーフローの反対です。 「.s」にコントロールを追加しました。

新しいマクロの助けを借りて、デバッグは高速でした。ところで、その前に、1行に1バイトコードを投稿しました。しかし、アセンブラを使用すると、文字列に数バイトを配置できます。使用しないでください。



2つのチェックを追加して、単語の解釈を終了しましょう。単語が数字に変換されていないことと、海外のスタックを終了することです。その結果、解釈は次のようになります。



  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
      
      





ところで、quitコマンドがスタックをリセットし、バッファーの状態を変更せずに解釈を再開することに注意してください。したがって、解釈は続行されますが、「新鮮な」スタックが使用されます。これは少し後で修正します。



残っているのは、キーボード入力を整理することだけです。



キーボード入力



砦でのキーボード入力は簡単です。期待という言葉がありますが、それは2つのパラメータを取ります-バッファのアドレスとそのサイズです。この単語はキーボード入力を実行します。入力された実際の文字数は、span変数に配置されます。これらの言葉を作りましょう。標準入力から入力します。



 .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
      
      





次に、キーボード入力バッファを作成する必要があります。256文字の長さにします。

前のテスト行の代わりに作成しましょう。



 inbuf_size = 256 inbuf: .byte b_var0 .space inbuf_size
      
      





そして、quitと開始バイトコードを変更します。tib変数をinbuf入力バッファに設定し、expectを呼び出してから、値をspanから#tibにコピーします。変数のin>は無効化され、interpretと呼ばれます。そして、私たちはサイクルで繰り返します。つまらないものがあります-入力プロンプトを追加すると、スタックのステータスを表示するのがいいでしょう(そして、このための既製のコマンドが既にあります!)。数回の反復の後、次のコード(startコマンドと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 - .
      
      





結果は次のとおりです。



 $ ./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! $
      
      





「>」記号の後はすべてキーボード入力です。残りはシステムの答えです。キーボードから入力しながら、コマンドを少し遊んでみました。彼はいくつかのスタック操作を実行し、日数で秒数を計算しました。



まとめ



通訳は完全で機能しています。そして、さようならを丁寧に言います-彼に「さようなら」と彼に「さようなら」:)

招待として-算術スタックの内容。カッコ内の最初の数字はスタックのサイズ、次に内容、および「>」を入力するためのプロンプトです。実装されたコマンドを入力できます(76個のコマンドをカウントしました)。確かに、多くはコンパイラーにとってのみ意味があります-例えば、リテラル、遷移、呼び出しコマンド。



完全なソース(約1300行)
 .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
      
      





ソースコードが大きくなっているので、最後にここに持ってきます。



現在、彼の居住地はgithubにあります。https : //github.com/hal9000cc/forth64

同じ場所のbinフォルダーには、Linux x64用に既にコンパイルされたバージョンがあります。 Linuxを持っている人は、ダウンロードして実行できます。



そして、Windowsを持っている人-あなたはWSL(Linux用のWindowsサブシステム)をインストールすることができます。私は休暇に向けて出発していました。 5分ほどで完了しましたが、すぐに起動せず、PowerShellコマンドを使用してサブシステムを「オン」にする必要がありました。エラーメッセージからのリンクをたどり、コマンドを実行すると、機能しました。



しかし、本物のインド人がWindowsですべて実行する方法もあります:)これを行うのは難しくありません。システムと対話するいくつかの単語をやり直すだけです。



それだけです!次回はコンパイラを実行します。



新しい単語をまとめる機会があり、条件、サイクルがあります。実際には、多かれ少なかれ標準的な砦に書き込み、それをバイトコードにコンパイルして実行することが可能です。さて、より深刻なテストを実施し、バイトマシンのパフォーマンスを確認することが可能になります。



続き:ネイティブアメリカンの砦のバイトマシン(だけでなく)(パート4)



All Articles