Dear Marc,
Below A B C D are Scheme procedures and f g h are C procedures.
In case of nested Scheme->C calls, i.e. if A calls f which calls B which calls g which calls C which calls h which calls D etc., then for the application not to enter an undefined state, returning the C calls _MUST_ be done in the exact reverse order in order, right? I.e., D must return to h which must return to C which must return to g which must return to B which must return to f which must return to A. If any mixing of this order is done - i.e. D returns to g, or B returns to h, etc., then the application enters an undefined state, right, or how is it?
That is, * Gambit in itself does not implement any blocking feature such that while g and h are still on the stack, a return from f to A would block until D returned to h and C returned to G * Gambit does not implement any exception handling for trying to return in the wrong order, but if returning in the wrong order the application simply terminates at some point * Gambit uses one ordinary C stack for all Scheme->C and C->Scheme calling, so it does not allocate a new C stack dynamically on the heap, or alike, for any Scheme->C call.
I experimented a bit with returning C->Scheme calls in the wrong order, and what happened was that at the LAST return I made, the application terminated. I suppose this is consistent with how it should be - the Gambit app may behave just about any way if you returned C->Scheme calls in the wrong order, as this messed up the C stack and the app is about to 'blow' just about any second. (I had A call f which called B which called g which called C which called h which called D, then D returned to g which returned to B which returned to h - and at h's return to Scheme the app terminated)
Also I suppose this is in line with the "19.7 Continuations and the C-interface" manual section and your paper http:/www.iro.umontreal.ca/~feeley/papers/FeeleySW00.pdfhttp://www.iro.umontreal.ca/~feeley/papers/FeeleySW00.pdf , which I suppose Gambit implements.
Looking forward to get clear on this one :-)
Kind regards, Mikael
test_c_stack_rewinding.scm :
(c-define (c-run thunk) (scheme-object) void "c_run" "" (print "c-run entered for " thunk ".\n") (thunk) (print "c-run returned for " thunk ".\n"))
(define run-through-c (c-lambda (scheme-object) void "c_run(___arg1);"))
(define (thread-go id sleep-seconds) (thread-start! (make-thread (lambda () (print "thread " id " entered.\n") (run-through-c (lambda () (print "thread " id "'s c thunk entered.\n") (thread-sleep! sleep-seconds) (print "thread " id "'s c thunk returning.\n"))) (print "thread " id " returned.\n")))))
REPL interaction: $ gsc Gambit v4.5.3
(compile-file "test_c_stack_rewinding.scm") (load "test_c_stack_rewinding")
;; Test one simultaneous Scheme->C->Scheme call:
(thread-go 1 5)
#<thread #2>
thread 1 entered.
c-run entered for #<procedure #3>. thread 1's c thunk entered. 1 1
thread 1's c thunk returning.
c-run returned for #<procedure #3>. thread 1 returned.
;; Scheme->C->Scheme calls can be made many at a time, as long as the last one made returns first:
(thread-go 3 10) (thread-sleep! 1) (thread-go 4 5)
#<thread #8>
thread 3 entered.
c-run entered for #<procedure #9>. thread 3's c thunk entered.
#<thread #10> thread 4 entered.
c-run entered for #<procedure #11>. thread 4's c thunk entered. thread 4's c thunk returning. c-run returned for #<procedure #11>. thread 4 returned.
thread 3's c thunk returning. c-run returned for #<procedure #9>. thread 3 returned.
(thread-go 8 15) (thread-sleep! 1) (thread-go 9 10) (thread-sleep! 1)
(thread-go 10 5) #<thread #2>
thread 8 entered.
c-run entered for #<procedure #3>. thread 8's c thunk entered.
#<thread #4> thread 9 entered.
c-run entered for #<procedure #5>. thread 9's c thunk entered.
#<thread #6> thread 10 entered.
c-run entered for #<procedure #7>. thread 10's c thunk entered. thread 10's c thunk returning. c-run returned for #<procedure #7>. thread 10 returned. thread 9's c thunk returning. c-run returned for #<procedure #5>. thread 9 returned. thread 8's c thunk returning. c-run returned for #<procedure #3>. thread 8 returned.
;; If C->Scheme calls return in another order then the app terminates
(thread-go 5 5) (thread-sleep! 1) (thread-go 6 10)
#<thread #12>
thread 5 entered.
c-run entered for #<procedure #13>. thread 5's c thunk entered.
#<thread #14>
thread 6 entered. c-run entered for #<procedure #15>. thread 6's c thunk entered. 1 1
thread 5's c thunk returning.
c-run returned for #<procedure #13>. thread 5 returned.
0 0
thread 6's c thunk returning.
c-run returned for #<procedure #15>. $
(thread-go 1 6) (thread-sleep! 1) (thread-go 2 12) (thread-sleep! 1)
(thread-go 3 9) (thread-sleep! 1) (thread-go 4 5) #<thread #2>
thread 1 entered.
c-run entered for #<procedure #3>. thread 1's c thunk entered.
#<thread #4> thread 2 entered.
c-run entered for #<procedure #5>. thread 2's c thunk entered.
#<thread #6> thread 3 entered.
c-run entered for #<procedure #7>. thread 3's c thunk entered.
#<thread #8> thread 4 entered.
c-run entered for #<procedure #9>. thread 4's c thunk entered. thread 1's c thunk returning. c-run returned for #<procedure #3>. thread 1 returned. thread 4's c thunk returning. c-run returned for #<procedure #9>. $
Afficher les réponses par date
On 2011-03-28, at 2:04 AM, Mikael wrote:
Dear Marc,
Below A B C D are Scheme procedures and f g h are C procedures.
In case of nested Scheme->C calls, i.e. if A calls f which calls B which calls g which calls C which calls h which calls D etc., then for the application not to enter an undefined state, returning the C calls _MUST_ be done in the exact reverse order in order, right? I.e., D must return to h which must return to C which must return to g which must return to B which must return to f which must return to A. If any mixing of this order is done - i.e. D returns to g, or B returns to h, etc., then the application enters an undefined state, right, or how is it?
Mikael, have you read the section "Continuations and the C-interface" in the Gambit manual? Note that Gambit implements threads using continuation captures/invocations. Because the thread system is preemptive, it is possible for the context switching between threads to happen at any time, and you have to design your program to tolerate this. This is an issue when more than one Scheme thread calls a C function which calls a Scheme function. For example, assume two Scheme threads T1 and T2 performing these (non-tail) calls
T1: A -> f -> B T2: X -> g -> Y
If there was a context switch to T2 while T1 was executing B, and T2 which was in X then did its call to g, the C stack will contain the stack frame for f and on top of that the stack frame for g.
If T2 completes the call to Y without a context switch, then it will pop g's frame from the C stack, leaving only the frame for f. If there is now a context switch to T1 (say when T2 is back in X) then execution will resume in B, and the return to f can proceed normally. All is good.
However... If T2 is in Y when there is a context switch back to T1, the execution will resume in B, and the return to f will be done by *removing the frame for g* from the C stack (using a longjump). When T2 wakes up and tries to return to g there will be a run time error because g's frame no longer exists.
This is a consequence of interleaving the C stack frames of multiple Scheme threads on a single C stack.
The "solution" is to have at most one Scheme thread which has call chains interleaving Scheme and C calls. It is OK for other Scheme threads to call C code, but they should not call back to Scheme.
Is this clear?
Marc
Hi,
Yes, how Scheme->C->Scheme must be returned is now perfectly clear to me, and this was what I was looking for - thanks!
I'll put it on the wiki.
As a real sidenote, if there's any way to get the error on 'return to g there will be a run time error because g's frame no longer exists' as a regular (error) report in the REPL or to catch it with with-exception-catcher etc. instead of that the app terminates with no error message, please let me know.
Kind regards, Mikael
2011/3/28 Marc Feeley feeley@iro.umontreal.ca
On 2011-03-28, at 2:04 AM, Mikael wrote:
Dear Marc,
Below A B C D are Scheme procedures and f g h are C procedures.
In case of nested Scheme->C calls, i.e. if A calls f which calls B which
calls g which calls C which calls h which calls D etc., then for the application not to enter an undefined state, returning the C calls _MUST_ be done in the exact reverse order in order, right?
I.e., D must return to h which must return to C which must return to
g which must return to B which must return to f which must return to A.
If any mixing of this order is done - i.e. D returns to g, or B
returns to h, etc., then the application enters an undefined state, right, or how is it?
Mikael, have you read the section "Continuations and the C-interface" in the Gambit manual? Note that Gambit implements threads using continuation captures/invocations. Because the thread system is preemptive, it is possible for the context switching between threads to happen at any time, and you have to design your program to tolerate this. This is an issue when more than one Scheme thread calls a C function which calls a Scheme function. For example, assume two Scheme threads T1 and T2 performing these (non-tail) calls
T1: A -> f -> B T2: X -> g -> Y
If there was a context switch to T2 while T1 was executing B, and T2 which was in X then did its call to g, the C stack will contain the stack frame for f and on top of that the stack frame for g.
If T2 completes the call to Y without a context switch, then it will pop g's frame from the C stack, leaving only the frame for f. If there is now a context switch to T1 (say when T2 is back in X) then execution will resume in B, and the return to f can proceed normally. All is good.
However... If T2 is in Y when there is a context switch back to T1, the execution will resume in B, and the return to f will be done by *removing the frame for g* from the C stack (using a longjump). When T2 wakes up and tries to return to g there will be a run time error because g's frame no longer exists.
This is a consequence of interleaving the C stack frames of multiple Scheme threads on a single C stack.
The "solution" is to have at most one Scheme thread which has call chains interleaving Scheme and C calls. It is OK for other Scheme threads to call C code, but they should not call back to Scheme.
Is this clear?
Marc