本人博客地址:
http://www.cppblog.com/pwq1989/上一篇对Luajit的代码结构和编译过程做了简单的描述,这一篇就讲一下buildvm在第一步预处理dasc文件的过程和DynASM这个轮子。
官方连接:
http://luajit.org/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
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);
dasm_put就是把num参数和actions[]一起放入了Dst(#define Dst &state)的制定的内存中,这时候已经是机器码的形式了。
下面是对于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) 直接运行刚刚生成的机器码了。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。