StkId结构:
typedef union StackValue {
TValue val;
} StackValue;
typedef StackValue *StkId;
StkId仅为一个正常的LUA变量
CallInfo结构:
typedef struct CallInfo {
StkId func;
StkId top;
struct CallInfo *previous, *next;
union {
struct {
const Instruction *savedpc;
volatile l_signalT trap;
int nextraargs;
} l;
struct {
lua_KFunction k;
ptrdiff_t old_errfunc;
lua_KContext ctx;
} c;
} u;
union {
int funcidx;
int nyield;
struct {
unsigned short ftransfer;
unsigned short ntransfer;
} transferinfo;
} u2;
short nresults;
unsigned short callstatus;
} CallInfo;
栈:CallInfo最主要的作用是记录一个函数调用涉及到的栈引用.每个函数的栈空间并不是一个独立的数组,而是所有函数公用一个共同的栈数据结构实例,func是与调用关联的函数对象,这个对象在线程的栈中,而top则是调用的栈顶,[func, top]就是这个调用使用的栈范围。
之前的LUA版本适用的数据结构如下图所示(由于未找到5.3版本的图):
可见函数对象在栈中,且CallInfo.func 为函数对象在栈中的序列号,栈的使用范围被限制在[func, top]中,且第一个参数序列号为1逐级向上
调用链:Lua在调用每个函数时,都会生成一个CallInfo,并将它们链接成一个双向链表。通过遍历这个链表,我们就可以知道整个调用链 & 调用关系,previous和next会使CallInfo形成一个双向链表,lua_State->ci则记录着当前的调用,这个链表可以用下图表示:
另外3个UNION结构暂放,先看此结构产生的相关环境
负责生成并填写此结构的函数为:luaD_call(ido.c):
void luaD_call (lua_State *L, StkId func, int nresults) {
lua_CFunction f;
retry:
switch (ttypetag(s2v(func))) {
case LUA_VCCL: /* C closure */
f = clCvalue(s2v(func))->f;
goto Cfunc;
case LUA_VLCF: /* light C function */
f = fvalue(s2v(func));
Cfunc: {
...
}
case LUA_VLCL: { /* Lua function */
...
}
default: { /* not a function */
checkstackGCp(L, 1, func); /* space for metamethod */
luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */
goto retry; /* try again with metamethod */
}
}
}
函数主体通过switch case调度,其中作为区分调度类别的关键变量:ttypetag(s2v(func))
ttypetag(s2v(func))--------->withvariant(rawtt(s2v(func)))
- func变量为StkId类型,其中只有一个成员变量为:TValue val
- #define s2v(o) (&(o)->val),即将func的真实TValue取出
- #define rawtt(o) ((o)->tt_),即将func变量的TValue成员的类型取出
- #define withvariant(t) ((t) & 0x3F),将成员类型与0x3F进行与运算
通过分析case的具体值可以得知各类型的值是多少,其中
LUA_VCCL:/* C closure */ :0x26(0x66 & 0x3F)
LUA_VLCF:/* light C function */ :0x16(0x16 & 0x3F)
LUA_VLCL:/* Lua function */ :0x6
对于C closure函数:
f = clCvalue(s2v(func))->f;
#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc))
且:ttisCclosure(o): ((o)->tt_) == 0x66
且:gco2ccl(val_(o).gc):取出func的GCObject成员用以初始化GCUnion结构体,并去除其中Closure类型的cl,再将cl中的CClosure c取出
union GCUnion {
GCObject gc; /* common header */
struct TString ts;
struct Udata u;
union Closure cl;
struct Table h;
struct Proto p;
struct lua_State th; /* thread */
struct UpVal upv;
};
而check_exp(true/false, CClosure c) == CClosure c,因此整体含义为初始化对于的CClosure c,
并将其中的lua_CFunction f成员取出赋值给f,然后执行Cfunc标签
对于light C函数:
f = fvalue(s2v(func)) 展开后为 f = ((((&(func)->val))->value_).f);
即直接取出func中的lua_CFunction f成员赋值给f,然后执行Cfunc函数
上述两个case均为赋值f后转向执行Cfunc标签,其源码如下:
int n; /* number of returns */
CallInfo *ci;
checkstackGCp(L, LUA_MINSTACK, func); /* ensure minimum stack size */
L->ci = ci = next_ci(L);
ci->nresults = nresults;
ci->callstatus = CIST_C;
ci->top = L->top + LUA_MINSTACK;
ci->func = func;
lua_assert(ci->top <= L->stack_last);
if (L->hookmask & LUA_MASKCALL) {
int narg = cast_int(L->top - func) - 1;
luaD_hook(L, LUA_HOOKCALL, -1, 1, narg);
}
lua_unlock(L);
n = (*f)(L); /* do the actual call */
lua_lock(L);
api_checknelems(L, n);
luaD_poscall(L, ci, n);
break;
创建CallInfo结构体后检查栈空间是否大于LUA_MINSTACK
填充ci结构体,L->ci = ci = (L->ci->next ? L->ci->next : luaE_extendCI(L)),如果存在nextci则直接填充,否则就新生成一个ci节点
进行其他结构体填充 & 检查栈越界问题
参考:【深入Lua:调用信息】深入Lua:调用信息.