【问题标题】:Gentle introduction to JIT and dynamic compilation / code generation轻柔介绍JIT和动态编译/代码生成
【发布时间】:2010-09-19 12:11:21
【问题描述】:

another question 已经介绍了 C/C++ 框架内动态代码生成的看似简单的基础。是否有关于代码示例的温和介绍?

当我的需求更加温和时,我开始盯着高度复杂的开源 JIT 编译器流血。

有没有计算机科学博士学位的关于该主题的好文章?我正在寻找磨损良好的图案、需要注意的事项、性能考虑等。电子或基于树的资源可能同样有价值。您可以假设您具有(不仅仅是 x86)汇编语言的工作知识。

【问题讨论】:

    标签: c++ assembly code-generation


    【解决方案1】:

    嗯,我在模拟器中使用的一个模式是这样的:

    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”时,如果您刚刚执行的块以调用、无条件跳转或根本没有以流控制结束,那么您可以“修复”它ret 指令直接 jmp 到它将执行的下一个块(因为它总是相同的)如果你已经发出了它。这使得您在 VM 中执行的频率越来越高,而在“execute_block”函数中执行的频率越来越低。

    【讨论】:

    • 没问题,如果您想要一些“简单”的示例,请查看仿真社区。他们倾向于称其为“动态重新编译”,但实际上它与 JIT 的概念完全相同(除了 dyn-rec 几乎没有努力改进它要翻译的代码)。跨度>
    【解决方案2】:

    我不知道有任何专门与 JIT 相关的来源,但我想它很像普通的编译器,只是在您不担心性能的情况下更简单。

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

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

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

    还可以将基于堆栈的代码转换为语法树。维护一个编译时堆栈。每个“推送”指令都会将被推送的事物的表示存储在堆栈中。运算符创建包含其操作数的语法树节点。例如,“XY +”可能导致堆栈包含“var(X)”,然后是“var(X) var(Y)”,然后 plus 会弹出两个 var 引用并推送“plus(var(X), var(Y))"。

    【讨论】:

      【解决方案3】:

      给自己买一本 Joel Pobar 关于转子的书(当它出版时),并深入研究源代码到 SSCLI。当心,精神错乱在于:)

      【讨论】:

      • 你刚刚因为这个“转子”的东西引起了我最兴奋的注意。
      • 玩起来很棒。说真的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-10
      相关资源
      最近更新 更多