代码之家  ›  专栏  ›  技术社区  ›  postfuturist

温和介绍JIT和动态编译/代码生成

  •  3
  • postfuturist  · 技术社区  · 16 年前

    在C/C++框架内,动态代码生成的简单基础已经被覆盖。 another question . 有没有用代码示例温和地介绍这个主题?

    我的眼睛开始出血,盯着高度复杂的开源JIT编译器,而我的需求是更温和的。

    有没有关于这一学科的好文章不假定是计算机科学博士?我在寻找老旧的模式、需要注意的事项、性能考虑因素等。基于电子或树的资源同样有价值。您可以假定具有(不仅仅是x86)汇编语言的工作知识。

    3 回复  |  直到 16 年前
        1
  •  4
  •   Evan Teran Benoît    16 年前

    我在模拟器中使用的模式是这样的:

    typedef void (*code_ptr)();
    unsigned long instruction_pointer = entry_point;
    std::map<unsigned long, code_ptr> code_map;
    
    
    void execute_block() {
        code_ptr f;
        std::map<unsigned long, void *>::iterator it = code_map.find(instruction_pointer);
        if(it != code_map.end()) {
            f = it->second
        } else {
            f = generate_code_block();
            code_map[instruction_pointer] = f;
        }
        f();
        instruction_pointer = update_instruction_pointer();
    }
    
    void execute() {
        while(true) {
            execute_block();
        }
    }
    

    这是一个简化,但想法是存在的。基本上,每次要求引擎执行“基本块”(通常是下一个流控制操作之前的所有操作或可能的整个功能)时,它都会查看是否已经创建了该块。如果是,执行它,否则创建它,添加它,然后执行。

    冲洗重复:

    至于代码生成,这有点复杂,但其思想是生成一个适当的“函数”,它在VM上下文中执行基本块的工作。

    编辑:请注意,我也没有演示过任何优化,但您要求“温和介绍”

    编辑2:我忘了提一个你能用这个模式实现的最快的生产力加速。基本上,如果你 从未 从树中删除一个块(如果这样做,您可以绕过它,但如果不这样做,则会简单得多),然后您可以将这些块“链接”在一起以避免查找。这是概念。每当您从f()返回并将要执行“update_instruction_pointer”时,如果您刚执行的块以调用、无条件跳转或根本没有以流控制结束,那么您可以使用直接JMP“修正”其RET指令到它将执行的下一个块(因为它始终是同一个块)。 如果 你已经发射了。这使得您在VM中执行的频率越来越高,而在“execute_block”函数中执行的频率越来越低。

        2
  •  2
  •   Andru Luvisi    16 年前

    我不知道任何与JIT特别相关的源代码,但是我认为它非常像一个普通的编译器,只有在您不担心性能的情况下才会更简单。

    最简单的方法是从一个VM解释器开始。然后,对于每个VM指令,生成解释器将要执行的汇编代码。

    除此之外,我设想您将解析VM字节代码并将其转换为某种合适的中间形式(三个地址代码?SSA?)然后像其他编译器一样优化和生成代码。

    对于基于堆栈的VM,在将字节代码转换为中间形式时跟踪“当前”堆栈深度可能会有所帮助,并将每个堆栈位置视为一个变量。例如,如果您认为当前堆栈深度为4,并且看到一条“push”指令,那么您可能会生成一个“stack_variable_5”的赋值,并增加一个编译时堆栈计数器,或者类似的东西。当堆栈深度为5时,“添加”可能会生成代码“stack_variable_4=stack_variable_4+stack_variable_5”,并减少编译时堆栈计数器。

    也可以将基于堆栈的代码转换为语法树。维护编译时堆栈。每一条“推”指令都会导致被推的对象的一个表示被存储在堆栈上。运算符创建包含其操作数的语法树节点。例如,“x y+”可能会导致堆栈包含“var(x)”,然后“var(x)var(y)”,然后加号会弹出var引用,并将其推送到“plus(var(x),var(y))”。

        3
  •  0
  •   OJ.    16 年前

    给自己一本乔尔·波巴的《论转子》一书(当它不在时),然后深入研究它的来源。 SSCLI . 小心,精神错乱就在里面。)