一、概览
ldo.h/ldo.c描述Lua的堆栈和调用的结构。
提供对调用、协程、异常等复杂控制流的支持。
模块中对外公开的API主要分为以下几类:
(1) 错误恢复:
luaD_seterrorobj,luaD_throw,luaD_rawrunprotected,luaD_pcall
(2) 堆栈操纵:
luaD_reallocCI
luaD_reallocstack,luaD_growstack,luaD_checkstack,
incr_top,savestack,restorestack
(3) 函数调用:
luaD_callhook,luaD_precall,luaD_poscall,luaD_call
(4) 协程控制:
lua_resume,lua_yield
(5) 代码加载:
luaD_protectedparser
因为ldo模块的luaD_call与lvm模块的luaV_execute是相互引用的,所以ldo和lvm可以看成是相互耦合的同一模块,共同构成lua的VM。
参考资料:
1. 官方代码参考src/ldo.c
http://www.lua.org/source/5.1/ldo.h.html
http://www.lua.org/source/5.1/ldo.c.html
2. Luaソースコード勉強会 (3)
http://d.hatena.ne.jp/hzkr/20080428
3. luaのお勉強[6]
http://d.hatena.ne.jp/jmk/20060428/p2
4. yield不能在c里调用
http://dheartf.blog.163.com/blog/static/38505465200762611034354/
5. Lua Source, Module Structure
http://lua-users.org/wiki/LuaSource
6. LUA源码分析八:小总结,完整分析dofile的过程和堆栈
http://lin-style.iteye.com/blog/1020290
7. Lua源码分析
http://wenku.baidu.com/view/d09e2f91dd88d0d233d46a7f.html
8. Lua 5.1.4: ldo.c
http://stevedonovan.github.com/lua-5.1.4/ldo.c.html
模块内部函数的相互调用,与模块外部对模块内函数的调用图如下:(红色圆圈为ldo模块内的函数)
注意:图中还有一些重要的关系没有表示出来:
(1) luaD_call->luaV_execute(在lvm.c中)->luaD_call
这个间接关系是由于ldo与lvm相互耦合导致的。
(2) lua_cpcall或lua_pcall(在lapi.c中)->luaD_pcall->luaD_rawrunprotected->f_Ccall或f_call(在lapi.c中)->luaD_call
这个间接关系是由于f_Ccall或f_call是作为luaD_pcall的一个函数指针参数传入而导致的。
二、头文件
#include "lobject.h"
#include "lstate.h"
#include "lzio.h"
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include "lua.h"
#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
#include "lmem.h"
#include "lobject.h"
#include "lopcodes.h"
#include "lparser.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "ltm.h"
#include "lundump.h"
#include "lvm.h"
#include "lzio.h"
三、公共或私有的宏定义
1. #define luaD_checkstack(L,n) \
if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \
luaD_growstack(L, n); \
else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1));
如果堆栈容量不足加n,则确保堆栈容量足够加n(必要时增长容量)。否则保持(检查?)堆栈容量为L->stacksize - EXTRA_STACK - 1大小。
condhardstacktests是定义在llimit.h中,是一个默认不执行任何操作的宏(因为HARDSTACKTESTS未定义)。
#ifndef HARDSTACKTESTS
#define condhardstacktests(x) ((void)0)
#else
#define condhardstacktests(x) x
#endif
2. #define incr_top(L) {luaD_checkstack(L,1); L->top++;}
先确保堆栈容量足够加1,然后让L->top加1。
3. #define savestack(L,p) ((char *)(p) - (char *)L->stack)
保存p相对于栈底L->stack的字节数偏移(p指针为StkId类型,即TValue *型),
返回值保存为函数的局部变量,
再执行一些操作(修改了L->stack),
最后把局部变量传给restorestack作为参数以还原p指针。
4. #define restorestack(L,n) ((TValue *)((char *)L->stack + (n)))
通过返回值还原堆栈指针(见savestack)
5. #define saveci(L,p) ((char *)(p) - (char *)L->base_ci)
保存p(CallInfo *型)相对于L->base_ci的偏移到局部变量(类似savestack)
在luaD_pcall中用于维持旧的L->ci指针值不被破坏。
6. #define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n)))
通过返回值还原CI指针(类似restorestack)
7. #define PCRLUA 0 /* initiated a call to a Lua function */
luaD_precall返回值,表示初始化一个对Lua函数的调用
8. #define PCRC 1 /* did a call to a C function */
luaD_precall返回值,执行了对C函数的调用
9. #define PCRYIELD 2 /* C funtion yielded */
luaD_precall返回值,C函数已经挂起
10. #define ldo_c
象征性质,表示ldo.c文件被编译
11. #define LUA_CORE
象征性质,表示此模块为内核
12. #define inc_ci(L) \
((L->ci == L->end_ci) ? growCI(L) : \
(condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))
如果L->ci == L->end_ci,确保CI数组容量足够加1,否则保持(检查?)CI数组容量为L->size_ci(实际上为无操作,因为HARDSTACKTESTS未定义,同luaD_checkstack中的condhardstacktests)。
最后让CI数组大小加1(作用类似于incr_top,不过是针对CI数组的)。
四、类型
1. typedef void (*Pfunc) (lua_State *L, void *ud);
luaD_pcall与luaD_rawrunprotected的函数指针参数类型,用于这种场合:
LUAI_TRY(L, &lj,
(*f)(L, ud);
);
定义这个类型的目的是,可以集中执行:
struct lua_longjmp lj;
lj.status = 0;
lj.previous = L->errorJmp;
L->errorJmp = &lj;
...
L->errorJmp = lj.previous;
return lj.status;
而结构体lua_longjmp只需要定义为ldo.c的私有结构体即可,ldo.c模块外的函数不需要知道它。
满足Pfunc类型的有如下函数:
(1) 作为luaD_pcall的参数:lapi.c的f_call,f_Ccall,f_parser
(2) 作为luaD_rawrunprotected的参数:ldo.c的resume,lapi.c的f_call,f_Ccall,f_parser(luaD_pcall内部也调用luaD_rawrunprotected),lstate.c的f_luaopen,callallgcTM。
2. struct lua_longjmp {
struct lua_longjmp *previous;
luai_jmpbuf b;
volatile int status; /* 错误码 */
};
一个类似链表头的结构体,作为luaD_rawrunprotected的局部变量,把执行f前的L->errorJmp暂时保存以下,在退出luaD_rawrunprotected后还原回L->errorJmp。
根据luaD_rawrunprotected的注释,它的作用是链接和还原新旧的错误处理链,把局部变量lj插入到L->errorJmp链表的第一个位置(或可理解L->errorJmp指向堆栈的栈顶)。
lstate.h:
struct lua_State {
...
struct lua_longjmp *errorJmp; /* 当前错误恢复点 */
...
};
另外,因为调用了LUAI_TRY
ldo.c
struct lua_longjmp lj;
...
LUAI_TRY(L, &lj,
(*f)(L, ud);
);
...
return lj.status;
lua_longjmp的b域专门用于C的异常跳转机制,而status域用于抛出异常时记录错误码(不一定在luaD_rawrunprotected中记录,也可能在luaD_throw中记录)以作为luaD_rawrunprotected的返回值(它们都可能需要在LUAI_TRY中使用):
(1) status域用于确保LUAI_TRY的try被throw触发时(C++风格异常),总是被赋值为非0值(至少为-1),因为C++异常不像C跳转那样,可能不是显式抛出的:
luaconf.h
#if defined(__cplusplus)
...
#define LUAI_TRY(L,c,a) try { a } catch(...) \
{ if ((c)->status == 0) (c)->status = -1; }
这并不意味着luaD_rawrunprotected返回status值总是0和-1,因为其它函数也可能通过L->errorJmp修改到status域。
(2) b域用于判断LUAI_TRY的_setjmp和setjmp是否被longjmp触发(C风格异常)(即(c)->b)
luaconf.h
#elif defined(LUA_USE_ULONGJMP)
...
#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a }
...
#else
...
#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }
3. struct SParser { /* f_parser的数据 */
ZIO *z;
Mbuffer buff; /* 被扫描器使用的缓冲 */
const char *name;
};
作为luaD_protectedparser的局部变量struct SParser p;
或作为f_parser的局部变量struct SParser *p = cast(struct SParser *, ud);
它的作用是把luaD_protectedparser传入的参数ZIO *z和const char *name保存起来以及创建Mbuffer buff。
最后,它的指针作为luaD_pcall的void *u传入,最后传递给f_parser的void *ud参数。
过程如下:
lua_load->luaD_protectedparser->luaD_pcall->luaD_rawrunprotected->f_parser
这个结构体是ldo私有的。
五、私有的静态函数
1. static void restore_stack_limit (lua_State *L) {
luaD_pcall和luaD_throw中使用。
(1) 检查L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1。
(2) 如果出现CI数组分配溢出情况(超过LUAI_MAXCALLS),尝试撤销它。
2. static void resetstack (lua_State *L, int status) {
在luaD_throw中使用,执行G(L)->panic(L);之前的L栈操作。
3. static void correctstack (lua_State *L, TValue *oldstack) {
luaD_reallocstack中使用。用于修正与堆栈有关的指针值。
根据oldstack的值(栈偏移)调整L->top,L->openupval数组(每个upvalue的v指针),L->base_ci数组(每个CI的top,base,func指针)和L->base
4. static CallInfo *growCI (lua_State *L) {
CI(调用信息)数组加一,CI容量以L->size_ci的2倍增长。如果超过LUAI_MAXCALLS(宏定义为20000),则抛出LUA_ERRERR异常。
5. static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {
在luaD_precall中调用,用于变长参数函数的特殊处理:
(1) LUA_COMPAT_VARARG宏块,创建arg表(htab)
(2) 移动参数位置
6. static StkId tryfuncTM (lua_State *L, StkId func) {
在luaD_precall中执行,如果堆栈中func位置上的对象不是function型,
那么尝试取出其元表的__call元方法(必须为function型),插入到堆栈的func位置中。
7. static StkId callrethooks (lua_State *L, StkId firstResult) {
luaD_poscall中调用,执行调用返回钩子LUA_HOOKRET
8. static void resume (lua_State *L, void *ud) {
重新恢复挂起调用的执行。
没有考虑异常处理,异常处理由luaD_rawrunprotected完成(见lua_resume)。
9. static int resume_error (lua_State *L, const char *msg) {
执行resume失败,见lua_resume
有两种可能:
cannot resume non-suspended coroutine
无法恢复非挂起的协程
C stack overflow
C堆栈溢出
10. static void f_parser (lua_State *L, void *ud) {
执行luaU_undump或luaY_parser加载Lua代码
这是一个函数指针(带异常处理的回调),用于luaD_pcall(见luaD_protectedparser)
六、公开的导出函数
1. LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
根据errcode的值设置错误对象(可能是MEMERRMSG即"not enough memory","error in error handling",或L->top - 1位置上的错误消息字符串),然后移动到oldtop位置上(?)。
参数errcode是异常的错误码,用于LUA_ERRMEM和LUA_ERRERR的特殊情况处理。
执行完luaD_seterrorobj后L栈还原的栈顶为oldtop+1(L->top = oldtop + 1;),即L栈顶为错误对象。
2. LUAI_FUNC void luaD_throw (lua_State *L, int errcode);
void luaD_throw (lua_State *L, int errcode) {
抛出异常,异常将被调用栈上最近的luaD_rawrunprotected捕获。
如果没有异常处理上下文(顶级调用?),执行resetstack和G(L)->panic(L)钩子后退出程序(exit(EXIT_FAILURE))
3. LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
捕获异常,如果执行f时出现异常(可能是luaD_throw主动抛出的,也可能是隐式的),那么立刻跳转回此函数,恢复前一个异常处理上下文,并且返回异常错误码。
4. LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize);
void luaD_reallocstack (lua_State *L, int newsize) {
扩展L->stack和L->stacksize至新的大小newsize,
然后执行correctstack
5. LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize);
void luaD_reallocCI (lua_State *L, int newsize) {
扩展L->base_ci和L->size_ci至新的大小newsize,
然后调整L->ci和L->end_ci
6. LUAI_FUNC void luaD_growstack (lua_State *L, int n);
void luaD_growstack (lua_State *L, int n) {
luaD_reallocstack的封装,不过参数n是个增量最小值。而且如果小于L->stacksize,就增加1倍的L->stacksize,否则堆栈容量才加n。
7. LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line);
void luaD_callhook (lua_State *L, int event, int line) {
调用钩子,第二参数event传入的可能值有:
lua.h:
#define LUA_HOOKCALL 0 //Lua函数开始调用时触发,见ldo.c的luaD_precall
#define LUA_HOOKRET 1 //Lua函数(包括尾调用)返回时触发,见ldo.c的callrethooks
#define LUA_HOOKLINE 2 //进入新的Lua代码行时触发,见lvm.c的traceexec
#define LUA_HOOKCOUNT 3 //debug.sethook的count参数不等于0时触发,见lvm.c的traceexec
#define LUA_HOOKTAILRET 4 //Lua函数尾调用返回时触发,见ldo.c的callrethooks
用于Debug API的debug.sethook(见《Lua参考手册》中debug.sethook的注释)
8. LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults);
int luaD_precall (lua_State *L, StkId func, int nresults) {
luaD_call、resume和luaV_execute的OP_CALL和OP_TAILCALL分支中执行,执行堆栈调整和钩子操作。
9. LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult);
int luaD_poscall (lua_State *L, StkId firstResult) {
被luaD_precall和resume和luaV_execute的OP_RETURN分支调用。
执行调用返回后的堆栈恢复和钩子(callrethooks)操作。
10. LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults);
void luaD_call (lua_State *L, StkId func, int nResults) {
原文注释:
** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function.
** When returns, all the results are on the stack, starting at the original
** function position.
翻译:
调用一个函数(C或Lua)。func上的函数被调用。
参数在堆栈上,正好在函数后面(上方)。
当返回时,所有结果(返回值)在堆栈上,从原来的函数位置开始。
注意,luaD_call没有考虑异常处理,异常处理工作由luaD_rawrunprotected完成(通过f_Ccall或f_call)
11. LUA_API int lua_resume (lua_State *L, int nargs) {
重新恢复之前挂起的调用。考虑异常处理。
12. LUA_API int lua_yield (lua_State *L, int nresults) {
挂起(暂停)Lua调用。
保护堆栈内容和标记状态为LUA_YIELD:
L->base = L->top - nresults; /* protect stack slots below */
L->status = LUA_YIELD;
13. LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef);
int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) {
执行luaD_rawrunprotected,如果出错的话恢复执行luaD_rawrunprotected前的L栈信息。
它被lua_pcall和lua_cpcall调用。
14. LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name);
int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) {
在lua_load中调用,用于加载Lua代码(相当于编译器和加载器)
七、值得关注的函数
1. luaD_call/luaD_seterrorobj/luaD_throw/luaD_rawrunprotected/
f_Ccall/f_call/f_parser
lua_cpcall/lua_pcall/lua_load/luaD_pcall
观察错误保护和异常处理过程,包括:
(1) 涉及lapi.c的f_Ccall/f_call回调函数
(2) Lua/C调用执行次序:(在luaD_call中下断点)
lua_cpcall或lua_pcall->luaD_pcall->luaD_rawrunprotected->f_Ccall或f_call->luaD_call
(3) Lua代码加载执行次序:(在f_parser中下断点)
lua_load->luaD_protectedparser->luaD_pcall->luaD_rawrunprotected->f_parser
(4) 观察异常捕捉的实现方式和数据结构(在luaD_rawrunprotected中下断点)。
2. luaD_callhook/luaD_precall/luaD_poscall/luaD_call
lua_resume/lua_yield/resume
观察调用过程,包括:
(1) 堆栈(stack和CI)内存容量和大小操纵。
(2) lua_State结构体中与调用相关的数据结构(stack和CI)及改变。
(3) debug.sethook钩子函数
(4) __call元方法
(5) 变长参数处理
(6) 协程(yield/resume)的实现
(7) 尾调用