5

私は、独自の仮想マシン用にコンパイルするスクリプト言語を開発しています。これは、ポイントベクトルフロートなどのある種のデータを処理する命令を持つ単純な言語です。メモリセルは次のように表されます。

struct memory_cell
{
    u32 id;
    u8 type;

    union
    {
        u8 b; /* boolean */
        double f; /* float */
        struct { double x, y, z; } v; /* vector */
        struct { double r, g, b; } c; /* color */
        struct { double r, g, b; } cw; /* color weight */
        struct { double x, y, z; } p; /* point variable */
        struct { u16 length; memory_cell **cells; } l; /* list variable */
    };  
};

命令は一般的であり、多くの異なるオペランドで機能します。例えば

ADD dest, src1, src2

オペランドに応じて適切な宛先のタイプを設定するフロート、ベクトル、ポイント、カラーを操作できます。

メインの実行サイクルは、命令のオペコード(あらゆる種類の命令を定義するためのユニオンを含む構造体)をチェックして実行するだけです。私は、レジスターを持たず、メモリーセルの大きな配列だけを持っている単純化されたアプローチを使用しました。

JITが最高のパフォーマンスを得るのに役立つかどうか、そしてそれをどのように達成するかを考えていました。

私が言ったように、これまでに到達した最良の実装は次のようなものです。

 void VirtualMachine::executeInstruction(instr i)
 {
     u8 opcode = (i.opcode[0] & (u8)0xFC) >> 2;

     if (opcode >= 1 && opcode <= 17) /* RTL instruction */
     {
        memory_cell *dest;
        memory_cell *src1;
        memory_cell *src2;

        /* fetching destination */
        switch (i.opcode[0] & 0x03)
        {
            /* skip fetching for optimization */
            case 0: { break; }
            case MEM_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]; break; }
            case ARRAY_VAL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[i.rtl.dest.index]; break; }
            case ARRAY_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[(int)i.rtl.dest.value]; break; }
        }

     /* omitted code */

     switch (opcode)
     {
         case ADD:
         {
             if (src1->type == M_VECTOR && src2->type == M_VECTOR)
             {
                 dest->type = M_VECTOR;
                 dest->v.x = src1->v.x + src2->v.x;
                 dest->v.y = src1->v.y + src2->v.y;
                 dest->v.z = src1->v.z + src2->v.z;
              }

      /* omitted code */

jitコンパイルを試すのは簡単/便利ですか?しかし、私は本当にどこから始めればよいのかわからないので、私はいくつかのアドバイスを求めています。

それとは別に、それを開発する際に考慮すべき他のアドバイスはありますか?

この仮想マシンは、レイトレーサーのシェーダーを計算するのに十分な速度である必要がありますが、私はどのような種類のベンチマークも実行していません。

4

3 に答える 3

7

JIT( "Just-in-time")コンパイラーを作成する前に、少なくとも"Way-ahead-of-time"コンパイラーの作成方法を検討する必要があります。

つまり、VMの命令で構成されるプログラムが与えられた場合、元のプログラムと同じように、x86(またはその他)の命令で構成されるプログラムをどのように作成しますか?異なる命令セット、および同じアーキテクチャの異なるバージョンの出力をどのように最適化しますか?あなたが与えたサンプルのオペコードは非常に複雑な実装を持っています、それであなたは仕事をするコードを出すことによって「インライン」を実装するでしょう、そしてあなたはいくつかの共有コードへの呼び出しを出すことによってどのオペコードを実装しますか?

JITはこれを実行できる必要があり、VMの実行中に、どのコードを実行するか、いつ実行するか、VM命令とネイティブ命令の結果の混合をどのように表すかについても決定する必要があります。

まだアセンブリジョッキーでない場合は、JITを作成することはお勧めしません。それは「絶対にやらない」ということではありませんが、本格的に始める前に、アセンブリジョッキーになる必要があります。

別の方法は、Jeff Fosterが言うように、VM命令(または元のスクリプト言語)をJavaバイトコード(LLVM)に変換する非JITコンパイラーを作成することです。次に、そのバイトコードのツールチェーンに、CPUに依存する難しい作業を実行させます。

于 2009-11-28T14:05:29.360 に答える
6

VMは考慮すべき大きなタスクです。VMをLLVMのようなものに基づいて作成することを検討しましたか?

LLVMは、開始するのに適した基盤を提供し、理解するために使用できるサンプルプロジェクトがたくさんあります。

于 2009-11-28T12:20:10.713 に答える
3

Steve Jessopには、JITコンパイラは通常のコンパイラよりもはるかに難しいという点があります。そして、通常のコンパイラはそれ自体が難しいです。

しかし、質問の最後の部分を読んで、本当にJITコンパイラが必要かどうか疑問に思います。

問題が次のような場合:

ユーザーが自分のドメイン固有言語を使用してシェーダー手順などを提供できるレイトレーシングプログラムを作成したいと思います。大丈夫です。私は自分の言語を定義し、インタプリタを実装しましたが、それはうまく機能します。しかし、それは遅いです:どうすればネイティブコードとして実行できますか?

次に、私が以前行っていたのは、同様の状況です。

  • ユーザー提供のプロシージャを、プログラムから呼び出すことができるC関数に変換します。

  • 適切な#includesなどを使用して通常のCソースファイルに書き込みます。

  • 通常のCコンパイラを使用して、それらを.dll(または* nixでは.so)としてコンパイルします。

  • プログラムに.dllを動的にロードし、関数ポインターを見つけて、解釈されたバージョンの代わりにレイトレーサーでそれらを使用します。

いくつかのメモ:

  • 一部の環境では、それが不可能な場合があります。独自のdllのロードを禁止するCコンパイラまたはシステムポリシーへのアクセスがありません。試してみる前に確認してください。

  • 通訳を捨てないでください。あなたの言語のリファレンス実装としてそれを保管してください。

于 2009-11-28T14:36:41.713 に答える