lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Actually the "long jump" does NOT destroy the call stack: it goes to some piece of code that will save the call stack state, before foing to another suitable and runnnable coroutine for which it will restore its call stack.

The long jump in C by itself does not destroy anything, except in its default implementation (in the main application thread) that does this extreme cleanup (and it is used to allow terminating a program properly after catching all pending exceptions; eventually the long jump handler to which you go can avoid this cleanup.
Long jumps in C are used for implementing exception handlers, they are also used by C++'s own exception handler (which of course must not destroy the call stack).

As you can see in the example shown in https://www.tutorialspoint.com/setjump-and-longjump-in-c,
the local variables are preserved, meaning that the call stack is also preserved, and in that example (which creates a loop) the C function doing that will return normally to the caller after performing the loops, which are counted normally.

All I said is that long jumps are just like breakpoints in a debugger: it preserves the current execution state, which can be saved before switching to another context by terminating the jump to the location immediately after where "setjmp()" was called in that other context, where the state (including its call stack) will be restored.

The setjmp() and longjmp() functions in C do not perform any context switching or something that would allow the call stack to be preserved: it's up to the C code to do that class stack saving/restoring. That's why setjmp() and longjmp() are rarely used directly in C applications, but used within a library that will handle properly the context switching (and this is the case for "Structured Error Handling" (SEH) in C, or standard C++ exception handlers, or for the Lua_C library that implements the Lua's coroutines, or for other similar mechanisms used in OSes (like the cooperative "multitasking" in old 16-bit versions of Windows, whose kernel still was not preemptive).

Preemptive multitasking (i.e. true multithreading) however can be implemented in C (or other languages), by adding a hardware timer interrupt handler that will encapsulate the setjmp/longjmp: this allows creating a true multithreading kernel in DOS for example.

[I did that in 1982-1984 in Pascal, with basic timer interrupt handlers (but without even needing to use setjmp/longjmp but handling the context switching, including switching stacks and saving/restoring CPU/FPU registers, with a small x86 assembly code). It was used for a networking server application and at that time there was still no free Linux, and 16-bit Windows did not even exist to implement its cooperative multitasking also based on coroutines. My code created a really multithreaded program, but there was initially no virtual memory management so no real multiprocessing in the first version and it was still constrained by the 640KB limit in which the OS was also living, plus 384KB for devices; later I added the use of DOS's VMM/EMM for switching also memory contexts and allow true multitasking, extending the total usable memory usable to several megabytes where each process could have 196KB of process-private memory in the paged device area plus some other pages in the low virtual 640KB space, when DOS only allowed 640KB of virtual memory in a single process; it worked even without hardware CPU support for the virtual memory, but used the EMM hardware in the external north-bridge chipset controlling memory paging, using the "EMM286" DOS driver, and later the EMM386 DOS driver using the first hardware integrated CPU support; later came virtual DOS and the first x86 free Unix-based kernels including Linux; it took many years before Windows switched to Win32 with a true preemptive multiprocessing, not just preemptive multithreading, and a decent virtual memory manager; during these years, Unix and NT/Win32 were completely proprietary and costly). Finally this server was extended to use 32-bit registers and 48-bit hardware memory addresses, and all the OS was running in a separate process, allowing each process to use up to 1MB, and even more when using explicit paging, for file-mappings or I/O buffers, networking packets, audio/video frames, or memory-resident databases and filesystem caches.]

"setjmp/longjmp" are core elements needed in C which is very complex to write and test (it's implemented in assembly and highly platform/CPU specific), but beside this pair that can be isolated in a small piece of code, you can then implement very powerful things and reimplement any VM or complete OS with them, using portable code.


Le mar. 20 août 2019 à 09:55, Tim Hill <drtimhill@gmail.com> a écrit :


> On Aug 19, 2019, at 7:07 AM, Andreas Falkenhahn <andreas@falkenhahn.com> wrote:
>
> I think it is possible, you just need the right trick ;) I've now come up with something that seems to do the job: The key to success was realizing that it is perfectly legal for Lua hooks to yield. Since Lua hooks can be called before every instruction I can just use this insight to yield before OP_CALL instead of during it.
>

No, Philippe is quite correct, there is no “trick” you can pull to work around this. To implement coroutines, Lua uses “long jumps” in C, and these implicitly demolish the C call stack. This means that lua_yield() NEVER RETURNS and any function that calls lua_yield() is abandoned (and any nested outer C functions as well). Whatever you do can not work around the fact that to yield a coroutine MUST demolish the C stack but the very way in which Lua intermixes the Lua and C stacks.

—Tim