0

我正在创建脚本语言,它首先解析代码,然后将函数(执行代码)复制到一个缓冲区\内存作为解析的代码。

有没有办法将函数的二进制代码复制到缓冲区然后执行整个缓冲区?我需要一次执行所有功能以获得更好的性能。

为了最好地理解我的问题,我想做这样的事情:

#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

有多种方法可以做到这一点,具体取决于您希望在运行时生成代码的深度,但一种相对简单的方法是使用线程代码和线程代码解释器。

基本上,线程代码由函数指针数组组成,解释器通过数组调用每个指向的函数。棘手的部分是,您通常让每个函数返回数组元素的地址,其中包含指向要调用的下一个函数的指针,这使您无需在解释器中进行任何努力就可以实现分支和调用之类的东西

通常你使用类似的东西:

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是某种包含一些运行时状态(通常是一个或多个堆栈)的数据结构。您可以通过创建一个函数指针数组tc_func_t并用函数指针(可能还有数据)填充它们来调用它,以空指针结尾,然后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自身用作线程代码数组中的函数,后跟指向另一个数组的指针,它将执行线程代码调用。

编辑

像这样的线程代码的最大问题与性能有关——线程编码的解释器对分支预测器极其不友好,因此性能几乎被锁定在每个分支预测错误恢复时间的一个线程指令调用上。

如果您想要更高的性能,您几乎必须进行全面的运行时代码生成。 LLVM提供了一个很好的、与机器无关的接口来执行此操作,以及用于公共平台的非常好的优化器,这些优化器将在运行时生成非常好的代码。

于 2012-07-29T03:15:54.117 回答