7

アセンブリ コードを保持するように最適化されたデータ構造を作成する予定です。そうすれば、この構造に作用する最適化アルゴリズムの全責任を負うことができます。実行中にコンパイルできれば。それは一種の動的な実行になります。これは可能ですか?誰もこのようなものを見たことがありますか?

構造体を使用して構造体をプログラム フローにリンクする必要がありますか。オブジェクトの方が優れていますか?

struct asm_code {
   int type;
   int value;
   int optimized;
   asm_code *next_to_execute;
 } asm_imp;

更新: リンクされたリストのようになると思います。

更新: 他のコンパイラがあることは知っています。しかし、これは軍の極秘プロジェクトです。したがって、どのコードも信頼できません。私たちはそれをすべて自分でやらなければなりません。

更新: OK、基本的な i386 マシン コードを生成するだけだと思います。しかし、メモリ ブロブが終了したら、どのようにメモリ ブロブにジャンプすればよいでしょうか?

4

5 に答える 5

8

可能です。動的コード生成は、ソフトウェア レンダリングやグラフィックスなどの一部の分野では主流です。あらゆる種類のスクリプト言語、マシン コード (私の知る限り、.NET、Java、Perl。最近では JavaScript もクラブに加わりました) でのバイトコードの動的コンパイルで、多くの用途があります。

また、非常に数学の多いアプリケーションでも使用されていることがわかります。たとえば、そのような乗算を数千回実行する予定がある場合、行列の乗算からゼロを使用してすべての乗算を削除すると、違いが生じます。

コードの SSA 表現を読むことを強くお勧めします。これは、各プリミティブがいわゆる 3 オペランド形式に変換され、各変数が 1 回だけ割り当てられる表現です (したがって、同じ静的単一割り当て形式)。

そのようなコードに対して高次の最適化を実行でき、そのコードを実行可能なコードに変換するのは簡単です。ただし、週末にそのコード生成バックエンドを作成することはありません...

SSA がどのように見えるかを理解するには、LLVM コンパイラを試してみてください。彼らの Web サイトには、ちょっとした "Try Out" ウィジェットがあります。C コードをウィンドウに貼り付けると、SSA フォームに近いものが得られます。

それがどのように見えるかの小さな例:

この整数平方根アルゴリズムを C で見てみましょう (任意の例です。単純でありながら自明ではないものを取り上げました)。

unsigned int isqrt32 (unsigned int value) 
{
    unsigned int g = 0;
    unsigned int bshift = 15;
    unsigned int b = 1<<bshift;
    do {
        unsigned int temp = (g+g+b)<<bshift;
        if (value >= temp) {
            g += b;
            value -= temp;
        }
        b>>=1;
    } while (bshift--);
    return g;
}

LLVM はそれを次のように変換します。

define i32 @isqrt32(i32 %value) nounwind  {
entry:
    br label %bb

bb:     ; preds = %bb, %entry
    %indvar = phi i32 [ 0, %entry ], [ %indvar.next, %bb ]      
    %b.0 = phi i32 [ 32768, %entry ], [ %tmp23, %bb ]
    %g.1 = phi i32 [ 0, %entry ], [ %g.0, %bb ]     
    %value_addr.1 = phi i32 [ %value, %entry ], [ %value_addr.0, %bb ]      
    %bshift.0 = sub i32 15, %indvar 
    %tmp5 = shl i32 %g.1, 1 
    %tmp7 = add i32 %tmp5, %b.0 
    %tmp9 = shl i32 %tmp7, %bshift.0    
    %tmp12 = icmp ult i32 %value_addr.1, %tmp9      
    %tmp17 = select i1 %tmp12, i32 0, i32 %b.0      
    %g.0 = add i32 %tmp17, %g.1     
    %tmp20 = select i1 %tmp12, i32 0, i32 %tmp9     
    %value_addr.0 = sub i32 %value_addr.1, %tmp20           
    %tmp23 = lshr i32 %b.0, 1       
    %indvar.next = add i32 %indvar, 1       
    %exitcond = icmp eq i32 %indvar.next, 16    
    br i1 %exitcond, label %bb30, label %bb

bb30:       ; preds = %bb
    ret i32 %g.0
}

私はそれが最初は恐ろしく見えることを知っています。純粋な SSA-Form でさえありません。その表現を読めば読むほど、それがより理にかなっているでしょう。また、この表現が最近広く使用されている理由もわかります。

必要なすべての情報をデータ構造にカプセル化するのは簡単です。最後に、オペコード名などに列挙型または文字列を使用するかどうかを決定する必要があります。

ところで、私はあなたにデータ構造を提供したのではなく、より形式的で実用的な言語と、どこを見ればよいかのアドバイスを提供したことを知っています.

とても素晴らしく興味深い研究分野です。

編集: 忘れる前に: .NET と Java の組み込み機能を見落とさないでください。これらの言語を使用すると、プログラム内のバイト コードまたはソース コードからコンパイルし、結果を実行できます。

乾杯、ニルス


あなたの編集について:コードでバイナリブロブを実行する方法:

バイナリ BLOB へのジャンプは、OS とプラットフォームに依存します。一言で言えば、命令キャッシュを無効にしました。おそらくデータキャッシュを書き戻す必要があり、コードを書き込んだメモリ領域で実行権を有効にする必要があるかもしれません。

win32 では、コードをヒープに配置すれば命令キャッシュのフラッシュで十分と思われるため、比較的簡単です。

このスタブを使用して開始できます。

typedef void (* voidfunc) (void);

void * generate_code (void)
{
    // reserve some space
    unsigned char * buffer = (unsigned char *) malloc (1024);


    // write a single RET-instruction
    buffer[0] = 0xc3; 

    return buffer;
}

int main (int argc, char **args)
{
    // generate some code:
    voidfunc func = (voidfunc) generate_code();

    // flush instruction cache:
    FlushInstructionCache(GetCurrentProcess(), func, 1024);

    // execute the code (it does nothing atm)
    func();

    // free memory and exit.
    free (func);
}
于 2008-09-20T15:31:55.837 に答える
1

次のような、おそらく既存のマシンコードから解析された、ある種の命令テンプレートを保持するデータ構造が必要だと思います。

add r1, r2, <int>

この構造の配列があり、この配列に対して最適化を実行します。おそらく、サイズを変更するか、新しい配列を作成して、対応するマシンコードを生成します。

ターゲットマシンが可変幅の命令(たとえばx86)を使用している場合、配列を構築する命令の解析を実際に終了せずに配列サイズを決定することはできません。また、最適化された配列からすべての命令を実際に生成する前に、必要なバッファーの量を正確に決定することはできません。しかし、あなたは良い見積もりをすることができます。

GNULightningをチェックしてください。それはあなたに役立つかもしれません。

于 2008-09-20T16:05:51.933 に答える
0

ケースの 99% では、パフォーマンスの違いはごくわずかです。クラスの主な利点は、OOP によって生成されたコードが手続き型コードよりも優れており、理解しやすいことです。

どの言語でコーディングしているかはわかりません。C# では、クラスと構造体の主な違いは、クラスが参照型であるのに対し、構造体は値型であることに注意してください。この場合、構造体から始めたいと思うかもしれませんが、それでも動作 (コンストラクター、メソッド) をそれらに追加します。

于 2008-09-20T15:34:25.987 に答える
0

コードを自分で最適化することの技術的価値については議論しませんが、C++ コードでは、POD 構造体または完全なオブジェクトのどちらを選択するかは、ほとんどがカプセル化のポイントです。

コードをインライン化すると、コンパイラは使用されるコンストラクター/アクセサーを最適化 (または最適化しない) できます。パフォーマンスが低下することはありません。

まず、コンストラクタを設定します

C++ コンパイラを使用している場合は、少なくとも 1 つのコンストラクターを作成します。

struct asm_code {
   asm_code()
      : type(0), value(0), optimized(0) {}

   asm_code(int type_, int value_, int optimized_)
      : type(type_), value(value_), optimized(_optimized) {}

   int type;
   int value;
   int optimized;
 };

少なくとも、コードに未定義の構造体が含まれることはありません。

データのあらゆる組み合わせが可能ですか?

あなたが使用するような構造体を使用することは、任意の型、任意の値、および最適化が可能であることを意味します。たとえば、タイプ = 25、値 = 1205、最適化 = -500 に設定すると、問題ありません。

ユーザーが構造内にランダムな値を入れたくない場合は、インライン アクセサーを追加します。

struct asm_code {

   int getType() { return type ; }
   void setType(int type_) { VERIFY_TYPE(type_) ; type = type_ ; }

   // Etc.

   private :
      int type;
      int value;
      int optimized;
 };

これにより、構造内に設定されているものを制御し、コードをより簡単にデバッグできます (または、コードの実行時検証を行うことさえできます)。

于 2008-09-20T15:37:04.343 に答える
0

少し読んだ後の私の結論は、Common Lisp がこのタスクに最適であるということです。Lisp マクロを使用すると、非常に大きな力が得られます。

于 2011-02-16T20:31:22.960 に答える