callback.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. ; Part of Scheme 48 1.9. See file COPYING for notices and license.
  2. ; Authors: Richard Kelsey, Jonathan Rees, Mike Sperber, Marcus Crestani, Robert Ransom,
  3. ; Harald Glab-Phlak
  4. ; This code, along with C code in c/external.c, handles the interaction between
  5. ; callbacks from external code to Scheme functions and uses of continuations in
  6. ; Scheme. The problem is that Scheme 48 uses multiple continuations while
  7. ; operating with only one process stack.
  8. ;
  9. ; Suppose we have Scheme procedures s1 and s2 and C procedure c1 such that
  10. ; s1 calls c1 and c1 calls s2. There are two trampoline functions that are
  11. ; used to do this. The VM uses s48_external_call to call c1 and c1 uses
  12. ; s48_call_scheme to start the VM running s2. While in s2 the process stack will
  13. ; look like this:
  14. ;
  15. ; <C frame for VM running s2>
  16. ; <C frame for s48_call_scheme>
  17. ; <C frame for c1>
  18. ; <C frame for s48_external_call>
  19. ; <C frame for VM running s1>
  20. ; <base>
  21. ;
  22. ; The C code in c/external.scm keeps a record of the portions of the process
  23. ; stack that are running external code. Each of these stack portions has an
  24. ; s48_external_call frame at the base and an s48_call_scheme frame at the top.
  25. ; The stack is represented as linked list of records, called `stack-block's,
  26. ; each of which contains the following values:
  27. ; free? ; true if this frame is no longer needed
  28. ; unwind ; the longjmp target used to skip over this frame
  29. ; proc-name ; the name of the procedure this block is executing
  30. ; placeholder ; either #f or a placeholder, see the section on threads below
  31. ; next ; the next stack-block below this one
  32. ; These are Scheme records and are traced by the GC.
  33. (define-record-type stack-block :stack-block
  34. (stack-blocks-are-made-from-c)
  35. stack-block?
  36. (free? stack-block-free? set-stack-block-free?!)
  37. (unwind stack-block-unwind)
  38. (proc-name stack-block-proc-name)
  39. (placeholder stack-block-placeholder set-stack-block-placeholder!)
  40. (next stack-block-next))
  41. ; Stack-blocks are made from C, so we need to export the type.
  42. (define-exported-binding "s48-stack-block-type" :stack-block)
  43. ; There is no need to keep track of the VM frames. These are all interchangeable
  44. ; because 1) the VM's state is kept in top-level variables and 2) we have
  45. ; arranged it so that the relevant VM opcodes, call-external-value and
  46. ; return-from-callback, are all the same length and are always immediately
  47. ; followed by a return instruction. s48_call_scheme can safely overwrite the
  48. ; template and code-pointer registers in the VM as they always point to a
  49. ; one-byte instruction followed by a return instruction. When the VM returns
  50. ; from the callback, via a return-from-callback instruction, that too is a
  51. ; one-byte instruction followed by a return instruction. The VM can proceed,
  52. ; happily ignorant of all this fooling around.
  53. ;
  54. ; On entry, s48_external_call saves a longjump target. This is used when
  55. ; raising exceptions from with the external code and for unwinding the process
  56. ; stack. Each invocation of s48_call_scheme creates a new stack-block, saving
  57. ; within it the longjump target of the corresponding s48_external_call. `Free?'
  58. ; and `placeholder' are initially false and `next' points to existing list of
  59. ; stack-blocks.
  60. ;
  61. ; When a callback returns to s48_call_scheme, the corresponding block is popped
  62. ; off the list of stack-blocks.
  63. ;
  64. ; So far so good, and if that were all that happened there would be no need for
  65. ; all this mechanism. There are two problems: call/cc and threads. Call/cc is
  66. ; simpler to deal with. We have downward continuations in C, as implemented
  67. ; by longjmp(), so we simply limit continuations that cross callbacks to being
  68. ; downwards only. We also need to arrange for any jumped-over stack portions
  69. ; to be popped off of the stack.
  70. ;
  71. ; The popping off is handled by s48_external_call. Just before returning to the
  72. ; VM it checks to see if the top stack-block is free. If so, it loops through
  73. ; the list of stack-blocks to find the first non-free stack portion. A longjump
  74. ; is performed to the target in the last free block, removing any unneeded frames
  75. ; from the stack.
  76. ;
  77. ; s48_call_scheme starts the VM running the following CALLBACK procedure. The
  78. ; arguments are BLOCK, the stack-block just created for this callback, and
  79. ; the procedure and arguments for the actual callback. It prevents jumps back
  80. ; into the callback and frees BLOCK if a throw out occurs.
  81. ;
  82. ; We disable interrupts to ensure that nothing intervenes between setting DONE?
  83. ; and returning from the callback. BLOCK is then either freed or returned to,
  84. ; but not both or neither. RETURN-FROM-CALLBACK reenables interrupts.
  85. (define (callback block proc . args)
  86. (let ((done? #f))
  87. (return-from-callback block
  88. (dynamic-wind
  89. (lambda ()
  90. (if done?
  91. (apply
  92. assertion-violation 'callback
  93. "attempt to throw into a callback"
  94. (cons proc args))))
  95. (lambda ()
  96. (let ((result (apply proc args)))
  97. (disable-interrupts!)
  98. (set! done? #t)
  99. result))
  100. (lambda ()
  101. (if (not done?)
  102. (begin
  103. (set! done? #t)
  104. (set-stack-block-free?! block #t)
  105. (clear-stack-top!))))))))
  106. (define-exported-binding "s48-callback" callback)
  107. ; CLEAR-STACK-TOP! is an empty C procedure. When it returns, s48_external_call
  108. ; will automatically clear any free frames off of the stack.
  109. (import-lambda-definition clear-stack-top! () "s48_clear_stack_top")
  110. ; Dealing with threads.
  111. ;
  112. ; The difficulty here is that each stack-block belongs to some thread. Thread A
  113. ; can call a C procedure which calls back into Scheme. At that point a context
  114. ; switch occurs and we start running thread B, which promptly does the same
  115. ; calls. The process stack then looks like this:
  116. ;
  117. ; <C frame for VM running B1>
  118. ; <C frame for s48_call_scheme>
  119. ; <C frame for B's C code>
  120. ; <C frame for s48_external_call>
  121. ; <C frame for VM running A1 and then B0>
  122. ; <C frame for s48_call_scheme>
  123. ; <C frame for A's C code>
  124. ; <C frame for s48_external_call>
  125. ; <C frame for VM running A0>
  126. ; <base>
  127. ;
  128. ; At this point A cannot return from its callback before B does, because B's
  129. ; portion of the process stack is above A's. If A does try to return it must
  130. ; block until it again is at the top of the stack.
  131. ;
  132. ; This is handled by s48_call_scheme, which checks to see if the stack-block
  133. ; being returned to is at the top of the stack. If not, it does a second
  134. ; callback to DELAY-CALLBACK-RETURN, defined below, with the same stack-block.
  135. ; DELAY-CALLBACK-RETURN creates a placeholder, puts it in the stack-block, and
  136. ; then blocks on it. When the placeholder gets a value the procedure attempts
  137. ; another return-from-callback.
  138. ;
  139. ; This is called with interrupts disabled, as we need to avoid having BLOCK
  140. ; reach the top of the stack before the placeholder is installed.
  141. (define (delay-callback-return block value)
  142. (let ((placeholder (make-placeholder)))
  143. (set-stack-block-placeholder! block placeholder)
  144. (enable-interrupts!)
  145. (placeholder-value placeholder)
  146. value))
  147. (define-exported-binding "s48-delay-callback-return" delay-callback-return)
  148. ; Finally, s48_external_call looks to see if the top stack-block has a
  149. ; placeholder. If it does, it raises an exception instead of doing a normal
  150. ; return. The exception handler sets the placeholder's value, allowing the
  151. ; blocked thread to continue. The handler then returns the external call's
  152. ; value to its own thread, or, if the callback-return-uncovered is piggybacked
  153. ; on another exception, we raise that exception.
  154. ;
  155. ; Because of the all of the games played above, the callback-return-uncovered
  156. ; exception may appear to have come from either the call-external-value, or
  157. ; return-from-callback opcodes.
  158. (define uncovered-return-handler
  159. (lambda (opcode reason . args)
  160. (define (blow-up con extract-message)
  161. ;; look at external.c for why this is all so strangely reversed
  162. (let ((rev (reverse args)))
  163. (raise
  164. (condition
  165. con
  166. (make-external-exception)
  167. (make-who-condition (cadr rev))
  168. (make-message-condition
  169. (os-string->string (byte-vector->os-string (extract-message (car rev)))))
  170. (make-irritants-condition (reverse (cddr rev)))))))
  171. (enum-case exception reason
  172. ((external-error)
  173. (blow-up (make-error) values))
  174. ((external-assertion-violation)
  175. (blow-up (make-assertion-violation) values))
  176. ((external-os-error)
  177. (blow-up (make-error) os-error-message))
  178. ((out-of-memory)
  179. (raise
  180. (condition
  181. (make-implementation-restriction-violation)
  182. (make-who-condition 'call-external-value)
  183. (make-message-condition "out of memory"))))
  184. ((callback-return-uncovered)
  185. (call-with-values
  186. (lambda ()
  187. (if (= 2 (length args))
  188. (values (car args)
  189. (cadr args)
  190. #f)
  191. (let ((args (reverse args)))
  192. (values (car args)
  193. (cadr args)
  194. (reverse (cddr args))))))
  195. (lambda (block return-value exception-args)
  196. (let ((placeholder (stack-block-placeholder block)))
  197. (set-stack-block-placeholder! block #f)
  198. (placeholder-set! placeholder #t)
  199. (if exception-args
  200. (apply signal-vm-exception opcode return-value exception-args)
  201. return-value)))))
  202. (else
  203. (apply signal-vm-exception opcode reason args)))))
  204. (define-condition-type &external-exception &serious
  205. make-external-exception external-exception?)
  206. (define (block-depth block)
  207. (if block
  208. (+ 1 (block-depth (stack-block-next block)))
  209. 0))
  210. (for-each (lambda (opcode)
  211. (define-vm-exception-handler opcode uncovered-return-handler))
  212. (list (enum op call-external-value)
  213. (enum op return-from-callback)))
  214. ;----------------
  215. ; Utility for the common case of calling an imported binding.
  216. (define (call-imported-binding proc . args)
  217. (if (and (shared-binding? proc)
  218. (shared-binding-is-import? proc))
  219. (let ((value (shared-binding-ref proc)))
  220. (if (byte-vector? value)
  221. (apply call-external-value
  222. value
  223. (shared-binding-name proc)
  224. args)
  225. (apply assertion-violation 'call-imported-binding "bad procedure"
  226. proc args)))
  227. (apply assertion-violation 'call-imported-binding "procedure not defined"
  228. proc args)))
  229. (define (call-imported-binding-2 proc . args)
  230. (if (and (shared-binding? proc)
  231. (shared-binding-is-import? proc))
  232. (let ((value (shared-binding-ref proc)))
  233. (if (byte-vector? value)
  234. (apply call-external-value-2
  235. value
  236. (shared-binding-name proc)
  237. args)
  238. (apply assertion-violation 'call-imported-binding-2 "bad procedure"
  239. proc args)))
  240. (apply assertion-violation 'call-imported-binding-2 "procedure not defined"
  241. proc args)))
  242. ;----------------
  243. ; We export the record-type type so that external code can check to see if
  244. ; supposed record types really are such.
  245. (define-exported-binding "s48-the-record-type" :record-type)
  246. ;----------------
  247. ; Testing
  248. ;
  249. ; `s48_trampoline' is a C routine that calls its Scheme argument with between
  250. ; zero and three arguments. The arguments are 100, 200, and 300.
  251. ;
  252. ;(import-lambda-definition trampoline (proc nargs)
  253. ; "s48_trampoline")
  254. ;
  255. ;(define (foo . args)
  256. ; (for-each display (list "[foo " args "]"))
  257. ; (newline)
  258. ; (cons 'foo-return args))
  259. ;
  260. ;; This should return 1100.
  261. ;
  262. ;(define (test0)
  263. ; (trampoline (lambda ()
  264. ; (call-with-current-continuation
  265. ; (lambda (c)
  266. ; (trampoline (lambda (x)
  267. ; (c (+ x 1000)))
  268. ; 1))))
  269. ; 0))
  270. ;
  271. ;; ,open threads locks debug-messages
  272. ;
  273. ;(define (test1 error?)
  274. ; (let ((lock (make-lock))
  275. ; (repl-lock (make-lock)))
  276. ; (obtain-lock repl-lock)
  277. ; (spawn (lambda ()
  278. ; (obtain-lock lock)
  279. ; (debug-message "A returned "
  280. ; (trampoline (lambda ()
  281. ; (obtain-lock lock) ; we block
  282. ; 'a)
  283. ; 0))
  284. ; (release-lock repl-lock))
  285. ; 'thread-a)
  286. ; (spawn (lambda ()
  287. ; (debug-message "B returned "
  288. ; (trampoline (lambda ()
  289. ; (release-lock lock) ; A can run
  290. ; (relinquish-timeslice) ; let A run
  291. ; (if error? #f 'b))
  292. ; 0)))
  293. ; 'thread-b)
  294. ; (obtain-lock repl-lock)))