bingoohuang/blog

剖析一下LuaJIT的Table

Opened this issue · 0 comments

剖析一下LuaJIT的Table

  1. 原文
  2. slides
  3. the basics and design of lua table.pdf

安装LuaJIT

因为是在openresty里面使用LuaJIT的,那么就直接从openresty/luajit2下载一份源代码吧。

看到有Makefile文件,直接make报错,

~/Downloads/luajit2-2.1-agentzh 
🕙[ 21:19:27 ] ❯ make        
==== Building LuaJIT 2.1.0-beta3 ====
/Library/Developer/CommandLineTools/usr/bin/make -C src
Makefile:323: *** missing: export MACOSX_DEPLOYMENT_TARGET=XX.YY.  Stop.
make: *** [default] Error 2

谷歌大法,找到MacOS源码安装LuaJIT

$ export MACOSX_DEPLOYMENT_TARGET=10.14
$ make install PREFIX=~/luajit

然后设置一下PATH环境变量,就可以看到luajit装好了

🕙[ 22:41:36 ] ❯ which luajit
/Users/bingoobjca/luajit/bin/luajit

🕙[ 22:41:59 ] ❯ luajit -v
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2020 Mike Pall. https://luajit.org/

hack源代码,增加打印table信息

在src/lj_strfmt.c中,修正函数lj_strfmt_obj,增加对tostring的处置

/* Raw conversion of object to string. */
GCstr * LJ_FASTCALL lj_strfmt_obj(lua_State *L, cTValue *o)
{
  if (tvisstr(o)) {
    return strV(o);
  } else if (tvisnumber(o)) {
    return lj_strfmt_number(L, o);
  } else if (tvisnil(o)) {
    return lj_str_newlit(L, "nil");
  } else if (tvisfalse(o)) {
    return lj_str_newlit(L, "false");
  } else if (tvistrue(o)) {
    return lj_str_newlit(L, "true");
  } else {
    char buf[8+2+2+16], *p = buf;
    p = lj_buf_wmem(p, lj_typename(o), (MSize)strlen(lj_typename(o)));
    *p++ = ':'; *p++ = ' ';
    if (tvisfunc(o) && isffunc(funcV(o))) {
      p = lj_buf_wmem(p, "builtin#", 8);
      p = lj_strfmt_wint(p, funcV(o)->c.ffid);
    } else if (tvistab(o)) { // 从这里开始是新增的代码
      p = lj_strfmt_wptr(p, lj_obj_ptr(G(L), o));
      *p++ = '\0';
      printf("-- %s\n", buf);

      GCtab *t = tabV(o);
      /* print array part */ {
        uint32_t i, asize = t->asize;
        printf("--  a[%d]: ", asize);
        TValue *array = tvref(t->array);
        for (i = 0; i < asize; i++) {
          if (i>0) printf(", ");

          cTValue *o = &array[i];
          if (tvisstr(o)) printf("%s", strdata(strV(o)));
          else if (tvisnum(o)) printf("%g", numV(o));
          else if (tvisnil(o)) printf("nil");
          else printf("?");
        }
        printf("\n");
      }

      /* print hashmap part */ {
        uint32_t i, hmask = t->hmask;
        printf("--  h[%d]: ", hmask+1);
        Node *node = noderef(t->node);
        for (i = 0; i <= hmask; i++) {
          if (i>0) printf(", ");

          Node *n = &node[i];
          cTValue *k = &n->key;
          cTValue *v = &n->val;

          if (tvisstr(k)) printf("%s", strdata(strV(k)));
          else if (tvisnum(k)) printf("%g", numV(k));
          else if (tvisnil(k)) printf("nil");
          else printf("?");
          printf("=");
          if (tvisstr(v)) printf("%s", strdata(strV(v)));
          else if (tvisnum(v)) printf("%g", numV(v));
          else if (tvisnil(v)) printf("nil");
          else printf("?");
        }
        printf("\n");
      }
     // 从这里新增的代码结束
    } else {
      p = lj_strfmt_wptr(p, lj_obj_ptr(G(L), o));
    }
    return lj_str_new(L, buf, (size_t)(p - buf));
  }
}

剖析table

habr.lua

jit.off()
ffi = require('ffi')
require('table.new')
require('table.clear')

local function caption(h, c)
    print(h)
    print(string.rep(c or '-', #h))
    print()
end

function traverse(fn, t)
    local str = '-- '
    for k, v, n in (fn or pairs)(t) do
        if str ~= '-- ' then
            str = str .. ', '
        end
        str = string.format('%s%s=%s%s',
            str, k, v, n and ', ' or ''
        )
    end
    print(str)
end

local function codeblock(str)
    print('```lua')
    for l in string.gmatch(str, "([^\n]*)\n") do
        print(l)
        assert(loadstring(l))()
    end
    print('```\n')
end

caption('Welcome to the LuaJIT internals', '=')

--------------------------
caption('Анатомия таблиц')

codeblock([[
t = {}
tostring(t)
]])

codeblock([[
t["a"] = "A"
t["b"] = "B"
t["c"] = "C"
tostring(t)
]])

codeblock([[
t1 = {a = 1, b = 2, c = 3}
tostring(t1)

t2 = {c = 3, b = 2, a = 1}
tostring(t2)

traverse(pairs, t1)

traverse(pairs, t2)
]])

codeblock([[
t2["c"] = nil
traverse(pairs, t2)
tostring(t2)
print('-- ', next(t2, "c"))
]])

------------------
caption('Массивы')

codeblock([[
t = {1, 2}
tostring(t)
]])

codeblock([[
t = {[2] = 2, 1}
tostring(t)
]])

---------------------------
caption('Итератор pairs()')

codeblock([[
t = table.new(4, 4)
for i = 1, 8 do t[i] = i end

tostring(t)
traverse(pairs, t)
]])

codeblock([[
t[9] = 9
tostring(t)
]])

-------------------------------------
caption('Длина таблицы table.getn()')

codeblock([[
print(#{nil, 2})
print(#{[2] = 2})
]])

codeblock([[
tostring({nil, 2})

tostring({[2] = 2})
]])

----------------------------------
caption('Сортировка table.sort()')

----------------------------------
caption('Pack / unpack')

codeblock([[
t = {nil, 2}
tostring(t)

traverse(pairs, t)
print(unpack(t, 1, t.n))
]])

----------------------------
caption('Итератор ipairs()')

codeblock([[
t = {1, 2, nil, 4}
print(#t) -- UB
traverse(ipairs, t)
]])
------------------------------
caption('Подводные камни FFI')

codeblock([[
NULL = ffi.new('void*', nil)
print(type(NULL))
print(type(nil))
print(NULL == nil)
if NULL then print('NULL is not nil') end
]])

os.exit(1)

运行

🕙[ 22:46:35 ] ❯ luajit habr.lua
Welcome to the LuaJIT internals
===============================

Анатомия таблиц

t = {}
tostring(t)
-- table: 0x07cd7c80
--  a[0]: 
--  h[1]: nil=nil
t["a"] = "A"
t["b"] = "B"
t["c"] = "C"
tostring(t)
-- table: 0x07cd7c80
--  a[0]: 
--  h[4]: nil=nil, b=B, a=A, c=C
t1 = {a = 1, b = 2, c = 3}
tostring(t1)
-- table: 0x07cd8890
--  a[0]: 
--  h[4]: nil=nil, b=2, a=1, c=3

t2 = {c = 3, b = 2, a = 1}
tostring(t2)
-- table: 0x07cd8f08
--  a[0]: 
--  h[4]: nil=nil, b=2, c=3, a=1

traverse(pairs, t1)
-- b=2, a=1, c=3

traverse(pairs, t2)
-- b=2, c=3, a=1
t2["c"] = nil
traverse(pairs, t2)
-- b=2, a=1
tostring(t2)
-- table: 0x07cd8f08
--  a[0]: 
--  h[4]: nil=nil, b=2, c=nil, a=1
print('-- ', next(t2, "c"))
--      a       1

Массивы

t = {1, 2}
tostring(t)
-- table: 0x07cda5d8
--  a[3]: nil, 1, 2
--  h[1]: nil=nil
t = {[2] = 2, 1}
tostring(t)
-- table: 0x07cdaa28
--  a[2]: nil, 1
--  h[2]: nil=nil, 2=2

Итератор pairs()

t = table.new(4, 4)
for i = 1, 8 do t[i] = i end

tostring(t)
-- table: 0x07cdaec0
--  a[5]: nil, 1, 2, 3, 4
--  h[4]: 7=7, 8=8, 5=5, 6=6
traverse(pairs, t)
-- 1=1, 2=2, 3=3, 4=4, 7=7, 8=8, 5=5, 6=6
t[9] = 9
tostring(t)
-- table: 0x07cdaec0
--  a[17]: nil, 1, 2, 3, 4, 5, 6, 7, 8, 9, nil, nil, nil, nil, nil, nil, nil
--  h[1]: nil=nil

Длина таблицы table.getn()

print(#{nil, 2})
2
print(#{[2] = 2})
0
tostring({nil, 2})
-- table: 0x07cdc660
--  a[3]: nil, nil, 2
--  h[1]: nil=nil

tostring({[2] = 2})
-- table: 0x07cdc848
--  a[0]: 
--  h[2]: nil=nil, 2=2

Сортировка table.sort()

Pack / unpack

t = {nil, 2}
tostring(t)
-- table: 0x07cdcdd0
--  a[3]: nil, nil, 2
--  h[1]: nil=nil

traverse(pairs, t)
-- 2=2
print(unpack(t, 1, t.n))
nil     2

Итератор ipairs()

t = {1, 2, nil, 4}
print(#t) -- UB
4
traverse(ipairs, t)
-- 1=1, 2=2

Подводные камни FFI

NULL = ffi.new('void*', nil)
print(type(NULL))
cdata
print(type(nil))
nil
print(NULL == nil)
true
if NULL then print('NULL is not nil') end
NULL is not nil