lua源码阅读笔记与思路整理一

感情迁移 提交于 2020-01-11 03:41:41

由于工作需要用了很多年的lua编写各种逻辑处理模块,今天心血来潮,决定深入了解一下lua源码的实现,目的是方便自己的lua代码调试,省的每次都用print打印这种原始手段,本“长篇小说“ 主要弄懂以下的问题:

1 local , global, upvalue 的原理和实现,upvalue的查找和递归标记是什么?

2 Function是什么,执行过程如何,参数如何传递,怎么分配栈上空间?

3 循环结构的实现

4 模块的实现,closure是什么

5 各种元表的实现,基本类型的元表如何增加(lua内部职能给table加元表,number,string这些都需要外部增加)

6 各种库方法的实现,如debug, math, string, os库等,浏览一遍有个概念即可

7 判断和跳转的实现

8 钩子函数的实现和应用

9 报错是如何组织的,各项信息在哪里保存的,又如何获取?

10 lua常量是什么,为什么要共享同一份常量?

11 gc回收是什么,如何实现的,流程是什么?

12 table类型的存储和访问,分数组类型和哈希类型

 

万里执行始于足下:先从最简单的local变量开始:

一、主要流程先表述一下:解析lua语言,生成c代码  >>>>>  调用c代码

前面的各种函数调用封装,检测错误封装暂且不表,直接进入最终解析语法的开始函数这: f_parser(lua_State*L ,void* ud)

ud: 源代码已经读入到ud里面了(简单的fread, 读成字符串形式)

f_parser做了总的来说两件大事:

        a: 调用luaY_parser 解析并生成一个Proto* tf (c语言形式的目标字节码),

        b:创建一个新的closure(暂且称为闭包cl), 把tf  的 数据 装入闭包cl中 ,最后把cl压栈, 算是完成了解析;    

解析完毕后,源代码统统已经转化为c语言的代码了(即得到上面的cl), 接下来就调用这些生成的c语言代码,处理各种源代码想要做的事,赋值计算等等。。。

 

二、 深入追踪一下luaY_parser  解析lua代码并生成c代码的过程;

变量的生成并不会生成字节码,直接就分配内存转成目标类型而已, 对变量的赋值才会生成字节码。

以local为例: local a=3

f_parser 首先读入源代码,新建一个FunctionState fs临时变量,用于解析,然后调用一个open_func函数,该函数新建一个proto*f,用于存放所有解析到的信息 然后将fs->h压栈,fs->f压栈, 接着解析源代码 luaX_next(), 解析到第一个token;

根据第一个token, chunk()开始进入模块的解析, 这里是进入statement();它会循环调用statement,知道整个模块解析完毕

statement:

--->luaX_nest();   // 解析下一个token,这里返回TK_LOCAL, 根据TK_LOCAL,会继续解析下一个token,返回TK_NAME,即变 

     量名a,  注意:期间读到的“local”,"a"等单独的元素,都会在fs->h中嘻哈一个Node,整个node对于变量没什么用,直接赋值为

     bool类型1,以保证整个node不会被回收,对于常量(数字,字符串等),则保存常量在fs->f->k[]数组中的下标index,整个

      fs->f->k[]数组保存该模块内部出现过的所有常量(为什么要保存,用于共享常量?留待后续研究:初步猜测是因为lua字

       节码是伪寄存器指令,空间有限,无法直接使用常量地址或者常量,所以转换成数组下标,这样就可以装进指令里了)。

---》localstate(ls);  //接着进入局部变量解析函数,如果是funciton则进入localfunc, 并读入下一个token, 这里为 “ = ”

       ---》new_localvar() ;  // 解析到“=”表示变量定义完毕,准备赋值了,该函数对于每个变量名 , 通过registerlocalvar(name) 

                                      将name保存在f->localvars[]中,然后将位置index保存在fs->actver[fs->nactvar] 中,表示第fs->nactvar

                                     个变量的在f->localvars[]数组中的位置

      ----》explist(ls,e); //解析后面的表达式,这里读入3, 然后进入simpleexp,进入TK_NUMBER的处理,得到一个

                                    expdesc* e,将e的寄存器暂定为0

     ---》adjust_assign() ;   //调整freereg计数,表示freereg之前的寄存器被局部变量占用,并对应变量生成字节码

                   ---》  luak_exp2nextreg();   //生成字节码,确认字节码的reg位置

                             --》exp2reg(..., freereg-1)  ;  //对于local a = 3来说, 直接在freereg-1处塞入常量3就表示赋值完毕

                                                                         //得到字节码  OP_LOCAK , reg, 3

----》close_func();  //至此表示模块解析完毕,调整各项函数指标(局部变量个数,执行开始地址的),并生成OP_RETURN

----》return f;  //返回得到的函数(指令序列和各种变量数据等)

接下来就进入调用环节了:

  ---》luaF_newLclosure(f);  //将f包装成 closure并压栈

---》lua_insert(L,base) ;  //作为 函数 f 的栈起始位置

--->lua_pcall() //调用函数

                  。。。。。----》luaV_execute();  //最终处理lua函数的地方,留待后续研究

 


至此,local变量的流程已经走完,总结下

fs->actvar[]  :  保存的是local变量名所在数组的下表(local变量的值一直在对应的栈中,直到函数调用结束)

fs->f :  解析得到的字节码指令序列载体

fs->f->localvars[],   真正保存local变量名所在的数组

fs->h[],  变量的哈希表,保存变量对应的(key,value)数值,暂时不知道干什么用的,可能用于debug吧

今天就先到这儿了,一个人的思路有限,难免有错漏,欢迎指正。。。

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!