posts - 9,  comments - 19,  trackbacks - 0
本人博客地址: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) 直接运行刚刚生成的机器码了。






posted on 2013-11-30 12:49 右席 阅读(6903) 评论(0)  编辑 收藏 引用 所属分类: Luajit

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理