Lua5.3 VM 分析(二)表處理
luaV_gettable
luaV_gettable 函數實現了Table類型的讀操作,可能觸發元方法。
/*
** Main function for table access (invoking metamethods if needed).
** Compute 'val = t[key]'
*/
void
luaV_gettable(lua_State *L, const TValue *t, TValue *key, StkId val)
{
int loop; /* counter to avoid infinite loops */
for (loop = 0; loop < MAXTAGLOOP; loop++) {
const TValue *tm;
if (ttistable(t)) { /* 't' is a table? */
Table *h = hvalue(t);
const TValue *res = luaH_get(h, key); /* do a primitive get */
if (!ttisnil(res) || /* result is not nil? */
(tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
setobj2s(L, val, res); /* result is the raw get */
return;
}
/* else will try metamethod */
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
luaG_typeerror(L, t, "index"); /* no metamethod */
if (ttisfunction(tm)) { /* metamethod is a function */
luaT_callTM(L, tm, t, key, val, 1);
return;
}
t = tm; /* else repeat access over 'tm' */
}
luaG_runerror(L, "gettable chain too long; possible loop");
}
OP_GETTABUP、OP_GETTABLE、OP_SELF 這三種指令會調用 luaV_gettable 函數對錶做讀操作。
處理元表的深度最大爲 MAXTAGLOOP (2000)層。越深的嵌套性能越低下。
當操作對象不可以按照 表的模式去索引時,利用luaG_typeerror 拋出異常,中斷死循環執行。
如果元表中的index 並不對應一張表,而是一個 函數的時候,就會引發一次元方法調用。它由luaT_callTM 函數實現。
void
luaT_callTM(lua_State *L, const TValue *f, const TValue *p1,
const TValue *p2, TValue *p3, int hasres)
{
ptrdiff_t result = savestack(L, p3);
setobj2s(L, L->top++, f); /* push function (assume EXTRA_STACK) */
setobj2s(L, L->top++, p1); /* 1st argument */
setobj2s(L, L->top++, p2); /* 2nd argument */
if (!hasres) /* no result? 'p3' is third argument */
setobj2s(L, L->top++, p3); /* 3rd argument */
/* metamethod may yield only when called from Lua code */
luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci));
if (hasres) { /* if has result, move it to its place */
p3 = restorestack(L, result);
setobjs2s(L, p3, --L->top);
}
}
callTM 的hasres 參數表示是否需要輸出。有輸出時,元方法只有兩個輸入參數(參數一 p1 和 參數二 p2),第三個參數 p3 作爲輸出。
所有的元方法都有三個參數:
1. 參數一一定是對象本身。 是輸入值,只讀。
2. 參數二則根據元方法的不同而不同。對於表操作,參數二爲Key 值;而對於二元運算操作則是第二個參數的數值。是輸入值,只讀。
3. 參數三則可以是輸入也可以是輸出使用。
luaV_settable
luaV_settable 函數實現了Table 類型的寫操作,可能觸發元方法。
/*
** Main function for table assignment (invoking metamethods if needed).
** Compute 't[key] = val'
*/
void
luaV_settable(lua_State *L, const TValue *t, TValue *key, StkId val)
{
int loop; /* counter to avoid infinite loops */
for (loop = 0; loop < MAXTAGLOOP; loop++) {
const TValue *tm;
if (ttistable(t)) { /* 't' is a table? */
Table *h = hvalue(t);
TValue *oldval = cast(TValue *, luaH_get(h, key));
/* if previous value is not nil, there must be a previous entry
in the table; a metamethod has no relevance */
if (!ttisnil(oldval) ||
/* previous value is nil; must check the metamethod */
((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL &&
/* no metamethod; is there a previous entry in the table? */
(oldval != luaO_nilobject ||
/* no previous entry; must create one. (The next test is
always true; we only need the assignment.) */
(oldval = luaH_newkey(L, h, key), 1)))) {
/* no metamethod and (now) there is an entry with given key */
setobj2t(L, oldval, val); /* assign new value to that entry */
invalidateTMcache(h);
luaC_barrierback(L, h, val);
return;
}
/* else will try the metamethod */
}
else /* not a table; check metamethod */
if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
luaG_typeerror(L, t, "index");
/* try the metamethod */
if (ttisfunction(tm)) {
luaT_callTM(L, tm, t, key, val, 0);
return;
}
t = tm; /* else repeat assignment over 'tm' */
}
luaG_runerror(L, "settable chain too long; possible loop");
}
OP_SETTABUP、OP_SETTABLE 這兩種指令會調用luaV_settable 函數對錶做寫操作。
由於Lua 表的刪除操作使用 對應 鍵值設置爲nil來實現,所以這裏有可能會導致Lua 內其它對象的生命期變化,這涉及到了 GC 的工作。
invalidateTMcache(h);
luaC_barrierback(L, h, val);