精华内容
下载资源
问答
  • lua源码解析

    千次阅读 2018-01-16 17:05:50
    而虚拟机对opcode 的解析和运作在lvm.c中,其API以luaV为前缀。lua虚拟机的外在数据形式是一个lua_State结构体,取名State大概意为 Lua虚拟机的当前状态。全局State引用了整个虚拟机的所有数据。这

    (一)概述:

    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<tb->size; 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<<p means tagmethod(p) is not present */
      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)异常处理













    展开全文
  • protobuf lua源码解析

    千次阅读 2016-08-23 23:03:26
    protobuf在互联网领域应用广泛,同时lua在游戏领域中...protoc-gen-lua库实现了protobuf到lua的移植,但是相关接口说明文档并不充分(只有一个非常简单的example),本着学习lua的心态,花了一些时间看看它的实现源码

    protobuf在互联网领域应用广泛,同时lua在游戏领域中作为一门热门的脚本语言也备受注目。protoc-gen-lua库实现了protobuf到lua的移植,但是相关接口说明文档并不充分(只有一个非常简单的example),本着学习lua的心态,花了一些时间看看它的实现源码。


    整体看下来,作者在实现上将function作为lua一类公民(first-class type)的身份发挥的淋漓尽致,处处闭包,需要花一些时间仔细学习。


    本文做一个简单的归纳。


    protobuf的编码原理推荐两篇文章:

    http://www.cnblogs.com/fullsail/p/4220293.html

    http://www.cnblogs.com/cobbliu/archive/2013/03/02/2940074.html

    本文也是参照protobuf的编码原理对照着看源码的,事半功倍


    • 编码

    编码的实现都在encoder.lua中,云风博客(http://blog.codingnow.com/2010/08/proto_buffers_in_lua.html)说的好:Google Proto Buffers的意义在于,定义了一个不错的PDL。protobuffers的实现反而不那么重要了。

    不过看看大牛的代码也是享受

    protobuf在编码一个字段时,分为key和value两端,key基本是通用的:

    Key = Varint (tag << 3 | wiretype),细节请参考前面引用的两篇文章,对应的代码如下:

    实现的一丝不苟,VarintBytes函数实现的就是Varint编码。

    举两个例子

    optional int32 aa = 2; (假设设置值为0x8888)


    源码:

    Int32Encoder = _SimpleEncoder(wire_format.WIRETYPE_VARINT, _EncodeSignedVarint, _SignedVarintSize)


    再来一个复杂的例子

    repeated SubMessage msgs = 25;


    实质上是先写tag,然后写submessage的长度,然后递归到submessage自己的编码函数,repeated字段的各个item都是一样处理。

    Varient编码细节以及ZigZagEncode细节也可以参考之前两篇文章。

    了解编码后解码部分也就不难了,这里省略

    • 补充几个使用例子

    项目源码中只有一个非常简单的例子,这里补充几个

    第一个是使用repeated submessage,首先是proto定义


    repeated message的添加接口是add,源码在container.lua中,一般类型的repeated字段的添加是append remove


    之后是运行结果:



    第二个例子是ListFields方法,类似于pairs,不过key是FieldDescriptor


    部分输出如下,会有很多实现上的细节输出:



    本文只是为了学习lua,在阅读一些库的过程中所记录的一些笔记,所以比较零碎,想要了解更多实现细节的同学可以参考源码,强烈推荐先了解一些protobuf的编码原理再看protoc-gen-lua库。


    展开全文
  • Lua源码解析之二:parser

    千次阅读 2017-08-07 22:40:47
    Lua中的parser目录Lua中的parser 目录 前言 extended BNF parse function 1 function name 2 function body 3 end and code closure1. 前言上一章介绍了Lua的词法分析,本章论述lua语法分析,但是纵观lua源代码,...

    Lua中的parser

    目录

    1. 前言

    上一章介绍了Lua的词法分析,本章论述lua语法分析,但是纵观lua源代码,发现语法分析和词法分析区分得并不明显,尽管看起来词法分析是放在llex.c,语法分析是放在lparser.c。

    Lua在load一段代码或者一个lua文件时(统称为buffer),首先open一个function,设置好function的参数和upval,然后将buffer里面的内容作为函数体内的代码去解析,以下代码引用自lparser.c中,解析一个文件buffer时调用的mainfunc:

    /*
    ** compiles the main function, which is a regular vararg function with an
    ** upvalue named LUA_ENV
    */
    static void mainfunc (LexState *ls, FuncState *fs) {
      BlockCnt bl;
      expdesc v;
      open_func(ls, fs, &bl);
      fs->f->is_vararg = 2;             /* main function 永远是变参 */
      init_exp(&v, VLOCAL, 0);          /* create and... */
      newupvalue(fs, ls->envn, &v);     /* ...set environment upvalue, 设置环境变量为upvalue,它指向偏移为0的stack */
      luaX_next(ls);                    /* read first token */
      statlist(ls);                     /* parse main body */
      check(ls, TK_EOS);
      close_func(ls);
    }
    1. 调用open_func,设定好parser过程中用到的FuncState和BlockCnt;
    2. 为当前的FuncState创建name为envn的upvalue,它偏移为0的栈
    3. 解析‘函数体内’的表达式
    4. 解析完buffer,close_func。解析到此为止

    lua代码由一条条语句(statement)组成,这里和c语言略不同:一个function的定义,可以看作语句;一个require语句,也可看作语句,总而言之,所有的lua代码都可以看作表达式。
    通过解析statment,采用自顶向下语法分析,会生成一棵语法树,但lua源代码中很难看到清晰的语法树结构,仔细阅读lparser.c就会发现,parser实质上一边生成临时的语法树,一边调用lcode.c生成code(生成code的过程不在本章中讲解)。

    2. extended BNF

    言归正传,本章重点讲解parser,据说最早的lua parser是用YACC生成的,后来改成手写的lparser了,这样的可读性会好很多,lua parser严格按照extend BNF表述的语法进行解析:

    chunk ::= block
    
        block ::= {stat} [retstat]
    
        stat ::=  ‘;’ | 
             varlist ‘=’ explist | 
             functioncall | 
             label | 
             break | 
             goto Name | 
             do block end | 
             while exp do block end | 
             repeat block until exp | 
             if exp then block {elseif exp then block} [else block] end | 
             for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end | 
             for namelist in explist do block end | 
             function funcname funcbody | 
             local function Name funcbody | 
             local namelist [‘=’ explist] 
    
        retstat ::= return [explist] [‘;’]
    
        label ::= ‘::’ Name ‘::’
    
        funcname ::= Name {‘.’ Name} [‘:’ Name]
    
        varlist ::= var {‘,’ var}
    
        var ::=  Name | prefixexp ‘[’ exp ‘]’ | prefixexp ‘.’ Name 
    
        namelist ::= Name {‘,’ Name}
    
        explist ::= exp {‘,’ exp}
    
        exp ::=  nil | false | true | Numeral | LiteralString | ‘...’ | functiondef | 
             prefixexp | tableconstructor | exp binop exp | unop exp 
    
        prefixexp ::= var | functioncall | ‘(’ exp ‘)’
    
        functioncall ::=  prefixexp args | prefixexp ‘:’ Name args 
    
        args ::=  ‘(’ [explist] ‘)’ | tableconstructor | LiteralString 
    
        functiondef ::= function funcbody
    
        funcbody ::= ‘(’ [parlist] ‘)’ block end
    
        parlist ::= namelist [‘,’ ‘...’] | ‘...’
    
        tableconstructor ::= ‘{’ [fieldlist] ‘}’
    
        fieldlist ::= field {fieldsep field} [fieldsep]
    
        field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp
    
        fieldsep ::= ‘,’ | ‘;’
    
        binop ::=  ‘+’ | ‘-’ | ‘*’ | ‘/’ | ‘//’ | ‘^’ | ‘%’ | 
             ‘&’ | ‘~’ | ‘|’ | ‘>>’ | ‘<<’ | ‘..’ | 
             ‘<’ | ‘<=’ | ‘>’ | ‘>=’ | ‘==’ | ‘~=’ | 
             and | or
    
        unop ::= ‘-’ | not | ‘#’ | ‘~’

    这里有必要介绍上述BNF中常用的几个推导符号:

    符号 含义
    ::= 推导
    {} 一个或者多个
    [] 出现0次或者1次
    | 或者

    以函数推导为例:

    functiondef ::= function funcbody
        funcbody ::= ‘(’ [parlist] ‘)’ block end
            parlist ::= namelist [‘,’ ‘...’] | ‘...’
            block ::= {stat} [retstat]

    一个函数的定义由function关键词开始,接着是参数列表,然后block语句 以end关键词结束
    function(a,b) block end 而,block本身也是由若干语句组成(又是递归)
    在下一小节中,将以解析一个函数为例,来分析lparser的源代码。

    3. parse function

    如何完整的解析一个函数呢,请看下面的代码:

    static void funcstat (LexState *ls, int line) {
      /* funcstat -> FUNCTION funcname body */
      int ismethod;
      expdesc v, b;
      luaX_next(ls);                    /* skip FUNCTION */
      ismethod = funcname(ls, &v);      /* 解析函数名,保存结果到v */
      body(ls, &b, ismethod, line);     /* 解析函数体,保存结果到b */
      luaK_storevar(ls->fs, &v, &b);    /* 编码生成赋值语句 */
      luaK_fixline(ls->fs, line);       /* definition "happens" in the first line */
    }

    expdesc是用于描述 表达式 的结构体,函数名可以用它来表达;这很好理解,而函数体也可以用它来表达,可以想象一下,函数体被编码好之后,放在某个角落,然后expdesc指向这个角落: ),具体分析代码的话,涉及到的细节比较多,不过这里分析的细节对于解析其它的BNF,也是有帮助的。

    3.1 function name

    函数名如何解析呢?
    先以合法的function name为例,一般有以下几种形式:

    function A()        end
    function A.a()      end
    function A.a.b()    end
    function A:a()      end

    查看,funcname函数源代码:

    /*
     * 解析函数名,保存结果到v
     * 1. function A 或者 function A.a
     * 2. function A:a
     */
    static int funcname (LexState *ls, expdesc *v) {
      /* funcname -> NAME {fieldsel} [':' NAME] */
      int ismethod = 0;
      /* 首先将它作为一个简单的变量名去解析 */
      singlevar(ls, v);
      while (ls->t.token == '.')
        fieldsel(ls, v);
      if (ls->t.token == ':') {
        ismethod = 1;
        fieldsel(ls, v);
      }
      return ismethod;
    }
    
    static void singlevar (LexState *ls, expdesc *var) {
      TString *varname = str_checkname(ls);
      FuncState *fs = ls->fs;
      if (singlevaraux(fs, varname, var, 1) == VVOID) {  /* global name? */
        expdesc key;
        singlevaraux(fs, ls->envn, var, 1);  /* get environment variable */
        lua_assert(var->k == VLOCAL || var->k == VUPVAL);
        codestring(ls, &key, varname);  /* key is variable name */
        luaK_indexed(fs, var, &key);  /* env[varname] */
      }
    }
    
    
    /*
      Find variable with given name 'n'. If it is an upvalue, add this
      upvalue into all intermediate functions.
    */
    static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
      if (fs == NULL)  /* no more levels? */
        return VVOID;  /* default is global */
      else {
        int v = searchvar(fs, n);  /* look up locals at current level */
        if (v >= 0) {  /* found? */
          init_exp(var, VLOCAL, v);  /* variable is local */
          if (!base)
            markupval(fs, v);  /* local will be used as an upval */
          return VLOCAL;
        }
        else {  /* not found as local at current level; try upvalues */
          int idx = searchupvalue(fs, n);  /* try existing upvalues */
          if (idx < 0) {  /* not found? */
            if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */
              return VVOID;  /* not found; is a global */
            /* else was LOCAL or UPVAL */
            idx  = newupvalue(fs, n, var);  /* will be a new upvalue */
          }
          init_exp(var, VUPVAL, idx);
          return VUPVAL;
        }
      }
    }
    

    首先,它将funcname作为单一变量名去解析,singlevar完成此功能,它先调用singlevaraux是否返回VVOID,如果返回VVOID,说明singlevaraux对var没有进行任何赋值,那么作为调用者的singlevar就需要对这个分支进行处理。

      if (singlevaraux(fs, varname, var, 1) == VVOID) {  /* global name? */
        expdesc key;
        singlevaraux(fs, ls->envn, var, 1);  /* get environment variable */
        lua_assert(var->k == VLOCAL || var->k == VUPVAL);
        codestring(ls, &key, varname);  /* key is variable name */
        luaK_indexed(fs, var, &key);  /* env[varname] */
      }

    首先,它获取ls->envn这个全局环境变量到var,然后调用luaK_indexed对var[key]即,env[varname]进行编码,逻辑上来说,luaK_indexed这个函数完成的功能即是将“取hashtable(env)的key(varname)”这一操作行为,保存在var中。
    PS:
    函数singlevaraux用于查找一个变量名为n的变量,将结果放在var中 ,这里用markdown编辑器里面提供的流程图来表达:

    Created with Raphaël 2.1.0开始fs!=NULL?local level没找到?当前level upvalue中没找到?在prev->level没找到?new upvalue,并返回UPVALUE结束返回GLOBAL返回UPVALUE返回LOCALyesnoyesnoyesnoyesno

    接着对后面的token进行分析,如果还有’.’,说明该函数名 是var这个table里面的一个key,查看

    static void fieldsel (LexState *ls, expdesc *v) {
      /* fieldsel -> ['.' | ':'] NAME */
      FuncState *fs = ls->fs;
      expdesc key;
      luaK_exp2anyregup(fs, v);
      luaX_next(ls);  /* skip the dot or colon */
      checkname(ls, &key);
      luaK_indexed(fs, v, &key);
    }

    发现,它首先将v放置在寄存器里面,然后再解析接下来的name(key),最后还是调用luaK_indexed来完成v[key]的操作。

    函数名解析完成后,funcname调用返回。接下来解析function body

    3.2 function body

    按惯例,先看代码:

    static void body (LexState *ls, expdesc *e, int ismethod, int line) {
      /* body ->  '(' parlist ')' block END */
      FuncState new_fs;
      BlockCnt bl;
      new_fs.f = addprototype(ls);
      new_fs.f->linedefined = line;
      open_func(ls, &new_fs, &bl);
      checknext(ls, '(');
      if (ismethod) {
        new_localvarliteral(ls, "self");  /* create 'self' parameter */
        adjustlocalvars(ls, 1);
      }
      parlist(ls);
      checknext(ls, ')');
      statlist(ls);
      new_fs.f->lastlinedefined = ls->linenumber;
      check_match(ls, TK_END, TK_FUNCTION, line);
      codeclosure(ls, e);
      close_func(ls);
    }

    addprototype是为了new一个proto,并将其加入ls->fs->p数组中,和mainfunc类似,会先open_func;
    注意到,判定ismethod为true,则会添加一个self的局部变量,添加局部变量是这样子的:

    /*
     * 新增一个局部变量,为该变量分配空间,并返回其在f->locvars数组中的下标
     * 注意: 数组f->locvars分配的空间大小为f->sizelocvars,但是数组长度却由fs->nlocavars来控制
     */
    static int registerlocalvar (LexState *ls, TString *varname) {
      FuncState *fs = ls->fs;
      Proto *f = fs->f;
      int oldsize = f->sizelocvars;
      luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars,
                      LocVar, SHRT_MAX, "local variables");
      while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL;
      f->locvars[fs->nlocvars].varname = varname;
      luaC_objbarrier(ls->L, f, varname);
      return fs->nlocvars++;
    }
    
    
    static void new_localvar (LexState *ls, TString *name) {
      FuncState *fs = ls->fs;
      Dyndata *dyd = ls->dyd;
      int reg = registerlocalvar(ls, name);
      checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,
                      MAXVARS, "local variables");
      luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1,
                      dyd->actvar.size, Vardesc, MAX_INT, "local variables");
      dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg);
    }
    
    
    static void new_localvarliteral_ (LexState *ls, const char *name, size_t sz) {
      new_localvar(ls, luaX_newstring(ls, name, sz));
    }
    
    #define new_localvarliteral(ls,v) \
        new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1)

    查看new_localvar函数,可以发现,dyd中保存的actvar信息只有下标idx。fs->f->locvars[idx]方可访问到此局部变量。
    adjustlocalvars函数仅仅是为了将设定一下新增的localvar的pc指针。

    接下来解析函数的形参列表parlist(ls):

    static void parlist (LexState *ls) {
      /* parlist -> [ param { ',' param } ] */
      FuncState *fs = ls->fs;
      Proto *f = fs->f;
      int nparams = 0;
      f->is_vararg = 0;
      if (ls->t.token != ')') {  /* is 'parlist' not empty? */
        do {
          switch (ls->t.token) {
            case TK_NAME: {  /* param -> NAME */
              new_localvar(ls, str_checkname(ls));
              nparams++;
              break;
            }
            case TK_DOTS: {  /* param -> '...' */
              luaX_next(ls);
              f->is_vararg = 2;  /* declared vararg */
              break;
            }
            default: luaX_syntaxerror(ls, "<name> or '...' expected");
          }
        } while (!f->is_vararg && testnext(ls, ','));
      }
      adjustlocalvars(ls, nparams);
      f->numparams = cast_byte(fs->nactvar);
      /* 需要将参数列表里的局部变量保留到寄存器中 */
      luaK_reserveregs(fs, fs->nactvar);  /* reserve register for parameters */
    }
    

    可以看到,如果ls->t.token为变量名的话,则新增一个局部变量,如果为’…’,则说明使用了变参,那么设定f->is_vararg=2后break–由于…只能是函数形参列表里面的最后一个参数,所以读到’…’之后退出是有必要的。
    最后一条语句,luaK_reserveregs是为了将局部变量保存于寄存器中,等等,parser的过程中怎么会出现寄存器,寄存器难道不是运行时才有的概念吗?
    嗯,确实这样,只有在lua虚拟机中运行时,register才有存在的意义;然luaK_reserveregs并不是使用寄存器,它仅仅只是累加 fs->freereg+=n,换句话讲,它仅仅只是访问和修改了寄存器的索引index,而lua虚拟机在运行时,会根据基地址+index来完成对寄存器的访问。

    接下来就是解析statement list:

    static void statlist (LexState *ls) {
      /* statlist -> { stat [';'] } */
      /* 只要当前token不代表下一个block,则继续解析statement */
      while (!block_follow(ls, 1)) {
        if (ls->t.token == TK_RETURN) {
          statement(ls);
          return;  /* 'return' must be last statement */
        }
        statement(ls);
      }
    }
    
    static void statement (LexState *ls) {
      int line = ls->linenumber;  /* may be needed for error messages */
      enterlevel(ls);
      switch (ls->t.token) {
        case ';': {  /* stat -> ';' (empty statement) */
          luaX_next(ls);  /* skip ';' */
          break;
        }
        case TK_IF: {  /* stat -> ifstat */
          ifstat(ls, line);
          break;
        }
        case TK_WHILE: {  /* stat -> whilestat */
          whilestat(ls, line);
          break;
        }
        case TK_DO: {  /* stat -> DO block END */
          luaX_next(ls);  /* skip DO */
          block(ls);
          check_match(ls, TK_END, TK_DO, line);
          break;
        }
        case TK_FOR: {  /* stat -> forstat */
          forstat(ls, line);
          break;
        }
        case TK_REPEAT: {  /* stat -> repeatstat */
          repeatstat(ls, line);
          break;
        }
        case TK_FUNCTION: {  /* stat -> funcstat */
          funcstat(ls, line);
          break;
        }
        case TK_LOCAL: {  /* stat -> localstat */
          luaX_next(ls);  /* skip LOCAL */
          if (testnext(ls, TK_FUNCTION))  /* local function? */
            localfunc(ls);
          else
            localstat(ls);
          break;
        }
        case TK_DBCOLON: {  /* stat -> label */
          luaX_next(ls);  /* skip double colon */
          labelstat(ls, str_checkname(ls), line);
          break;
        }
        case TK_RETURN: {  /* stat -> retstat */
          luaX_next(ls);  /* skip RETURN */
          retstat(ls);
          break;
        }
        case TK_BREAK:   /* stat -> breakstat */
        case TK_GOTO: {  /* stat -> 'goto' NAME */
          gotostat(ls, luaK_jump(ls->fs));
          break;
        }
        default: {  /* stat -> func | assignment */
          exprstat(ls);
          break;
        }
      }
      lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
                 ls->fs->freereg >= ls->fs->nactvar);
      ls->fs->freereg = ls->fs->nactvar;  /* free registers */
      leavelevel(ls);
    }
    

    statlist和BNF表达的一样,它需要进一步的调用statement来解析一条语句,我们来回顾一下statement的BNF:

        stat ::=  ‘;’ | 
             varlist ‘=’ explist | 
             functioncall | 
             label | 
             break | 
             goto Name | 
             do block end | 
             while exp do block end | 
             repeat block until exp | 
             if exp then block {elseif exp then block} [else block] end | 
             for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end | 
             for namelist in explist do block end | 
             function funcname funcbody | 
             local function Name funcbody | 
             local namelist [‘=’ explist] 

    而statement函数也和上述BNF一样,分为很多个case,’;’ 空语句,if语句;do-while语句,还有function语句。。。

    具体每一个case如何解析,还有如何进行编码,将在系列三之中具体描述。。。

    3.3 end and code closure

    最后的代码:

      check_match(ls, TK_END, TK_FUNCTION, line);
      codeclosure(ls, e);
      close_func(ls);

    check_match会校验函数结束标志符”end”,调用codeclosure,然后把OP_CLOSURE这条指令添加到父函数的code之中。

    展开全文
  • Lua源码解析之一:lexical

    千次阅读 2016-03-26 19:44:14
    我们知道,任何高级一点的编译器,在解析源代码时,都需要进行词法分析。而词法分析的过程就是先识别token的一个过程,总体来说,lua里面的token大致分为: 1. 数字和字符串 2. 特殊字符:包括运算符和括号 3. ...

    我们知道,任何高级一点的编译器,在解析源代码时,都需要进行词法分析。而词法分析的过程就是先识别token的一个过程,总体来说,lua里面的token大致分为:

    1. 数字和字符串

    2. 特殊字符:包括运算符和括号

    3. 关键词

    对于每一类token lua都有唯一的id与之对应,此id用int来表示,对于第2种类型,直接用该字符的ASCII码来表示,对于1,3两类,则定义一组枚举,为了与第2种区别开,枚举从257开始。比如关键字break 对应 TK_BREAK,do 对应 TK_DO。


    先来看看luaX_next,它用来识别下一个token,会调用 llex 函数,返回token type和seminfo。有了token,接下来就会分析一条条的语句。

    一个statementlist 的 production为: statlist -> { stat [`;'] } ,下面先将一个statement的grammer production列出:

    stat = { ifstat | dostat | whilestat | functionstat | localstat | retstat | forstat | repeatestat | goto | breakstat  | exprstat }


    以ifstat为例:

    ifstat -> IF exprstat THEN statlist END

    exprstat -> subexpr

    subexpr ->(simpleexp | unop subexpr) { binop subexpr }

    simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | constructor | FUNCTION body | suffixedexp */


    关键词以大写表示,在读完IF token之后,接着读exprstat,exprstat继续向下分解为subexpr,接着是simpleexp,直至基础数据NUMBER为止。这实际上就是所谓的自顶向下分析法。


    说到这里,似乎词法分析很简单,单论词法分析,lua确实很简单,但lua是解释型的脚本语言,实际在是一边做词法分析,一边再做语义分析,同时根据语义分析结果,生成lua指令。也就是说,在词法分析的同时,干了很多事情。


    具体来看看lua指令,一个lua指令用一个32位的整数来表示。其中6bit用于表示指令码,8bit表示操作数A,9bit表示B,9bit表示C(这种做法在逻辑上有点类似汇编),操作数一般来说分几种类型:

    1) R(x): 表示该操作数在寄存器中

    2) Kst(x):表示该操作数在常量表中

    3) RK(x):表示该操作数如果是常量的话,则表示2),否则表示1)

    举例:指令OP_MOVE,看代码注释为R(A) := R(B),这就表示将寄存器B的值赋值给寄存器A。

    到这里不禁要问,lua寄存器是个什么概念?我们知道,对汇编来说,具体的寄存器是使用具体寄存器名字来指定的,例如mov ax, bx ...,而lua则由下标来指向寄存器。实质上R(x) = [base+x] ,其中base表示当前函数调用的一个基地址。


    lua解析是以function为单位来进行的,加载一个文件,可以看作是在一个全局的大函数里面对文件进行解析。那么,最终生成的指令都会放在归属函数Proto的opcodes数组里面。执行一个函数的时候,VM直接取指令分析操作数,接着往下执行就可以了。


    在下一篇中我将重点表述语法分析,以及生成具体指令的这么一个过程。



    展开全文
  • Lua源码解析之三:code

    千次阅读 2017-08-15 12:01:10
    在“Lua源码解析之一”中,有介绍到,lua的指令(instructor),lua指令由一个32位的unsigned int组成,其中: 6位用于表示OPCODE,即操作指令 8位用于表示操作数A,9位用于表示操作数B,9位用于表示操作数C ...

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 339
精华内容 135
关键字:

lua源码解析