0

最初にコードを解析し、次に関数を (コードを実行するために) 解析されたコードとして 1 つの buffer\memory にコピーするスクリプト言語を作成しています。

関数のバイナリ コードをバッファにコピーしてから、バッファ全体を実行する方法はありますか? パフォーマンスを向上させるには、すべての関数を一度に実行する必要があります。

私の質問を最もよく理解するために、次のようなことをしたいと思います。

#include <vector>
using namespace std;

class RuntimeFunction; //The buffer to my runtime function

enum ByteCodeType {
    Return,
    None
};

class ByteCode {
    ByteCodeType type;
}

void ReturnRuntime() {
    return;
}

RuntimeFunction GetExecutableData(vector<ByteCode> function) {
    RuntimeFunction runtimeFunction=RuntimeFunction(sizeof(int)); //Returns int
    for (int i = 0 ; i < function.size() ; i++ ) {
        #define CurrentByteCode function[i]
        if (CurrentByteCode.Type==Return) {
            runtimeFunction.Append(&ReturnRuntime);
        } //etc.
        #undef
    }
    return runtimeFunction;
}

void* CallFunc(RuntimeFunction runtimeFunction,vector<void*> custom_parameters) {
    for (int i=custom_parameters-1;i>=0;--i) { //Invert parameters loop
        __asm {
            push custom_parameters[i]
        }
    }
    __asm {
        call runtimeFunction.pHandle
    }
}
4

1 に答える 1

3

実行時にコードを生成する深さに応じて、これを行う方法はいくつかありますが、比較的簡単な方法の 1 つは、スレッド化されたコードとスレッド化されたコード インタープリターを使用することです。

基本的に、スレッド化されたコードは関数ポインターの配列で構成され、インタープリターは配列を調べて、それぞれが指す関数を呼び出します。トリッキーな部分は、通常、各関数が次に呼び出す関数へのポインターを含む配列要素のアドレスを返すようにすることです。これにより、インタープリターで何の努力もせずに分岐や呼び出しなどを実装できます。

通常、次のようなものを使用します。

typedef void *(*tc_func_t)(void *, runtime_state_t *);

void *interp(tc_func_t **entry, runtime_state_t *state) {
    tc_func_t *pc = *entry;
    while (pc) pc = (*pc)(pc+1, state);
    return entry+1;
}

それがインタプリタ全体です。 runtime_state_t実行時の状態 (通常は 1 つまたは複数のスタック) を含むある種のデータ構造です。関数ポインターの配列を作成し、tc_func_tそれらに関数ポインター (および場合によってはデータ) を入力し、null ポインターで終了してinterpから、配列の先頭を含む変数のアドレスを指定して呼び出します。したがって、次のようなものがあるかもしれません:

void *add(tc_func_t *pc, runtime_state_t *state) {
    int v1 = state->data.pop();
    int v2 = state->data.pop();
    state->data.push(v1 + v2);
    return pc; }
void *push_int(tc_func_t *pc, runtime_state_t *state) {
    state->data.push((int)*pc);
    return pc+1; }
void *print(tc_func_t *pc, runtime_state_t *state) {
    cout << state->data.pop();
    return pc; }

tc_func_t program[] = {
    (tc_func_t)push_int,
    (tc_func_t)2,
    (tc_func_t)push_int,
    (tc_func_t)2,
    (tc_func_t)add,
    (tc_func_t)print,
    0
};

void run_prgram() {
    runtime_state_t  state;
    tc_func_t *entry = program;
    interp(&entry, &state);
}

を呼び出すrun_programと、2+2 を加算して結果を出力する小さなプログラムが実行されます。

ここで、引数interpに余分なレベルの間接性がある の少し奇妙な呼び出し設定に混乱するかもしれません。entryこれは、interpそれ自体をスレッド化されたコード配列の関数として使用し、その後に別の配列へのポインターを使用して、スレッド化されたコード呼び出しを実行できるようにするためです。

編集

このようなスレッド化されたコードの最大の問題は、パフォーマンスに関連しています。スレッド化されたコード化されたインタープリターは、分岐予測子に対して非常に友好的ではないため、パフォーマンスは、分岐予測ミスの回復時間ごとに 1 つのスレッド化された命令呼び出しでほとんどロックされます。

より高いパフォーマンスが必要な場合は、完全なランタイム コード生成を実行する必要があります。 LLVMは、実行時に非常に優れたコードを生成する一般的なプラットフォーム用の非常に優れたオプティマイザーと共に、それを行うための優れたマシンに依存しないインターフェイスを提供します。

于 2012-07-29T03:15:54.117 に答える