Lua5.3 VM 分析(二)表處理

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);

發佈了198 篇原創文章 · 獲贊 36 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章