Yes. However, I'm not sure how much I can help, since I'm actively
developing and getting to year-end deadlines...
> I'm writing my own toy CL-like language, and i'm very interested in GC implementation on x86 and other such systems where lisp control stack is shared with C.
>
> I mean, i cannot understand how the stack is examined - in the presence of FFI, the stack can contain raw C data, and so we cannot distinguish such data from lisp pointers. As i understand, this requires "conservative" garbage collector implementation. But then, how to look for GC roots on the stack, anyway? Can you please point to some articles/books describing such GC implementations(particularly, implementations for lisp-like languages)?
>
> On the other side - if the control stack is separated from "number" stack, how proper unwinding semantics could be implemented(i mean, lisp unwinding should also reflect on C stack, so we can use such things as windows SEH to implement unwind-protect and friends)?
It seems you have a good understanding of the tradeoffs; the former
has identification problems, and the latter has synchronization
problems, especially during unwinding. It seems to be easiest to push
the stack structure decisions off to gc, rather than to actively
manage the stack at run-time; consider that a gc might not happen
during 10 advances and retreats of the stack, and so all of that stack
management while building and tearing down the stack is wasted.
Better to put as little control data on the stack as possible. and
have the gc do the work, at the time it is actually needed.
Another issue, assuming a decision has been made for a shared control
stack, is how to mark the stack, when C programs know nothing about
such marking. Clearly, unless you want to force C users into a
particular calling discipline (which then locks out the use of other
languages and/or pre-built libraries) the lisp itself has to lay down
the separation markers on the stack.
In Allegro CL, we have a structure on the stack called a clink chain,
which is built and maintained by those lisp functions which perform
the transitions from lisp to foreign code and vice versa. We also
identify functions as being declared "from-c" as opposed to lisp only,
and also a lisp function can make a "c-call" or a lisp call (either
call can be made from any lisp function, due to the way we set up
these clink structures). The information needed in these structures:
1. must be findable by the gc, per thread
2. must be on the stack, so as not to run into the same problem as
would be seen by separating the stacks
3. must be maintained by all lisp functionalities, including
interrupts.
A c-call chains in a new clink before the call is made, setting it to
a state that is identifiable as being "in C". The from-c functions
will immediately mark the top clink as having come back from C, so
that the slink is identifiable as being "in lisp". We also used to
establish a "leaf" state, for C functions which promise never to call
back into lisp, but that proves to be an impractical promise,
especially in the face of debugging, interrupts, and Windows-style
message-passing.
Once the clink chain is established, the gc can walk the stack and
know which sections to ignore. Assuming the gc knows the lisp frame
structures, it can then walk those frames and perform either
conservative or precise gc on the slots therein.
Good luck in your design.
Duane