Lua Gen Plus Plus

lua-users home
wiki

luagen++ 是在編譯期間經由範本巨程式設計方法來產生有效率的 Lua C API 呼叫順序的 C++ 標頭。

說明

警告:查看下方的狀態訊息以了解本專案的狀態。

這可能會開始一篇長篇文章,描述 C++ 範本巨程式設計方法的實作,目的是在編譯期間產生在執行時可以有效率執行的一系列 Lua C API 呼叫。這將會在單一可重複使用的 C++ 標頭檔 (luagen.hpp - 簡稱「luagen++」) 中達到最高點,而 luagen.hpp 就實作了這些方法。您可以利用此標頭檔以乾淨且有效率的方式繫結 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 行未徹底說明的複雜範本巨程式編寫程式碼標頭檔,並提供較高階且更自動化的定義(例如模組與類別衍生定義),這非我所要。雖然 luagen.hpp 可能會有數千行 C API 呼叫的完整封裝,但核心技術本身(可獨立出來)僅能實作於數百行程式碼,且此文章將示範做法。

因此,本文的目的是雙重的:展示「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

以下是在「-fdump-tree-optimized」旗標(「test.cpp.126t.optimized」)下 g++ 4.3 的輸出,顯示了最佳化後程式碼的 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]

我對於此專案感到有點灰心。此做法是合理的,但困難在於上面第一個目標仰賴編譯器的有效範本擴充和內嵌。此行為與實作有關。我發現我常常反組譯、檢查 g++ 4.x gimple 輸出、檢查連結器 .map 檔等等,只為了確保編譯器不會產生多餘的範本實例化,或無法內嵌暫時性物件。有時產生出來的結果的效能很好,尤其是 AST 較淺的簡單運算式;其他時候則會產生程式碼膨脹,這取決於編譯器(如 g++ 3.x、g++ 4.x、MSVC++9)和編譯器選項。在程式碼中擺弄 const v.s. const & 和 inline v.s. forceinline 可能有幫助,但結果似乎不像所預期的一樣一致。或許其他人想找出解決熱門編譯器問題的方法。此外,複雜的範本會增加建置時間並導致令人費解的編譯器錯誤。這一切似乎並未簡化事情,也不會提高生產力。

所以……我開始了一個新專案:LuaToCee。不再倚賴可怕的 C++ шабло來進行元程式設計,我現在使用 Lua 來編寫元程式設計。這令人滿意多了。雖然它可能無法達成上面提到的所有目標,但它有它自己的用途。

Lua C API 其實沒那麼糟。事實上,為了避免過度使用 C API,請盡可能用 Lua 來編寫程式碼,將其編譯為位元組碼,執行 BinToCee,並從 C 呼叫該函式,傳遞 C 的剩餘資料給它。有些技術也可以避免使用 C API 時的陷阱(例如堆疊毀損)。

作者

DavidManura

使用者留言

...

另請參閱


近期變更 · 偏好設定
編輯 · 歷史記錄
上次編輯時間 2009 年 10 月 31 日,下午 7:37(格林威治標準時間)(差異)