(一)概述:
lua核心部分仅包括lua虚拟机的运转。lua虚拟机的行为是由一组组opcode控制的。这些opcode定义在lopcodes.h及lopcodes.c中。而虚拟机对opcode 的解析和运作在lvm.c中,其API以luaV为前缀。lua虚拟机的外在数据形式是一个lua_State结构体,取名State大概意为 Lua虚拟机的当前状态。全局State引用了整个虚拟机的所有数据。这个全局State的相关代码放在lstate.c中,API使用luaE为前缀。函数的运行流程:函数调用及返回则放在ldo.c中,相关API以luaD为前缀。
(二)字符串:
(1)字符串的初始化
在Lua状态机内有两种内部形式,短字符串和长字符串
#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) /* short strings */
#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */
typedef union TString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
struct {
CommonHeader;//用于GC
lu_byte extra; /* reserved words for short strings; "has hash" for longs */
unsigned int hash;//记录字符串的hash用来加快字符串的匹配
size_t len; /* number of characters in string */ //Lua不是以\0结尾,需要len表示字符串的长度
} tsv;
} TString;
所有短字符串均被存放在全局表(global_State)的strt域中,strt是string table的简写,是一个hash表。
typedef struct stringtable {
GCObject **hash;
lu_int32 nuse; /* number of elements */
int size;
} stringtable;
相同的短字符串在同一个Lua State中只存在唯一一份,这被称为字符串的内部化。
长字符串则独立存放,从外部压入一个长字符串时,简单复制一遍字符串,并不立刻计算其hash值,而是标记一下extra域。直到需要对字符串做键匹配时,才惰性计算hash值,加快以后的键比较过程
unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) {
unsigned int h = seed ^ cast(unsigned int, l);
size_t l1;
size_t step = (l >> LUAI_HASHLIMIT) + 1;
for (l1 = l; l1 >= step; l1 -= step)
h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1]));
return h;
}//对于长字符串,为了加快内部化的过程,计算长字符串哈希值是跳跃进行的。
(2)字符串的比较
int luaS_eqstr (TString *a, TString *b) { return (a->tsv.tt == b->tsv.tt) &&(a->tsv.tt == LUA_TSHRSTR ? eqshrstr(a, b) : luaS_eqlngstr(a, b));}
a.短字符串因为经过的内部化,所以在字符串比较时不必比较字符串内容,而仅需要比较对象地址即可。
static TString *internshrstr (lua_State *L, const char *str, size_t l) {
GCObject *o;
global_State *g = G(L);
unsigned int h = luaS_hash(str, l, g->seed);
for (o = g->strt.hash[lmod(h, g->strt.size)]; o != NULL;o = gch(o)->next)
{ TString *ts = rawgco2ts(o);
if (h == ts->tsv.hash &&l == ts->tsv.len &&(memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
if (isdead(G(L), o)) /* string is dead (but was not collected yet)? */
changewhite(o); /* resurrect it */ return ts;
}
}
return newshrstr(L, str, l, h); /* not found; create a new string */
}
这是一个开散列的哈希表实现。一个字符串被放入字符串表的时候,先检查一下表中有没有相同的字符串。如果有,则复用已有的字符串;没有则创建一个新的。碰到哈希值相同的字符串,简单的串在同一个哈希位的链表上即可.
当哈希表中字符串的数量超过预定容量时,会发生冲出,此时需要luaS_resize把字符串表的哈希链表数组扩大,重排所有字符串的位置。
void luaS_resize (lua_State *L, int newsize) {
int i;
stringtable *tb = &G(L)->strt;
/* cannot resize while GC is traversing strings */
luaC_runtilstate(L, ~bitmask(GCSsweepstring));
if (newsize > tb->size) {
luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *);
for (i = tb->size; i < newsize; i++) tb->hash[i] = NULL;
}
/* rehash */
for (i=0; i
GCObject *p = tb->hash[i];
tb->hash[i] = NULL;
while (p) { /* for each node in the list */
GCObject *next = gch(p)->next; /* save next */
unsigned int h = lmod(gco2ts(p)->hash, newsize); /* new position */ gch(p)->next = tb->hash[h]; /* chain it */
tb->hash[h] = p;
resetoldbit(p); /* see MOVE OLD rule */
p = next;
}
}
if (newsize < tb->size) {
/* shrinking slice must be empty */
lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL);
luaM_reallocvector(L, tb->hash, tb->size, newsize, GCObject *);
}
tb->size = newsize;
}
(3)UserData
UserData在Lua中并没有太特别的地方,在储存形式上和字符串相同。可以看成是拥有独立元表,不被内部化处理,也不需要追加\0的字符串。在实现上,只是对象结构从Tstring换成了UData。所以实现代码也被放在lstring.c中,其api也以luaS开头。
typedef union Udata {
L_Umaxalign dummy; /* ensures maximum alignment for ‘local’ udata */
struct {
CommonHeader;
struct Table *metatable;
struct Table *env;
size_t len; /* number of bytes */
}uv;
} Udata;
(三)表
Lua的官方实现,又把table的储存分为数组部分和哈希表部分。数组部分从1开始作整数数字索引。这可以提供紧凑且高效的随机访问。而不能被储存在数组部分的数据全部放在哈希表中,唯一不能做哈希键值的是nil,这个限制可以 帮助我们发现许多运行期错误。lua的哈希表有一个高效的实现,几乎可以认为操作哈希表的时间复杂度为O(1)。
typedef union TKey {
struct {
TValuefields;
int next; /* for chaining (offset for next node) */
} nk;
TValue tvk;
} TKey;
typedef struct Node {
TValue i_val;
TKey i_key;
} Node;
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
(1)写
写操作被实现为查询已有键位,若不存在则创建新键。得到键位后,写入操作就是一次赋值。所以,在 table模块中,实际实现的基本操作为:创建、查询、迭代和获取长度。创建操作的api为luaH_newkey ,阅读它的实现就能对整个table有一个全面的认识。它只负责在哈希 表中创建出一个不存在的键,而不关数组部分的工作。
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
TValue aux;
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Integer k;
if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
setivalue(&aux, k);
key = &aux; /* insert it as an integer */
}
else if (luai_numisnan(fltvalue(key)))
luaG_runerror(L, "table index is NaN");
}
mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */
Node *othern;
Node *f = getfreepos(t); /* get a free place */
if (f == NULL) { /* cannot find a free place? */
rehash(L, t, key); /* grow table */
/* whatever called 'newkey' takes care of TM cache */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(t));
othern = mainposition(t, gkey(mp));
if (othern != mp) { /* is colliding node out of its main position? */
/* yes; move colliding node into free position */
while (othern + gnext(othern) != mp) /* find previous */
othern += gnext(othern);
gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */
*f = *mp; /* copy colliding node into free pos. (mp->next also goes) */
if (gnext(mp) != 0) {
gnext(f) += cast_int(mp - f); /* correct 'next' */
gnext(mp) = 0; /* now 'mp' is free */
}
setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position */
/* new node will go into free position */
if (gnext(mp) != 0)
gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */
else lua_assert(gnext(f) == 0);
gnext(mp) = cast_int(f - mp);
mp = f;
}
}
setnodekey(L, &mp->i_key, key);
luaC_barrierback(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}
lua的哈希表以闭散列3方式实现。每个可能的键值,在哈希表中都有一个主要位置,称作 mainposition。创建一个新键时,检查mainposition,若无人使用,则可以直接设置为这个新键。若之前有其它键占据了这个位置,则检查占据此位置的键的主位置是不是这里。若两者位置冲突,则利用Node结构中的next域,以一个单向链表的形式把它们链起来;否则,新键占据这个位置,而老键更换到新位置并根据它的主键找到属于它的链的那条单向链表中上一个结点,重新链入。无论是哪种冲突情况,都需要在哈希表中找到一个空闲可用的结点。这里是在 getfreepos函数中,递减lastfree域来实现的。lua也不会在设置键位的值为nil时而回收空间,而是在预先准备好的哈希空间使用完 后惰性回收。即在lastfree递减到哈希空间头时,做一次rehash操作。
static void rehash (lua_State *L, Table *t, const TValue *ek) {
unsigned int asize; /* optimal size for array part */
unsigned int na; /* number of keys in the array part */
unsigned int nums[MAXABITS + 1];
int i;
int totaluse;
for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */
na = numusearray(t, nums); /* count keys in array part */
totaluse = na; /* all those keys are integer keys */
totaluse += numusehash(t, nums, &na); /* count keys in hash part */
/* count extra key */
na += countint(ek, nums);
totaluse++;
/* compute new size for array part */
asize = computesizes(nums, &na);
/* resize the table to new computed sizes */
luaH_resize(L, t, asize, totaluse - na);
}
refresh的主要工作是统计当前table中到底有多少有效键值对,以及决定数组部分需要开辟多少空间。其原则是最终数组部分的利用率需要超过50%。lua使用一个refresh函数中定义在栈上的nums数组来做这个整数键统计工作。这个数组按2的整数幂次来分开统计各个区段间的整数键个数。统计过程的实现见nummusearray和 numusehash函数。最终,computesizes函数计算出不低50%利用率下,数组该维持多少空间。同时,还可以得到有多少有效键将被储存在哈希表里。根据这些统计数据,refresh函数调用 luaH_resize这个api来重新调整数组部分和哈希部分的大小,并把不能放在数组里的键值对重新塞入哈希表。
(2)表的迭代
在Lua中,并没有提供一个自维护状态的迭代器。而是给出了一个next方法。传入上一个键,返回下一个键值对。这就是luaH_next所要实现的。
int luaH_next (lua_State *L, Table *t, StkId key) {
unsigned int i = findindex(L, t, key); /* find original element */
for (; i < t->sizearray; i++) { /* try first array part */
if (!ttisnil(&t->array[i])) { /* a non-nil value? */
setivalue(key, i + 1);
setobj2s(L, key+1, &t->array[i]);
return 1;
}
}
for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */
if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */
setobj2s(L, key, gkey(gnode(t, i)));
setobj2s(L, key+1, gval(gnode(t, i)));
return 1;
}
}
return 0; /* no more elements */
}
它尝试返回传入的key在数组部分中的下一个非空值。当超出数组部分后,则检索哈希表中的对应位置,并返回哈希表中对应节点在储存空间分布上的下一个节点处的键值对。在大多数其它语言中,遍历一个无序集合的过程中,通常不允许对这个集合做任何修改。即使允许,也可能产生未定义的结果。在lua中也一样,遍历一个table的过程中,向这个 table插入一个新键这个行为,将无法预测后续的遍历行为,但是lua却允许在遍历过程中,修改table中已存在的键对应的值。由于lua没有显式的从table中删除键的操作,只能对不需要的键设为空。一旦在迭代过程中发生垃圾收集,对键值赋值为空的操作就有可能导致垃圾收集过程中把这个键值对标 记为死键。所以,在next操作中,从上一个键定位下一个键时,需要支持检索一个死键,查询这个死键的 下一个键位。具体代码见findindex的实现:
static unsigned int findindex (lua_State *L, Table *t, StkId key) {
unsigned int i;
if (ttisnil(key)) return 0; /* first iteration */
i = arrayindex(key);
if (i != 0 && i <= t->sizearray) /* is 'key' inside array part? */
return i; /* yes; that's the index */
else {
int nx;
Node *n = mainposition(t, key);
for (;;) { /* check whether 'key' is somewhere in the chain */
/* key may be dead already, but it is ok to use it in 'next' */
if (luaV_rawequalobj(gkey(n), key) ||
(ttisdeadkey(gkey(n)) && iscollectable(key) &&
deadvalue(gkey(n)) == gcvalue(key))) {
i = cast_int(n - gnode(t, 0)); /* key index in hash table */
/* hash elements are numbered after array ones */
return (i + 1) + t->sizearray;
}
nx = gnext(n);
if (nx == 0)
luaG_runerror(L, "invalid key to 'next'"); /* key not found */
else n += nx;
}
}
}
lua的table的长度定义只对序列表有效。所以,在实现的时候,仅需要遍历table的数组部分。只有当数组部分填满时才需要进一步的去检索哈希表。它使用二分法,来快速在哈希表中快速定位一个非空的整数键的位置。
static int unbound_search (Table *t, unsigned int j) {
unsigned int i = j; /* i is zero or a present index */
j++;
/* find 'i' and 'j' such that i is present and j is not */
while (!ttisnil(luaH_getint(t, j))) {
i = j;
if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */
/* table was built with bad purposes: resort to linear search */
i = 1;
while (!ttisnil(luaH_getint(t, i))) i++;
return i - 1;
}
j *= 2;
}
/* now do a binary search between them */
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(luaH_getint(t, m))) j = m;
else i = m;
}
return i;
}
/*
** Try to find a boundary in table 't'. A 'boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (isdummy(t)) /* hash part is empty? */
return j; /* that is easy... */
else return unbound_search(t, j);
}
(四)函数:
(1)upvalue
Upvalue指在闭包生成的那一刻,与函数原型绑定在一起的那些外部变量。这些变量原本是上一层函数的局部变量或 upvalue ,可以在上层返回返回后,继续被闭包引用。遍历当前所有开放的upvalue利用的是当前线程中记录的链表 openupval。这是一个双向链表,所以在UpVal结构中有两个指针指向前后节点。链表指针保存在一个联合中。当upvalue被关闭后,就不再需要这两个指针了。所谓关闭upvalue,就是当upvalue引用的数据栈上的数据不再存在于栈上时(通常是由申请局部变量的函数返回引起的),需要把upvalue从开放链表中拿掉,并把其引用的数据栈上的变量值换一个安全的地方存放。这个安全所在就是UpVal结构体内。无须用特别的标记位区分一个UpVal在开放还是关闭状态。当upvalue关闭时,UpVal中的指针v一定 指向结构内部的value 。
struct UpVal {
TValue *v; /* points to stack or to its own value */
lu_mem refcount; /* reference counter */
union {
struct { /* (when open) */
UpVal *next; /* linked list */
int touched; /* mark to avoid cycles with dead threads */
} open;
TValue value; /* the value (when closed) */
} u;
};
(2)闭包
a.lua闭包
构造Lua闭包的内部APIluaF_newLclosure 只绑定了Proto却不包括初始化upvalue对象的过程。这是因为构造 Lua闭包有两种可能的途径。
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
LClosure *luaF_newLclosure (lua_State *L, int n) {
GCObject *o = luaC_newobj(L, LUA_TLCL, sizeLclosure(n));
LClosure *c = gco2lcl(o);
c->p = NULL;
c->nupvalues = cast_byte(n);
while (n--) c->upvals[n] = NULL;
return c;
}
第一、Lua闭包一般在虚拟机运行的过程中被动态构造出来的。这时,闭包需引用的upvalue都在当前的数据栈上。利用luaF_findupval这个 API 可以把数据栈上的值转换为upvalue 。
UpVal *luaF_findupval (lua_State *L, StkId level) {
UpVal **pp = &L->openupval;
UpVal *p;
UpVal *uv;
lua_assert(isintwups(L) || L->openupval == NULL);
while (*pp != NULL && (p = *pp)->v >= level) {
lua_assert(upisopen(p));
if (p->v == level) /* found a corresponding upvalue? *///在当前的openupval链表中寻找是否已经转化过,如果有就复用之
return p; /* return it */
pp = &p->u.open.next;
}
/* not found: create a new upvalue */
//构造一个新的 upvalue 对象,并把它串到 openupval 链表中
uv = luaM_new(L, UpVal);
uv->refcount = 0;
uv->u.open.next = *pp; /* link it to list of open upvalues */
uv->u.open.touched = 1;
*pp = uv;
uv->v = level; /* current value lives in the stack */
if (!isintwups(L)) { /* thread not in list of threads with upvalues? */
L->twups = G(L)->twups; /* link it to the list */
G(L)->twups = L;
}
return uv;
}
第二种生成 Lua 闭包的方式是加载一段 Lua 代码。每段 Lua 代码都会被编译成一个函数原型,但 Lua 的外部 API 是不返回函数原型对象的,而是把这个函数原型转换为一个 Lua 闭包对象。如果从源代码加载 的话,不可能有用户构建出来的 upvalue 的。但是,任何一个代码块都至少有一个 upvalue : ENV。(Lua 的核心不再区分全局表、环境表这些。访问全局变量只是对 ENV 这张表访问的语法糖。 ENV 必须被每一个 Lua 函数可见,所以被放在 Lua 代码块的第一个 upvalue 中).如果用户试图 dump 一个拥有多个 upvalue 的 Lua 闭包,它会得到一个 upvalue 数量不为一的函数原型 的二进制数据块。undump 这个数据块,就将生成多个 upvalue 的闭包。这些 upvalue 并不源于数据栈,所以是用 luaF newupval 新构造出来的
//f_parser 函数把加载进内存的函数原型和 upvalue 绑定起来
static void f_parser (lua_State *L, void *ud) {
LClosure *cl;
struct SParser *p = cast(struct SParser *, ud);
int c = zgetc(p->z); /* read first character */
if (c == LUA_SIGNATURE[0]) {
checkmode(L, p->mode, "binary");
cl = luaU_undump(L, p->z, p->name);
}
else {
checkmode(L, p->mode, "text");
cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);
}
lua_assert(cl->nupvalues == cl->p->sizeupvalues);
luaF_initupvals(L, cl);
}
b.C闭包
和 Lua 闭包不同,在 C 函数中不会去引用外层函数中的局部变量。所以,C 闭包中的 upvalue 天生 就是关闭状态的。Lua 也就不需要用独立的 UpVal 对象来引用它们。对于 C 闭包,upvalue 是直接存放在 CClosure 结构中的。
CClosure *luaF_newCclosure (lua_State *L, int n) {
GCObject *o = luaC_newobj(L, LUA_TCCL, sizeCclosure(n));
CClosure *c = gco2ccl(o);
c->nupvalues = cast_byte(n);
return c;
}
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
(五)协程及函数的执行
lua State是暴露给用户的数据类型。从名字上看,它想表示一个Lua程序的执行状态,在官方文档中,它指代Lua 的一个线程。每个线程拥有独立的数据栈以及函数调用链,还有独立的调试钩子和错误处理设施。所以我们不应当简单的把lua State看成一个静态的数据集,它是一组Lua程序的执行状态机。所有的Lua C API都是围绕这个状态机,改变其状态的:或把数据压入堆栈,或取出,或执行栈顶的函数,或继续 上次被中断的执行过程。
同一 Lua 虚拟机中的所有执行线程,共享了一块全局数据global State。在Lua的实现代码中,需要访问这个结构体的时候,会调用宏
#define G(L) (L->l_G)
/*
** 'global state', shared by all threads of this state
*/
typedef struct global_State {
lua_Alloc frealloc; /* function to reallocate memory */
void *ud; /* auxiliary data to 'frealloc' */
l_mem totalbytes; /* number of bytes currently allocated - GCdebt */
l_mem GCdebt; /* bytes allocated not yet compensated by the collector */
lu_mem GCmemtrav; /* memory traversed by the GC */
lu_mem GCestimate; /* an estimate of the non-garbage memory in use */
stringtable strt; /* hash table for strings */
TValue l_registry;
unsigned int seed; /* randomized seed for hashes */
lu_byte currentwhite;
lu_byte gcstate; /* state of garbage collector */
lu_byte gckind; /* kind of GC running */
lu_byte gcrunning; /* true if GC is running */
GCObject *allgc; /* list of all collectable objects */
GCObject **sweepgc; /* current position of sweep in list */
GCObject *finobj; /* list of collectable objects with finalizers */
GCObject *gray; /* list of gray objects */
GCObject *grayagain; /* list of objects to be traversed atomically */
GCObject *weak; /* list of tables with weak values */
GCObject *ephemeron; /* list of ephemeron tables (weak keys) */
GCObject *allweak; /* list of all-weak tables */
GCObject *tobefnz; /* list of userdata to be GC */
GCObject *fixedgc; /* list of objects not to be collected */
struct lua_State *twups; /* list of threads with open upvalues */
unsigned int gcfinnum; /* number of finalizers to call in each GC step */
int gcpause; /* size of pause between successive GCs */
int gcstepmul; /* GC 'granularity' */
lua_CFunction panic; /* to be called in unprotected errors */
struct lua_State *mainthread;
const lua_Number *version; /* pointer to version number */
TString *memerrmsg; /* memory-error message */
TString *tmname[TM_N]; /* array with tag-method names */
struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */
} global_State;
static void stack_init (lua_State *L1, lua_State *L) {
int i; CallInfo *ci;
/* initialize stack array */
L1->stack = luaM_newvector(L, BASIC_STACK_SIZE, TValue);
L1->stacksize = BASIC_STACK_SIZE;
for (i = 0; i < BASIC_STACK_SIZE; i++)
setnilvalue(L1->stack + i); /* erase new stack */
L1->top = L1->stack;
L1->stack_last = L1->stack + L1->stacksize - EXTRA_STACK;
/* initialize first ci */
ci = &L1->base_ci;
ci->next = ci->previous = NULL;
ci->callstatus = 0;
ci->func = L1->top;
setnilvalue(L1->top++); /* 'function' entry for this 'ci' */
ci->top = L1->top + LUA_MINSTACK;
L1->ci = ci;
}
static void freestack (lua_State *L) {
if (L->stack == NULL)
return; /* stack not completely built yet */
L->ci = &L->base_ci; /* free the entire 'ci' list */
luaE_freeCI(L);
lua_assert(L->nci == 0);
luaM_freearray(L, L->stack, L->stacksize); /* free stack array */
}
#define BASIC_STACK_SIZE (2*LUA_MINSTACK)//数据栈的空间很有限,只有2倍的LUA MINSTACK的大小
Lua供C使用的栈相关API都是不检查数据栈越界的,这是因为通常我们编写C扩展都能把数据栈空间的使用控制在LUA MINSTACK以内,或是显式扩展。对每次数据栈访问都强制做越界检查是非常低效的。数据栈不够用的时候,可以扩展。这种扩展是用realloc实现的,每次至少分配比原来大一倍的空间,并把旧的数据复制到新空间。
#define ERRORSTACKSIZE (LUAI_MAXSTACK + 200)
void luaD_reallocstack (lua_State *L, int newsize) {
TValue *oldstack = L->stack;
int lim = L->stacksize;
lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE);
lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
luaM_reallocvector(L, L->stack, L->stacksize, newsize, TValue);
for (; lim < newsize; lim++)
setnilvalue(L->stack + lim); /* erase new segment */
L->stacksize = newsize;
L->stack_last = L->stack + newsize - EXTRA_STACK;
correctstack(L, oldstack);
}
void luaD_growstack (lua_State *L, int n) {
int size = L->stacksize;
if (size > LUAI_MAXSTACK) /* error after extra size? */
luaD_throw(L, LUA_ERRERR);
else {
int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK;
int newsize = 2 * size;
if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK;
if (newsize < needed) newsize = needed;
if (newsize > LUAI_MAXSTACK) { /* stack overflow? */
luaD_reallocstack(L, ERRORSTACKSIZE);
luaG_runerror(L, "stack overflow");
}
else
luaD_reallocstack(L, newsize);
}
}
数据栈扩展的过程,伴随着数据拷贝。这些数据都是可以直接值复制的,所以不需要在扩展之后修正其 中的指针。但,有些外部结构对数据栈的引用需要修正为正确的新地址。这些需要修正的位置包括 upvalue 以及执行栈对数据栈的引用
static void correctstack (lua_State *L, TValue *oldstack) {
CallInfo *ci;
UpVal *up;
L->top = (L->top - oldstack) + L->stack;
for (up = L->openupval; up != NULL; up = up->u.open.next)
up->v = (up->v - oldstack) + L->stack;
for (ci = L->ci; ci != NULL; ci = ci->previous) {
ci->top = (ci->top - oldstack) + L->stack;
ci->func = (ci->func - oldstack) + L->stack;
if (isLua(ci))
ci->u.l.base = (ci->u.l.base - oldstack) + L->stack;
}
}
Lua 把调用栈和数据栈分开保存。调用栈放在一个叫做 CallInfo 的结构中,以双向链表的形式储存在线程对象里
typedef struct CallInfo {
StkId func; /* function index in the stack */
StkId top; /* top for this function */
struct CallInfo *previous, *next; /* dynamic call link */
union {
struct { /* only for Lua functions */
StkId base; /* base for this function */
const Instruction *savedpc;
} l;
struct { /* only for C functions */
lua_KFunction k; /* continuation in case of yields */
ptrdiff_t old_errfunc;
lua_KContext ctx; /* context info. in case of yields */
} c;
} u;
ptrdiff_t extra;
short nresults; /* expected number of results from this function */
unsigned short callstatus;//保存着正在调用的函数的运行状态
} CallInfo;
#define isLua(ci) ((ci)->callstatus & CIST_LUA)
线程:把数据栈和调用栈合起来就构成了 Lua 中的线程。在同一个 Lua 虚拟机中的不同线程因为共享了 global State 而很难做到真正意义上的并发。它也绝非操作系统意义上的线程,但在行为上很相似。用户可以 resume 一个线程,线程可以被 yield 打断。Lua 的执行过程就是围绕线程进行的
LUA_API lua_State *lua_newthread (lua_State *L) {
global_State *g = G(L);
lua_State *L1;
lua_lock(L);
luaC_checkGC(L);
/* create new thread */
L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l;
L1->marked = luaC_white(g);
L1->tt = LUA_TTHREAD;
/* link it on list 'allgc' */
L1->next = g->allgc;
g->allgc = obj2gco(L1);
/* anchor it on L stack */
setthvalue(L, L->top, L1);
api_incr_top(L);
preinit_thread(L1, g);
L1->hookmask = L->hookmask;
L1->basehookcount = L->basehookcount;
L1->hook = L->hook;
resethookcount(L1);
/* initialize L1 extra space */
memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread),
LUA_EXTRASPACE);
luai_userstatethread(L, L1);
stack_init(L1, L); /* init stack */
lua_unlock(L);
return L1;
}
/*
** thread state + extra space
*/
typedef struct LX {
lu_byte extra_[LUA_EXTRASPACE];
lua_State l;
} LX;
在 lua State 之前留出了大小为 LUAI EXTRASPACE 字节的空间。面对外部用户操作的指针是 L 而不 是 LX ,但 L 所占据的内存块的前面却是有所保留的。
(1)异常处理