在another question中已经涵盖了C / C ++框架中动态代码生成的简单基础。是否有关于代码示例的主题的温和介绍?
当我的需求更加温和时,我的眼睛开始流露出高度错综复杂的开源JIT编译器。
是否有关于该主题的优秀文本没有获得计算机科学博士学位?我正在寻找破旧的模式,需要注意的事项,性能考虑因素等。电子或基于树的资源同样有价值。您可以假设(不仅仅是x86)汇编语言的工作知识。
答案 0 :(得分:4)
我在模拟器中使用的模式是这样的:
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”函数中越来越少。
答案 1 :(得分: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)”,然后加上弹出两个var引用并推送“加号(var(X), VAR(Y))。”
答案 2 :(得分:0)
给自己一份Joel Pobar关于Rotor的书(当它出来的时候),并将源头深入研究SSCLI。当心,精神错乱在于:)