Research on LUA function execution flow and function continuation point problem

Source: Internet
Author: User


First: The overall flow of LUA function calls

In the case of a protective call, the process of function calls in Lua is as follows, and the process of unprotected calls is simpler, please trace the Lua_ callfunction


int Docall (lua_state *l, int narg, int nres) |--int lua_pcallk (lua_state *l, int nargs, int nresults, int errfunc ...) | --luad_pcall (lua_state *l, Pfunc func, void *u,ptrdiff_t old_top, ptrdiff_t ef) | --luad_rawrunprotected (lua_state *l, Pfunc F, void *ud) | --void F_call (Lua_state *l, void *ud) | --void Luad_call (Lua_state *l, stkid func, int nresults, int allowyield) | --int Luad_precall (lua_state *l, stkid func, int nresults) | --Luad_poscall
|
---...

Second: The way the function is called and the exception handling

You can see that the luad_rawrunprotected function call is actually f_call, and the function that is actually called is called in F_call, and the meaning of encapsulating this layer is to implement a protective call. In the case of a protective call, the LUA virtual machine uses LUA_LONGJMP to implement the stack continuation function of the function, that is, when the error occurs, it is possible to eventually jump to the call point within LUA to continue execution down. All calls that use the luad_rawrunprotected function do not cause the program to exit directly because of an error, but instead go back to the call point and return the state to the outer logic processing.

Protective call int luad_rawrunprotected (lua_state *l, Pfunc F, void *ud) {  unsigned short oldnccalls = l->nccalls;  struct LUA_LONGJMP LJ;  Lj.status = LUA_OK;  lj.previous = l->errorjmp;  /* Chain New Error handler */  l->errorjmp = &lj;  Luai_try (L, &lj,    (*f) (L, UD); When the F function calls out the exception will go back here and continue down  );  l->errorjmp = lj.previous;  /* Restore old Error handler */  l->nccalls = oldnccalls;  return lj.status;}

For LUA, only Lua_yield is considered a recoverable exception #define ERRORSTATUS (s) ((s) > Lua_yield), and for other errors it will be an error.

    In fact, for calling a function, whether it is a LUA function or C function, you can use Lua_pacall (Lua_call): This method of invocation we can see, in the call to  luad_call this process is, Allowyield is 0, which means it is not allowed to hang, so if you use the yield-related function in the function to attempt to suspend the program, then the LUA_YIELDK will error: Attempt to yield from outside a Coroutine. So I'm not able to understand that if you need to yield in a function, you can't initiate a function call in the form of Lua_pcall and Lua_call. There is, of course, a form of initiating a function call using Lua_resume: We know that the function of resume is to wake up a suspended thread (coroutine), and when it is first called, he simply executes the function body, The ability to resume a thread is allowed only if the resume is called again after a record of yield hangs, which allows the function to have the threads (yield hang), as described below.

Lua_api int Lua_yieldk (lua_state *l, int nresults, Lua_kcontext ctx, Lua_kfunction k) {Callinfo  *ci = l->ci;  Luai_userstateyield (L, nresults);  Lua_lock (L);  Api_checknelems (L, nresults); if (L->nny > 0) {if (l! = G (l)->mainthread) Luag_runerror (L, "attempt to yield across a c-call boundary"    );  else Luag_runerror (L, "attempt to yield from outside a coroutine");  Here is an error!!  } l->status = Lua_yield;  Ci->extra = Savestack (L, Ci->func); /* Save current ' func ' */if (Islua (CI)) {/* inside a hook? */Api_check (L, k = = NULL, "Hooks cannot continue after  Yielding ");  } else {if ((CI->U.C.K = k) = NULL)/* Is there a continuation? */ci->u.c.ctx = CTX;  /* Save Context */Ci->func = l->top-nresults-1;  /* Protect Stack below results */Luad_throw (L, Lua_yield);  } lua_assert (Ci->callstatus & cist_hooked);  /* Must be inside a hook */Lua_unlock (L);  return 0; /* Return to ' Luad_hOok ' * *} 

Summarize the above: if you call a function body or function block that does not suspend a thread, use Lua_pcall (Lua_call) and lua_resume to perform functions normally, and if the function body contains a process that suspends threads, you must use Lua_ Resume initiates a function call.

Third: The core function of function call

Lua_precall is the first part of a function call, and lua_postcall the second half of the function call as the name implies. If the call is a C function, then adjusting the adjustment in Lua_precall is called directly, and then call the Lua_postcall function call even if it ends, but if it is a LUA function, it needs to be given to the LUA virtual machine to execute the instruction set call, so Lua_ Precall just adjusts the stack and adjusts the return value after the LVM executes Lua_postcall.

 int Luad_precall (lua_state *l, stkid func, int nresults) {lua_cfunction F;   Callinfo *ci;  int n;   /* Number of arguments (LUA) or returns (C) */ptrdiff_t FUNCR = Savestack (L, func);       Switch (Ttype (func)) {case LUA_TLCF:/* Light C function */F = Fvalue (func);     Goto Cfunc;      Case LUA_TCCL: {/* C closure */F = clcvalue (func)->f;  CFUNC:LUAC_CHECKGC (L);  /* Stack grow uses memory */Luad_checkstack (L, lua_minstack);  /* Ensure minimum stack size */ci = next_ci (L);       /* now ' enter ' new function */////Create call chain to enter the call information into ci->nresults = Nresults;       Ci->func = Restorestack (L, FUNCR);       Ci->top = L->top + lua_minstack;       Lua_assert (Ci->top <= l->stack_last);       Ci->callstatus = 0;       if (L->hookmask & Lua_maskcall) Luad_hook (L, Lua_hookcall,-1);       Lua_unlock (L);  n = (*f) (L);       /* The actual call * */////If it is a C closure function or C function, invoke Lua_lock (L) directly; Api_checknElems (L, N);           Luad_poscall (L, L->top-n, N);     adjust stack return 1;       } case LUA_TLCL: {/* LUA function:prepare its-call */Stkid base;       Proto *p = Cllvalue (func)->p;  n = cast_int (l->top-func)-1;  /* Number of real arguments */LUAC_CHECKGC (L);       /* Stack grow uses memory */Luad_checkstack (L, p->maxstacksize);  for (n < p->numparams; n++)//If the function defines a number of arguments greater than the actual number of arguments, then the nil value is used to make up (you can see that the closer the parameter is closer to the top of the stack) Setnilvalue (l->top++); /* Complete missing arguments */if (!P-&GT;IS_VARARG) {//non-default parameters are not defined in the function function definition without ... func = Restorestack (L, Fu         NCR);       Base = func + 1;         } else {//function with default parameters, function definition with ... base = Adjust_varargs (L, p, N);  Func = Restorestack (L, FUNCR);  /* Previous call can change stack */} CI = Next_ci (L);       /* now ' enter ' new function */ci->nresults = nresults;       Ci->func = func;       Ci->u.l.base = base; Ci->top = Base+ p->maxstacksize;       Lua_assert (Ci->top <= l->stack_last);  CI-&GT;U.L.SAVEDPC = p->code;       /* Starting point */ci->callstatus = Cist_lua;       L->top = ci->top;       if (L->hookmask & Lua_maskcall) Callhook (L, CI);     return 0; }//meta-table-driven function invocation, "call": function invocation operation func (args). This event is triggered when Lua attempts to invoke a value that is not a function (that is, Func is not a function).     To find the Meta method of Func, if found, call the meta-method, Func as the first parameter passed in, the original invocation of the parameters (args) followed by the following.  Default: {/* not a function */Luad_checkstack (L, 1);  /* Ensure space for Metamethod */func = Restorestack (L, FUNCR);  /* Previous call could change stack */TRYFUNCTM (L, func);  /* Try to get ' __call ' Metamethod */return Luad_precall (L, Func, nresults);                                                                                                                                                                                           /* Now it must is a function */}}}

Lua_postcall is mainly to adjust the stack after the function call, especially to adjust the return value and function call chain, the code description is very clear.

int Luad_poscall (lua_state *l, stkid firstresult, int nres) {  stkid res;  int wanted, I;  Callinfo *ci = l->ci;  if (L->hookmask & (Lua_maskret | Lua_maskline) {    if (L->hookmask & Lua_maskret) {      ptrdiff_t fr = Savestack (L, firstresult);  /* Hook Change stack *      /Luad_hook (L, Lua_hookret,-1);      Firstresult = Restorestack (L, FR);    }    L->OLDPC = ci->previous->u.l.savedpc;  /* ' OLDPC ' for caller function */  }  res = ci->func;  /* Res = = Final position of 1st result */  wanted = ci->nresults;  L->ci = ci->previous;  /* Back to Caller *  /* Move results to correct place *  /for (i = wanted; I! = 0 && nres--> 0; i--) C20/>setobjs2s (L, res++, firstresult++);  while (i--> 0)    setnilvalue (res++);  L->top = res;  return (Wanted-lua_multret);  /* 0 iff wanted = = Lua_multret *}

Fourth: The use of the continuation function

This refers to the exception handling of function calls in Lua, which relies on ljmp for exception recovery, but if the call chain is suspended in the C function, the stack in C is lost when you try to restore the call stack again using Lua_resume. Popular point is: you in a function a yield, the first resume in function B begins to execute a function, when the call process is interrupted when the yield is encountered, the thread is suspended, when you call resume again, What you want is to go back to the a function to continue executing a code snippet below the yield function, but this is not done because the C stack is no longer found in the LUA virtual machine! So LUA provides a continuation function to deal with this problem indirectly, you can pass in a K function in Lua_pcallk or LUA_CALLK, that is, the continuation function, when a yield in your call is awakened by the resume, because it is not able to go back to the C function to continue execution, But he goes back to the K function you provided, and lets you do things as an intermediate springboard! This is the continuation function. The LUA_PCALLK and LUA_CALLK functions cannot be called at the outermost layer, or the problem mentioned above, the outermost function call will appear above if it is not initiated with Lua_resume. In fact, this is a good understanding, because your function contains yield-related code snippets, so your functions are Allowyield, but through Lua_pcallk and Lua_callk is actually called Luad_ Call does not allow the version of Allowyield.

lua_api void Lua_callk (lua_state *l, int nargs, int nresults,  Lua_kcontext ctx, Lua_kfunction k) {stkid func;  Lua_lock (L);  Api_check (L, k = = NULL | |!islua (L->CI), "Cannot use continuations inside hooks");  Api_checknelems (L, nargs+1);  Api_check (L, L->status = = LUA_OK, "Cannot do calls on Non-normal thread");  Checkresults (L, Nargs, nresults);  Func = L->top-(nargs+1);  if (k! = NULL && L->nny = = 0) {/* need to prepare continuation? */l->ci->u.c.k = k;  /* Save Continuation */L->ci->u.c.ctx = CTX;  /* Save Context */Luad_call (L, Func, Nresults, 1);  /* Do the call *///yield version} else/* No continuation or no yieldable */ Luad_call (L, func, nresults, 0);  /* Just do the call *///notyield version  adjustresults (L, nresults); Lua_unlock (L);} 

Perhaps people will have doubts, I passed the K function, why not call yield version, because l->nny this value in Luastate initialization is not 0 but 1, so always into the Noyield version. When using Lua_resume to initiate a function call, at the beginning of lua_resume This function will reset the L->nny to 0, so under the lua_resume of the outer layer protection, Lua_ Pcallk and Luacallk are able to enter the yield version smoothly.   &NBSP;

Here the parameters and function positions have been adjusted, p3,p2,p1,func.errfunc the top-down arrangement on the stack lua_api int lua_pcallk (lua_state *l, int nargs, int nresults, int errfu  NC, Lua_kcontext CTX, lua_kfunction k) {struct CallS C;  int status;  ptrdiff_t func;  Lua_lock (L);  Api_check (L, k = = NULL | |!islua (L-&GT;CI), "Cannot use continuations inside hooks");  Api_checknelems (L, nargs+1);  Api_check (L, L->status = = LUA_OK, "Cannot do calls on Non-normal thread");  Checkresults (L, Nargs, nresults);  if (Errfunc = = 0) func = 0;    else {stkid o = index2addr (L, Errfunc);    Api_checkstackindex (L, Errfunc, O);  Func = Savestack (L, O);  } C.func = L->top-(nargs+1); /* function to be called *//point to the position of the if (k = = NULL | |  L->nny > 0) {/* No continuation or no yieldable? */c.nresults = nresults; /* Do a ' conventional ' protected call * * status = Luad_pcall (L, F_call, &c, Savestack (L, C.func), func);  Call F_call} else {/* Prepare continuation (call was already protected by ' resume ') */Callinfo *ci = l->ci;  CI-&GT;U.C.K = k;  /* Save Continuation */Ci->u.c.ctx = CTX;    /* Save Context */* Save information for error recovery */Ci->extra = Savestack (L, C.func);    Ci->u.c.old_errfunc = l->errfunc;    L->errfunc = func;  Setoah (Ci->callstatus, L->allowhook);  /* Save value of ' allowhook ' */ci->callstatus |= cist_ypcall;  /* function can do error recovery */Luad_call (L, C.func, Nresults, 1);    /* Do the call */Ci->callstatus &= ~cist_ypcall;    L->errfunc = ci->u.c.old_errfunc;  status = LUA_OK;  /* If it is here, there were no errors */} adjustresults (L, nresults);  Lua_unlock (L); return status;}

The following is a test code used to verify the above conclusion that the comment section is not operational because the outer layer directly uses LUA_PCALLK for function calls.

#include <stdio.h> #include <string.h> #include <lua.h> #include <lauxlib.h> #include <  lualib.h> #include <dlfcn.h> #include <math.h>static int cont (lua_state *l, int status, Lua_kcontext ctx) { printf ("Error occurred!!  \ n "); return 0;} static int pcall_test (Lua_state *l) {return Lua_yield (l,0);}  static int mytest (Lua_state *l) {printf ("mytest\n");  Lua_pushcfunction (L, pcall_test);  int ret = LUA_PCALLK (L, 0, 0, 0, 0, cont); return 1;}  int main (void) {Lua_state *l = Lual_newstate ();  Lual_openlibs (L);  Lua_pushcfunction (L, mytest);  Lua_pushcfunction (L, pcall_test);  Lua_callk (L, 0, 0, 0, cont);  /*IF (ret! = 0) {Const char* err = lual_checkstring (L,-1);  Err:attempt to yield from outside a coroutine printf ("%s\n", err);  }*///lua_resume (L, NULL, 0);  int ret = Lua_resume (L, NULL, 0);    if ((RET!=LUA_OK) && (Ret!=lua_yield)) {Const char* err = lual_checkstring (L,-1);    printf ("%s\n", err);  Return } ret = Lua_resume (L, NULL, 0);  Lua_close (L);  return 0;}

Research on LUA function execution flow and function continuation point problem

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.