本人博客地址:http://www./pwq1989/ 上一篇对Luajit的代码结构和编译过程做了简单的描述,这一篇就讲一下buildvm在第一步预处理dasc文件的过程和DynASM这个轮子。 官方连接:http:///dynasm.html 是为了让你更优雅的C里面撸汇编的一个工具,我记得以前看过一个老外的blog对比过同样功能的jit code generator的语法,Luajit的作者显然品位还是很高的。 我们先来看看如果不用工具硬生生撸代码的话会发生什么。 1、你往一段内存里面写0xB8,0x00,0x01.... 2、你在文件里定义好多label,写个copy section的宏往内存里面复制,你还不能确定里面到底是什么。(哦。。这个的术语叫Threaded。。。) 然后再对比下AsmJit或者Xbyak的例子看看(他们的功能差不多),DynASM还提供了.marco实现,就会发现语法真是sweeeet~ 这是我写着玩的一个草泥马语jit解释器(https://github.com/pwq1989/GMHjit)语法真是清新自然啊,如果你想看工业级的应用,可以看看Google的Haberman写的protobuf的upb库,里面用DynASM进行了jit,号称快了多少多少(不去考证了),或者是agentzh写的sregex正则库,也是用它做了jit。一般来说DSL配上jit的话一定会快很多就错不了了。 下面给一个DynASM的Demo程序(摘抄自这个blog) 1 // DynASM directives. 2 |.arch x64 3 |.actionlist actions 4 5 // This define affects "|" DynASM lines. "Dst" must 6 // resolve to a dasm_State** that points to a dasm_State*. 7 #define Dst &state 8 9 int main(int argc, char *argv[]) { 10 if (argc < 2) { 11 fprintf(stderr, "Usage: jit1 <integer>\n"); 12 return 1; 13 } 14 15 int num = atoi(argv[1]); 16 dasm_State *state; 17 initjit(&state, actions); 18 19 // Generate the code. Each line appends to a buffer in 20 // "state", but the code in this buffer is not fully linked 21 // yet because labels can be referenced before they are 22 // defined. 23 // 24 // The run-time value of C variable "num" is substituted 25 // into the immediate value of the instruction. 26 | mov eax, num 27 | ret 28 29 // Link the code and write it to executable memory. 30 int (*fptr)() = jitcode(&state); 31 32 // Call the JIT-ted function. 33 int ret = fptr(); 34 assert(num == ret); 35 36 // Free the machine code. 37 free_jitcode(fptr); 38 39 return ret; 40 } 预处理之后那就会变成这样子: 1 //|.arch x64 dasm_put就是把num参数和actions[]一起放入了Dst(#define Dst &state)的制定的内存中,这时候已经是机器码的形式了。2 //|.actionlist actions 3 static const unsigned char actions[4] = { 4 184,237,195,255 5 }; 6 7 // [ ![]() 8 9 //| mov eax, num 10 //| ret 11 dasm_put(Dst, 0, num); 下面是对于acitons[]数组内容的解释: 184(B8)-- mov eax, [immediate] 指令的第一个字节 237 -- 内置的标志DASM_IMM_D, 指明应该放入一个4字节宽度的参数,与上一条指令完成一个MOV 195(C3)-- 对应ret指令 255 -- 内置的标志DASM_STOP 以上就是最简单的例子,dasm_growpc()是内置的函数,用来增长maxpc, 这样在程序里面就可以方便写出jmp => label 这样的指令了。 由于DynASM的文档很少,幸亏还有几个例子,除了例子唯一能看的就是源码了,所以在用的时候出现问题是很痛苦的。。当时写GMHjit就发现了蛋疼的pre-process period bug,后来绕过去了。 源码文件有这么几个 -- dynasm.lua -- dynasm_proto.h -- dynasm_*.lua -- dynasm_*.h // * x64 x86 ppc mips arm 等target 用起来就是lua dynasm.lua a.dasm > a.h 下面就从dynasm.lua开始分析下他的源码 入口是parseargs函数,里面给的g_opt参数赋默认的值,一个repeat 中调用parseopt解析参数,opt_map就是option对args的函数映射。 函数wline,wcomment,wsync,wdumplines都是对输出的目标文件的操作。 真正的主函数是 translate,把input file变成 output file,在readfile中的doline函数是真正的处理过程,里面判断是否是Assembler line之后Emit C code,调用dostmt(aline)。里面继续有map_coreop[*]来处理section macro arch nop_ error_1 include if endif elseif 等关键字,想深入研究的可以自己去看,其中在loadarch中根据arch加载不同的lua库 如果arch是x64的话,本质还是require x86 来看dasm_x86.lua文件 _M.mergemaps这是关键的方法,设置了2个Map的元方法,然后返回,相当于是把方法绑定在table里面传递了出去。处理后文件中关键的actionlist[]数组和Dasm_put(Dst, ...)的输出就是这个lua文件的方法。 里面提供了很多dump方法,可以供我们遇到问题时候调试处理过程。action_names就是以后生成的action_list中的内置标志定义,必须与dasm_x86.h中的enum定义一致。 表明了代表的参数和长度等信息。这个文件里面所有的函数就是做了一件事,把你的 |... 这样子的代码处理成数组输出到目标文件中(我是汇编渣渣,里面貌似支持SSE2、3、4+,看不懂,等到以后看到traced jit的时候再去翻手册把) 预处理完成之后,就是#include "dasm_x86.h",里面有最关键的dasm_State结构体的定义,几乎里面所有的函数都是对外的API,有init,setup,free等等,除去初始化与free之外,有三个步骤是需要出现在你都代码中: 1、dasm_put(Dst,...) 这个是自动生成的,不用我们操心,根据actionlist[]和运行时的参数写入到Dst指定的内存(Dst->section)中. 2、dasm_link() 第二个参数是返回的代码长度大小,这个函数把section合并到一起,处理偏移等等。 3、dasm_encode() 第二个参数是一个接受encode输出的buffer指针。 然后就可以用一个函数指针,比如声明一个 int (*f)(*int), int ret = f(param) 直接运行刚刚生成的机器码了。 |
|