Lua function call mechanism
Lua version 5.3.5.
Lua's execution flow has been introduced in this post.
In Lua, functions are represented as two types CClosure
and LClosure
, for
functions defined in C and Lua respectively.
#define ClosureHeader \
CommonHeader; lu_byte nupvalues; GCObject *gclist
typedef int (*lua_CFunction) (lua_State *L);
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
typedef union Closure {
CClosure c;
LClosure l;
} Closure;
Functions are called closures because they have upvalues
associated with them.
The CClosure
struct contains an array of TValue
values as upvalues. The
LClosure
struct contains an array of UpVal
pointers:
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;
};
typedef struct UpVal UpVal;
An UpVal
is open if it is in the scope of the execution flow, in which case the
UpVal.v
field points to the stack slot holding the current value of the
upvalue.
An UpVal
is closed if it is out of the scope of the execution flow, in which
case the UpVal.u.value
field holds the final value of the upvalue and the
UpVal.v
field points to the UpVal.u.value
field.
The LClosure.p
field is a pointer to a Proto
struct:
typedef struct Proto {
CommonHeader;
TValue *k; /* constants used by the function */
Instruction *code;
struct Proto **p; /* functions defined inside the function */
int *lineinfo; /* map from opcodes to source lines (debug information) */
LocVar *locvars; /* information about local variables (debug information) */
Upvaldesc *upvalues; /* upvalue information */
union Closure *cache; /* last created closure with this prototype */
TString *source; /* used for debug information */
int sizeupvalues; /* size of 'upvalues' */
int sizek; /* size of `k' */
int sizecode;
int sizelineinfo;
int sizep; /* size of `p' */
int sizelocvars;
int linedefined;
int lastlinedefined;
GCObject *gclist;
lu_byte numparams; /* number of fixed parameters */
lu_byte is_vararg;
lu_byte maxstacksize; /* maximum stack used by this function */
} Proto;
The Proto.code
field is an array of VM instructions.
The Proto.p
field is an array of pointers to Proto
structs of the closure's
sub-closures. Inside the luaY_parser
function, the main closure's LClosure
struct is created. During parsing source code, pointers to Proto
structs of
sub-closures are added to the main closure's Proto.p
array.
After parsing of source code is done, the main closure's LClosure
struct is
on stack. The handle_script
function calls docall
, which calls lua_pcall
,
which calls lua_pcallk
, which calls luaD_pcall
, which calls
luaD_rawrunprotected
, which calls f_call
, which calls luaD_callnoyield
,
which calls luaD_call
.
luaD_call
calls luaD_precall
to prepare the call. If the target closure is
a CClosure
, it gets called immediately. If the target closure is a
LClosure
, a CallInfo
struct is created to describe the closure call:
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;
The CallInfo.l.base
is the offset base for stack indexes used in the target
closure.
The CallInfo.func
field is index (offset from CallInfo.l.base
) of the stack
slot holding the target closure.
The CallInfo.l.savedpc
points to the next VM instruction of the target
closure.
luaD_precall
sets L-ci
to point to the CallInfo
.
luaD_call
then calls luaV_execute
. luaV_execute
gets the current
CallInfo
from L-ci
, and start running the target closure's VM instructions.
When source code defines a function, the corresponding VM instruction is
OP_CLOSURE
. The handling code in luaV_execute
is:
vmcase(OP_CLOSURE) {
Proto *p = cl->p->p[GETARG_Bx(i)];
LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */
if (ncl == NULL) /* no match? */
pushclosure(L, p, cl->upvals, base, ra); /* create a new one */
else
setclLvalue(L, ra, ncl); /* push cashed closure */
checkGC(L, ra + 1);
vmbreak;
}
The sub-closure's Proto
struct pointer is obtained from the current closure's
Proto.p
array. The pushclosure
function creates a new LClosure
for the
sub-closure and set its LClosure.p
field to point to the obtained Proto
struct.
The pushclosure
function then initializes the sub-closure's LClosure.upvals
field (an UpVal
pointer array), for upvalues used in the sub-closure.
Depending on whether an upvalue is defined in a parent closure or an ancestor
closure, the UpVal
struct pointer is obtained from the parent closure's
UpVal
pointer array, or by calling luaF_findupval
, respectively,
When source code calls a function, the corresponding VM instruction is
OP_CALL
. The handling code in luaV_execute
is:
vmcase(OP_CALL) {
int b = GETARG_B(i);
int nresults = GETARG_C(i) - 1;
if (b != 0) L->top = ra+b; /* else previous instruction set top */
if (luaD_precall(L, ra, nresults)) { /* C function? */
if (nresults >= 0)
L->top = ci->top; /* adjust results */
Protect((void)0); /* update 'base' */
}
else { /* Lua function */
ci = L->ci;
goto newframe; /* restart luaV_execute over new Lua function */
}
vmbreak;
}
The stack index of the closure to call is encoded in the OP_CALL
instruction's RA
field, which is put in luaV_execute
's ra
variable.
luaD_precall
is called to create a new CallInfo
and sets L-ci
to point to
it. luaV_execute
's ci
variable is set to point to the new CallInfo
. Then
the goto newframe
statement causes the VM to start running the target
closure's instructions.
When source code returns from a function, the corresponding VM instruction is
OP_RETURN
. The handling code in luaV_execute
is:
vmcase(OP_RETURN) {
int b = GETARG_B(i);
if (cl->p->sizep > 0) luaF_close(L, base);
b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra)));
if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */
return; /* external invocation: return */
else { /* invocation via reentry: continue execution */
ci = L->ci;
if (b) L->top = ci->top;
lua_assert(isLua(ci));
lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL);
goto newframe; /* restart luaV_execute over new Lua function */
}
}
The luaD_poscall
function is called. L.ci
is set to point to the parent
closure's CallInfo
. luaV_execute
's ci
variable is set to point to the
parent closure's CallInfo
. Then the goto newframe
statement causes the VM
to start running the parent closure's next instruction pointed to by
CallInfo.l.savedpc
.
Comments: