non-local exits are icky
Tom Lord
(23 Dec 2003 23:07 UTC)
|
Re: non-local exits are icky
Michael Sperber
(26 Dec 2003 15:41 UTC)
|
Re: non-local exits are icky
Tom Lord
(26 Dec 2003 18:14 UTC)
|
Re: non-local exits are icky
Michael Sperber
(26 Dec 2003 18:30 UTC)
|
Re: non-local exits are icky
Tom Lord
(26 Dec 2003 19:19 UTC)
|
Re: non-local exits are icky
Michael Sperber
(27 Dec 2003 16:16 UTC)
|
Re: non-local exits are icky Tom Lord (27 Dec 2003 18:38 UTC)
|
Re: non-local exits are icky
Michael Sperber
(27 Dec 2003 18:43 UTC)
|
T>>> [The FFI lacks unwind-protection for C code which is a T>>> serious problem since the FFI also uses non-local exits past T>>> C code. I see a choice in the design space: either add T>>> unwind protection for C, or user error-codes rather than T>>> than non-local exits. I think the latter is cleaner.] I'll also add that they're equivalently expressive: given an error-code-using FFI I can write a layered FFI that uses non-local-exits-past-C and provides unwind-protection for C; conversely, given non-local exits and unwind-protection for C, I can write a layered FFI that uses error codes. Scheme's dynamic-wind (your proposed answer) plus the proposed FFI comes _close_ to providing unwind protection for C, but falls short. I can not layer on top of it either an error-code based interface, or an interface that provides sufficient unwind protection from C. In general, let's say we want to write a C function which is _conceptually_: fn () { declare_my_local_state; init_my_local_state; /* cleanups required after this */ do_some_scheme_stuff; /* might trigger a non-local exit */ cleanup_my_local_state; } You're saying, let's write that conceptual function as four actual C functions: fn_prolog () { allocate_my_local_state_on_the_scheme_heap; init_my_local_state_part_1; } fn_postlog () { cleanup_my_local_state_part_2; } fn_body () { init_my_local_state_part_2; do_some_scheme_stuff; cleanup_my_local_state_part_1; } fn () { arrange_to_scheme_eval (dynamic-wind fn_prolog fn_body fn_postlog); } The biggest problem with that solution is that it can not be adapted for use with libaries not written in anticipation of it. Here's an illustration: A common C idiom, one you can likely find in just about any GUI toolkit for example, is the "callback" idiom. A library exports entry points like: add_callback_fn (event_type_t event_type, int (*)(fn)); handle_event (event_t event); where calling handle_event can result in calls to a bunch of functions registered with add_callback_fn. A callback mechanism can reasonably make provisions for errors: a callback function may return an error code causing the callback run to be aborted or to otherwise change its course. An FFI should be able to handle a situation where: Scheme calls out to C, eventually to handle_event. handle_event calls to C, to my_hook_fn. my_hook_fn calls out to Scheme Unless the GUI library I'm using has made it safe to longjmp past handle_event, I can not implement this using the proposed FFI. A Scheme error underneat my_hook_fn will want to exit all the way past handle_event. There is no way, upon such an error, to return flow control back to handle_event. Another problem concerns the awkwardness of protecting against asynchronous interrupts. While we can reasonably expect interrupts to be implicitly masked during the prolog and postlog, they are possible from the time the prolog returns to the time the postlog is entered. Meanwhile, the body is required to manage the local state in such a way that the postlog can always clean up reliably -- which means it will sometimes have to make complex atomic updates to that state. In the general case, then, one will have to write SCHEME_CALL_SANS_INTERRUPTS essentially as a call to (dynamic-wind noop noop atomic-step) and use that in fn_body for every update to the local state -- you'll need not just the four functions above, but a bunch of additional helper functions for those updates. I despair of many programmers either wanting to write such code or of getting it right. Another very minor problem is that the FFI provides no reliable means by which to apply a version of DYNAMIC-WIND. Either a C function, SCHEME_DYNAMIC_WIND is needed, or it should be defined as a mandatory shared binding. > ... and handle the non-local control flow from Scheme. C just > isn't powerful enough to play these games gracefully. Well, yes, that's my point. As I see it, the FFI design-space has a bifurcation wrt to non-local exits from Scheme past C code: 1) You can not use them -- use error values instead. C is quite powerful enough to handle those. They're easy to understand and, most importantly, they "play nicely" with more C libraries. They tend to be lower-overhead in the common case where errors don't occur. As I recall, Tcl (which has a notoriously clean and easy to use FFI) goes this route. 2) You can try to design an unwind-protect mechanism for C and add that to the FFI. This is what Guile and Systas have done internally for years and if you look at recent guile-dev list traffic, you can see that they're working on adding a similar facility to their "simpler, easier to use" FFI layer (the gh_ layer). -t