Lua Gen Plus Plus |
|
警告:请参阅下方的状态消息了解该项目的状态。
这可能是一篇长篇介绍,描述了一种 C++ 模板元编程技术的实现,旨在 *在编译时* 生成高效执行的 Lua C API 调用序列。最终会形成一个单一的可重用 C++ 头文件 (luagen.hpp - 称为“luagen++”) 来实现这些技术。您可以使用它以一种清晰且仍然非常高效的方式来绑定 C++ 和 Lua 代码。
例如,以下 C++ 代码
#include <luagen.hpp>
using namespace luagen;
...
eval(L, global("print")(global("math")["sqrt"](lnumber(2.0))));
在编译时会展开成这段代码
lua_getglobal(L,"print") lua_getglobal(L,"math") lua_getfield(L,-1,"sqrt") lua_remove(L,-2) lua_pushnumber(L,2.0) lua_call(L,1, -1) lua_call(L,1, 0)
现在,已经有许多技术用于将 Lua 和 C++ 代码绑定在一起 (BindingCodeToLua)。这里的方法具有以下特点:
我发现与此最相关的项目是 Luabind 中的 object.hpp [3]。它也使用了 C++ 模板元编程技术,但代码生成的优化程度似乎不如上述第 1 点。Luabind 包含了 Boost 头文件 [4],并有大约 10K 行头文件,包含复杂的模板元编程代码,且文档不详。它提供了比我们这里想要的更高层、更自动化的定义(例如,模块和类继承定义)。尽管一个相当完整的 C API 调用封装的 luagen.hpp 可能需要几千行代码,但核心技术本身(可以隔离)只需要几百行合理易懂的代码就可以实现,并且本文将展示如何做到。
因此,本文的目的是双重的:演示“luagen++”头文件 (luagen.hpp) 并解释其实现。
待办事项!- 这里可能有一个关于实现的长而非常有趣的讨论。
如果您不相信我,请查看反汇编(注意:luagen.hpp 中可能还有一些额外的 lua_gettop,可以被消除)。
.file "test.cpp" .text .align 2 .p2align 4,,15 .def __ZN6luagen11debugprintfEz; .scl 3; .type 32; .endef __ZN6luagen11debugprintfEz: pushl %ebp movl %esp, %ebp popl %ebp ret .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC2: .ascii "sqrt\0" LC0: .ascii "print\0" LC1: .ascii "math\0" .text .align 2 .p2align 4,,15 .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl $16, %eax movl %esp, %ebp pushl %edi pushl %esi pushl %ebx subl $92, %esp andl $-16, %esp call __alloca call ___main call _luaL_newstate movl %eax, (%esp) movl %eax, %ebx call _luaL_openlibs movl $LC2, -28(%ebp) leal -24(%ebp), %eax movl -28(%ebp), %edx movl %eax, -32(%ebp) movl -32(%ebp), %eax movl %edx, -36(%ebp) movl %ebx, (%esp) movl %eax, -40(%ebp) leal -40(%ebp), %eax movl %eax, -56(%ebp) leal -48(%ebp), %eax movl %eax, -52(%ebp) movl -56(%ebp), %eax movl $LC0, -20(%ebp) movl -52(%ebp), %edx movl $LC1, -24(%ebp) movl %eax, -64(%ebp) leal -20(%ebp), %eax movl %eax, -72(%ebp) leal -64(%ebp), %eax movl %eax, -68(%ebp) movl -72(%ebp), %eax movl %edx, -60(%ebp) movl -68(%ebp), %edx movl $0, -48(%ebp) movl $1073741824, -44(%ebp) movl %edx, -76(%ebp) movl %eax, -80(%ebp) call _lua_gettop movl -80(%ebp), %eax movl (%eax), %eax movl %ebx, (%esp) movl %eax, 8(%esp) movl $-10002, %eax movl %eax, 4(%esp) call _lua_getfield movl %ebx, (%esp) call _lua_gettop movl %eax, -84(%ebp) movl -76(%ebp), %edi movl %ebx, (%esp) call _lua_gettop movl (%edi), %esi movl (%esi), %eax movl (%eax), %eax movl %ebx, (%esp) movl %eax, 8(%esp) movl $-10002, %eax movl %eax, 4(%esp) call _lua_getfield movl 4(%esi), %eax movl %ebx, (%esp) movl %eax, 8(%esp) movl $-1, %eax movl %eax, 4(%esp) call _lua_getfield movl %ebx, (%esp) movl $-2, %eax movl %eax, 4(%esp) call _lua_remove movl %ebx, (%esp) call _lua_gettop movl %eax, %esi movl 4(%edi), %eax fldl (%eax) movl %ebx, (%esp) fstpl 4(%esp) call _lua_pushnumber movl %ebx, (%esp) call _lua_gettop movl %ebx, (%esp) subl %esi, %eax movl $-1, %esi movl %esi, 8(%esp) movl %eax, 4(%esp) call _lua_call movl %ebx, (%esp) call _lua_gettop movl %ebx, (%esp) movl -84(%ebp), %ecx xorl %edx, %edx movl %edx, 8(%esp) subl %ecx, %eax movl %eax, 4(%esp) call _lua_call movl %ebx, (%esp) call _lua_close leal -12(%ebp), %esp xorl %eax, %eax popl %ebx popl %esi popl %edi popl %ebp ret .def _lua_pushnumber; .scl 3; .type 32; .endef .def _lua_remove; .scl 3; .type 32; .endef .def _lua_getfield; .scl 3; .type 32; .endef .def _lua_call; .scl 3; .type 32; .endef .def _lua_gettop; .scl 3; .type 32; .endef .def _lua_close; .scl 3; .type 32; .endef .def _luaL_openlibs; .scl 3; .type 32; .endef .def _luaL_newstate; .scl 3; .type 32; .endef
这是 g++ 4.3 在 “-fdump-tree-optimized” 标志下生成的输出 (“test.cpp.126t.optimized”),显示了优化后代码的类 C 表示。
;; Function int main() (main)
Analyzing Edge Insertions.
int main() ()
{
int D.5767;
int pos;
int D.5766;
int pos;
const struct getfield_ * this.27;
const struct call_ * this.24;
struct lua_State * L;
struct global D.4728;
struct global D.4729;
const struct getfield_ D.4820;
struct lnumber D.4830;
const struct call_ D.4917;
<bb 2>:
L = luaL_newstate ();
luaL_openlibs (L);
D.4830.v_ = 2.0e+0;
D.4729.name_ = &"math"[0];
D.4820.k_ = &"sqrt"[0];
D.4820.t_ = (struct val *) &D.4729;
D.4917.p1_ = (const struct val *) &D.4830;
D.4917.f_ = (const struct val *) &D.4820;
D.4728.name_ = &"print"[0];
lua_gettop (L);
lua_getfield (L, -10002, ((const struct global *) (struct val *) &D.4728)->name_);
pos = lua_gettop (L);
this.24 = (const struct call_ *) (const struct val *) &D.4917;
lua_gettop (L);
this.27 = (const struct getfield_ *) this.24->f_;
lua_getfield (L, -10002, ((const struct global *) this.27->t_)->name_);
lua_getfield (L, -1, this.27->k_);
lua_remove (L, -2);
pos = lua_gettop (L);
lua_pushnumber (L, ((const struct lnumber *) this.24->p1_)->v_);
D.5766 = lua_gettop (L);
lua_call (L, D.5766 - pos, -1);
D.5767 = lua_gettop (L);
lua_call (L, D.5767 - pos, 0);
lua_close (L);
return 0;
注意:以上代码生成结果来自较旧版本。实际上,在所有输入下生成最优代码是困难的。可能会构建临时对象。
警告:此代码仍在开发中。可能存在错误和缺少 API 功能,尽管它通过了初始测试套件。请在下面的评论部分报告任何问题。
[2008-07-16]
我对这个项目有些气馁。方法是合理的,但困难在于,目标 1 依赖于编译器高效的模板展开和内联。这种行为是依赖于实现的。我发现自己经常反汇编,检查 g++ 4.x 的 gimple 输出,检查链接器的 .map 文件等等,只是为了确保编译器没有生成不必要的模板实例化或未能内联临时对象。有时生成非常高效,特别是对于具有浅 AST 的简单表达式,其他时候则存在代码膨胀,这取决于编译器(例如 g++ 3.x、g++ 4.x、MSVC++9)和编译器选项。在代码中调整 const 与 const & 以及 inline 与 forceinline 有所帮助,但结果似乎不如预期的那样一致。也许其他人可以弄清楚什么对流行编译器有效。此外,复杂的模板会增加构建时间并导致晦涩的编译器错误。这一切似乎并没有简化事情并提高生产力。
所以……我开始了一个新项目:LuaToCee。不再依赖糟糕的 C++ 模板进行元编程,我现在使用 Lua 进行元编程。这更令人满意。它可能无法实现上述所有相同的目标,但它有其自身的用途。
Lua C API 其实并不坏。事实上,为了避免过度使用 C API,应尽可能多地用 Lua 编写代码,将其编译成字节码,运行 BinToCee,然后从 C 调用该函数,并将 C 中的剩余数据传递给它。还有一些技术可以避免使用 C API 时的陷阱(例如,堆栈损坏)。
...