Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack.
This is very useful for a number of reasons. It's also a very rare feature to have in your language.
Neither node nor lua has support for this. The closest I've found is a Lua extension which adds coroutine.clone(). In principle, this is the solution. In practice, it has a number of limitations, such as restrictions on when you're allowed to call coroutine.clone(). (For example, if your stack looks like Lua -> C -> Lua, then it won't work.)
I tried to channel my inner Mike Pall and solve this problem once and for all, but I'm not Mike Pall, and this is really hard. I was hoping you might know of any possible solution which is (a) practical, (b) cross-platform, and (c) works in all cases.
Why post this here? Because this post happens to attract exactly the kind of people that might know a way forward. There must be a way. Apologies for the off-topic comment.
He mentioned a paper titled "Exceptional Continuations in JavaScript" [2], that describes the method he used in his implementation.
Maybe you should get in touch?
I don't think you can safely solve this in the general case. There is a key problem I don't think you can work around.
Say your stack looks like C(1) -> Lua -> C -> Lua. The outermost C frames might not know anything about Lua (they just use some library that uses Lua as a library). Say you try to take a snapshot of this stack to create a continuation. You probably just want to snapshot the Lua -> C -> Lua part, since that is the portion of the stack representing the execution of the Lua program.
Now say all these frames return. Then the main program calls Lua again, through through a slightly different code-path, and now you have C(2) -> Lua. Say the embedded Lua program decides to resume the continuation.
Now keep in mind that the C stack is not position-independent. The C stack can contain pointers to the C stack, so when you resume, you need your resumed stack to live at exactly the same address as last time it ran. But what if C(1) and C(2) are not exactly the same size? What if we called one extra function before calling Lua the second time? It is impossible to copy the continuation's C stack back into its original position. So it's impossible to resume the continuation.
You could try to snapshot the entire C stack to get around this, including the outermost C frames. But this would be most unexpected for the C program that is using the Lua interpreter. Lua is supposed to just be a regular C library: you call a function, it does things, and then returns. It wouldn't be acceptable that calling a Lua interpreter function like lua_call() backs your entire C program to a previous state just because the embedded Lua program used a fancy feature called continuations!
There are many other things that would make this tricky at best to get working, but I think the problem above really tanks the idea completely.
Say you want to resume continuation K, which has a stack of some size N.
The current thread has a stack of size M. If M >= N, everything is fine: you can safely overwrite the current stack with K's stack.
If M < N, recurse until M >= N.
You could try to snapshot the entire C stack to get around this, including the outermost C frames.
Indeed! This is a solution.
It wouldn't be acceptable...
I like doing unacceptable things in my programs. It's the best part of programming, really.
There are a lot of solid arguments against call/cc. I think the most persuasive argument in favor of call/cc is that you become more powerful. Whatever metric you use to measure power, call/cc will improve it: Smaller code, less time spent writing code, and you can even write algorithms that you otherwise would not be able to.
Personally, I want call/cc in order to be able to use choose and fail. It's the ability to write programs that are guaranteed to never call fail(). pg explains it well:
"For example, this is a perfectly legitimate nondeterministic algorithm for discovering whether you have a known ancestor called Igor:
Function Ig(n)
if name(n) = ‘Igor’
return n
if parents(n)
return Ig(choose(parents(n)))
fail
The fail operator is used to influence the value returned by choose. If we
ever encounter a fail, choose would have chosen incorrectly. By definition choose
guesses correctly."Call/cc makes this possible. There are a lot of fun things to do. The last few chapters of On Lisp show some particularly interesting sketches.
If you want to use this to do something that takes multiple invocations of lua_resume to complete without the calling Lua code being aware, you might be able to use lua_yieldk.
#define ASM_VEC_BYTE_COUNT_SET(vec, sum, mask, shuf) \
__asm volatile ("vpsrld $4, %[VEC], %[SUM]\n" \
"vpand %[MASK], %[VEC], %[VEC]\n" \
"vpand %[MASK], %[SUM], %[SUM]\n" \
"vpshufb %[VEC], %[SHUF], %[VEC]\n" \
"vpshufb %[SUM], %[SHUF], %[SUM]\n" \
"vpaddb %[VEC], %[SUM], %[SUM]\n" : \
/* rd/wr ymm */ [VEC] "+&x" (vec), \
/* write ymm */ [SUM] "=&x" (sum) : \
/* read ymm */ [MASK] "x" (mask), \
/* read ymm */ [SHUF] "x" (shuf))
1) Try to use the %[symbolic] syntax rather than %[n] numeric. It's slightly longer to write, but usually clearer to read. Use upper case for the symbolic name. Put your inputs one per line, with a preceding comment.2) If you are using the same assembly more than once in your program, declare your assembly within a #define macro, then use the macro in your code.
3) Use "__asm volatile". Declaring "volatile" is not required, but once you are writing inline assembly you usually know more than the compiler about where the block should go.
5) If you have multiple lines of assembly and output registers, you are almost always safer to use "+&" and "=&" for your constraint rather than just "+" or "=". Search for "early clobber" for details.
6) Strongly prefer single type constraints. The more flexibility you give the compiler, the more likely it will defeat your efforts at optimization. Use explicit memory addressing modes rather than "m". The modifier "c" is needed for the offset.
#define ASM_VEC_LOAD_OFFSET_MEM(off, mem, vec) \
__asm volatile ("vmovdqu %c[OFF](%[MEM]), %[VEC]\n" : \
/* destination */ [VEC] "=x" (vec) : \
/* byte offset */ [OFF] "i" (off), \
/* mem address */ [MEM] "r" (mem))
7) The register constraints for vectors are tricky, because the "x" constraint is used for both XMM and YMM vectors. There is no way to specify that one wants only one or the other. This sort of makes sense, since in hardware they share the same register. You can use the "q" modifier when you need to specify XMM syntax in the output when you need both forms of the same vector.5 - I can't think of any meaning early clobber has on an input+output constraint ("+")?
6 - there are many cases where you really do want to give the compiler flexibility in addressing modes. Unfortunately clang tends to ignore that and generate (reg) regardless.
7 - not really different than GPRs; you use "r" as the constraint then a modifier like "k" for the size.
I guess the lesson is that yeah gcc inline asm is powerful, but they try to leave it undocumented for a reason. Also, who stole number 4?
re 5: Barring compiler bugs, I think you'd be right if correctness was the only issue. But I'm pretty sure I've sometimes solved problems by adding it, although this may have been when working around the POPCNT bug that added a false dependency on the output. It also might have been when reading and writing a variable multiple times?
re 6: In theory, yes. But usually in these cases you should be writing intrinsics or straight C instead of inline assembly. The place where this comes up most for me is when I have two variables that use the same index, and I want to ensure "DEC/JNZ" fusion at the end of the loop. If I let the compiler choose, it will find a way to defeat me by incrementing both array addresses. The other case is when you explicitly want a store to use Port 7 for address generation, which only happens without an index register.
re 7: Yes, I just personally find it more confusing because "x" fits so well with "XMM", and thus it feels odd to use it when you want only a "YMM". Also, see here for problems with a Clang and %q[VEC]: http://stackoverflow.com/questions/34459803/in-gnu-c-inline-...
re 4: Oops, I forgot to renumber. I had another comment suggesting that one always use the "V" VEX prefix on vector commands and the explicit output register, but deleted it because it seemed off topic.
Of course this is not true for synchronization primitives and the like.
My goal is to "lock in" an established level of performance once I've achieved it, so that compiler upgrades or changes don't result in performance drops. I often compare the output of multiple compilers with a matrix of optimization flags, choose the best blocks from each, and then hand-optimize from there while cross-referencing Agner's handbooks with Likwid's performance reports. If I've chosen to use inline assembly, the chances that the compiler will succeed in further optimizing my code is very low.
I realize it's not a popular view, but I think that using volatile with __asm is usually the correct approach. If you don't need "volatile", you probably should be using an intrinsic instead. I think the alternative (which may in fact be the better solution) is dropping to straight assembly for the entire function or distributing binary code.
Another approach that's not quite there yet but is becoming more possible is to use https://www.cilkplus.org to annotate your C code to force automatic vectorization. It's native to ICC, built-in to GCC 5.0+, and available as an extension to Clang: https://news.ycombinator.com/item?id=11550250
The asm {} blocks of PC compilers are so much developer friendly.
I rather use an external Assembler than GCC's inline syntax.
#define mb() asm volatile("mfence":::"memory")
#define rmb() asm volatile("lfence":::"memory")
#define wmb() asm volatile("sfence" ::: "memory")
Those are memory barriers, so they essentially must be inline (they'd be too slow and possibly even change their meaning if they were located in a separate source file and you had to call them).[1]: https://github.com/andrewrk/zig/blob/master/std/linux_x86_64...