C/C++ 框架内动态代码生成的看似简单的基础已经在另一个问题中讨论过。是否有关于代码示例的温和介绍?
当我的需求更加温和时,我开始盯着高度复杂的开源 JIT 编译器流血。
是否有关于该主题的好文章不假定计算机科学博士学位?我正在寻找磨损良好的图案、需要注意的事项、性能考虑等。电子或基于树的资源可能同样有价值。您可以假设(不仅仅是 x86)汇编语言的工作知识。
C/C++ 框架内动态代码生成的看似简单的基础已经在另一个问题中讨论过。是否有关于代码示例的温和介绍?
当我的需求更加温和时,我开始盯着高度复杂的开源 JIT 编译器流血。
是否有关于该主题的好文章不假定计算机科学博士学位?我正在寻找磨损良好的图案、需要注意的事项、性能考虑等。电子或基于树的资源可能同样有价值。您可以假设(不仅仅是 x86)汇编语言的工作知识。
好吧,我在模拟器中使用的模式是这样的:
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 指令,它将执行下一个块(因为它总是相同的) 。
我不知道有任何专门与 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),各不相同))”。
给自己买一本 Joel Pobar 关于转子的书(当它出版时),并深入研究SSCLI的源代码。当心,精神错乱在于:)