根据编译时生成的函数数,在运行时用c++生成函数

build function at runtime c++ from number of functions that built at compilation

本文关键字:函数 运行时 c++ 编译 数数      更新时间:2023-10-16

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

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

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

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

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

基本上,线程代码由一个函数指针数组组成,解释器通过该数组调用每个指向函数的指针。棘手的部分是,通常每个函数都会返回数组元素的地址,其中包含指向要调用的下一个函数的指针,这使您可以在解释器中实现分支和调用等功能

通常你会使用这样的东西:

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提供了一个很好的、独立于机器的接口来实现这一点,同时为通用平台提供了非常好的优化器,这些优化器将在运行时生成非常好的代码。