The most significant improvement of Lua 5.2 was "yieldable pcall and metamethods ". This requires us to overcome a difficult problem: how to correctly call yield back to resume in the C function call.
Resume is always initiated through a lua_resume call. Before Lua 5.1, yield calls must end with a lua_yield call, and the C function that calls it must be immediately returned. No C function is in the middle of execution. In this way, Lua VM can work normally.
(C) lua_resume-> Lua functions-> coroutine. yield
-> (C) lua_yield-> (C) return
In this process, no matter how many layers Lua functions has, it is managed by the lua stack in The lua state. Therefore, when C return returns to the original resume point, there is no problem, so that the next resume can continue correctly. That is to say, in yield, whether the lua function has been executed can be completed on the lua stack, but not whether the C function has been executed.
If we write such a C extension, we call back a passed-in Lua function in the C function. The situation becomes different.
(C) lua_resume-> Lua function-> C function
-> (C) lua_call-> Lua function
-> Coroutine. yield-> (C) lua_yield
When C calls coroutine. yield again in the Lua function called by lua_call, it will no longer be possible to continue running from the next line of lua_call after yield. In this case, lua throws an exception "attempt to yield processing SS metamethod/C-call boundary ".
Someone tried to solve this problem before January 5.2 and removed the limitations of coroutine. For example, Coco. It uses the operating system coroutine to solve this problem (for example, using Fiber on Windows ). That is, each lua coroutine is attached to a C coroutine and a C stack is independent.
This solution is costly and relies on platform features. In Lua 5.2, a more thorough solution was provided to solve this problem.
In fact, the problem that needs to be solved is how to continue running the C code after the C boundary after the yield when the C and Lua boundary.
When there is only one C stack, it can only jump out of the calling depth (using longjmp), but cannot return to that position (because the stack will be destroyed once it jumps out ). Lua 5.2 thought of a clever way to solve this problem.
There are four APIs for C to access Lua: lua_call, lua_pcall, lua_resume and lua_yield. The key problem to be solved is that a call lua function has two return paths.
The normal return of lua function should execute the C code after lua_call. If yield occurs in the middle, the execution will return to the next line of C code in the previous lua_resume call. For the latter type, after a subsequent lua_resume occurs, lua coroutine ends and the subsequent C execution logic needs to be completed after lua_call is returned. C language does not allow this because the C stack does not exist.
Lua 5.2 provides a new API: lua_callk to solve this problem. Since the execution sequence of C cannot return to the next line of code of lua_callk after yield, the C language user can provide a Continuation function k to continue.
We can understand the k parameter as follows: when the lua function called by lua_callk does not contain yield, it returns normally. Once yield occurs, the caller must understand that the C Code cannot be continued normally, and lua vm will call k to complete subsequent work when the continuation is required.
K will get the correct L and keep the correct lua state. It looks like replacing the original C execution order with a new C execution order.
A typical usage is to use callk at the end of a C function call:
Lua_callk (L, 0, LUA_MULTRET, 0, k );
Return k (L );
That is to say, put the execution logic behind callk in an independent C function k, call it after callk, or pass it to the framework, so that the framework can be called after resume.
Here, the status of the lua state machine is correctly stored in L, and the C function stack is destroyed after yield. What if we need to get the C function status before the continuation point in k? Lua provides ctx to help record the status in C.
In k, you can use lua_getctx to obtain the k passed in the last boundary call. Lua_getctx returns two parameters, k and the current execution position. Is the original function (not interrupted by yield) or the continuation point function after yield is interrupted. There is a little bit of interface design like setjmp or fork.
Author: Yunfeng