由于工作需要用了很多年的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吧
今天就先到这儿了,一个人的思路有限,难免有错漏,欢迎指正。。。
来源:CSDN
作者:u014750316
链接:https://blog.csdn.net/u014750316/article/details/103871510