This file is indexed.

/usr/share/scheme48-1.9/big/callback.scm is in scheme48 1.9-5.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
; Part of Scheme 48 1.9.  See file COPYING for notices and license.

; Authors: Richard Kelsey, Jonathan Rees, Mike Sperber, Marcus Crestani, Robert Ransom,
; Harald Glab-Phlak


; This code, along with C code in c/external.c, handles the interaction between
; callbacks from external code to Scheme functions and uses of continuations in
; Scheme.  The problem is that Scheme 48 uses multiple continuations while
; operating with only one process stack.
;
; Suppose we have Scheme procedures s1 and s2 and C procedure c1 such that
; s1 calls c1 and c1 calls s2.  There are two trampoline functions that are
; used to do this.  The VM uses s48_external_call to call c1 and c1 uses
; s48_call_scheme to start the VM running s2.  While in s2 the process stack will
; look like this:
;
;  <C frame for VM running s2>
;  <C frame for s48_call_scheme>
;  <C frame for c1>
;  <C frame for s48_external_call>
;  <C frame for VM running s1>
;  <base>
;
; The C code in c/external.scm keeps a record of the portions of the process
; stack that are running external code.  Each of these stack portions has an
; s48_external_call frame at the base and an s48_call_scheme frame at the top.
; The stack is represented as linked list of records, called `stack-block's,
; each of which contains the following values:
;    free?       ; true if this frame is no longer needed
;    unwind      ; the longjmp target used to skip over this frame
;    proc-name   ; the name of the procedure this block is executing
;    placeholder ; either #f or a placeholder, see the section on threads below
;    next        ; the next stack-block below this one
; These are Scheme records and are traced by the GC.

(define-record-type stack-block :stack-block
  (stack-blocks-are-made-from-c)
  stack-block?
  (free? stack-block-free? set-stack-block-free?!)
  (unwind stack-block-unwind)
  (proc-name stack-block-proc-name)
  (placeholder stack-block-placeholder set-stack-block-placeholder!)
  (next stack-block-next))

; Stack-blocks are made from C, so we need to export the type.

(define-exported-binding "s48-stack-block-type" :stack-block)

; There is no need to keep track of the VM frames.  These are all interchangeable
; because 1) the VM's state is kept in top-level variables and 2) we have
; arranged it so that the relevant VM opcodes, call-external-value and
; return-from-callback, are all the same length and are always immediately
; followed by a return instruction.  s48_call_scheme can safely overwrite the
; template and code-pointer registers in the VM as they always point to a
; one-byte instruction followed by a return instruction.  When the VM returns
; from the callback, via a return-from-callback instruction, that too is a
; one-byte instruction followed by a return instruction.  The VM can proceed,
; happily ignorant of all this fooling around.
;
; On entry, s48_external_call saves a longjump target.  This is used when
; raising exceptions from with the external code and for unwinding the process
; stack.  Each invocation of s48_call_scheme creates a new stack-block, saving
; within it the longjump target of the corresponding s48_external_call.  `Free?'
; and `placeholder' are initially false and `next' points to existing list of
; stack-blocks.
;
; When a callback returns to s48_call_scheme, the corresponding block is popped
; off the list of stack-blocks.
;
; So far so good, and if that were all that happened there would be no need for
; all this mechanism.  There are two problems: call/cc and threads.  Call/cc is
; simpler to deal with.  We have downward continuations in C, as implemented
; by longjmp(), so we simply limit continuations that cross callbacks to being
; downwards only.  We also need to arrange for any jumped-over stack portions
; to be popped off of the stack.
;
; The popping off is handled by s48_external_call.  Just before returning to the
; VM it checks to see if the top stack-block is free.  If so, it loops through
; the list of stack-blocks to find the first non-free stack portion.  A longjump
; is performed to the target in the last free block, removing any unneeded frames
; from the stack.
;
; s48_call_scheme starts the VM running the following CALLBACK procedure.  The
; arguments are BLOCK, the stack-block just created for this callback, and
; the procedure and arguments for the actual callback.  It prevents jumps back
; into the callback and frees BLOCK if a throw out occurs.
;
; We disable interrupts to ensure that nothing intervenes between setting DONE?
; and returning from the callback.  BLOCK is then either freed or returned to,
; but not both or neither.  RETURN-FROM-CALLBACK reenables interrupts.

(define (callback block proc . args)
  (let ((done? #f))
    (return-from-callback block
			  (dynamic-wind
			   (lambda ()
			     (if done?
				 (apply
				  assertion-violation 'callback
				  "attempt to throw into a callback"
				  (cons proc args))))
			   (lambda ()
			     (let ((result (apply proc args)))
			       (disable-interrupts!)
			       (set! done? #t)
			       result))
			   (lambda ()
			     (if (not done?)
				 (begin
				   (set! done? #t)
				   (set-stack-block-free?! block #t)
				   (clear-stack-top!))))))))

(define-exported-binding "s48-callback" callback)

; CLEAR-STACK-TOP! is an empty C procedure.  When it returns, s48_external_call
; will automatically clear any free frames off of the stack.

(import-lambda-definition clear-stack-top! () "s48_clear_stack_top")

; Dealing with threads.
;
; The difficulty here is that each stack-block belongs to some thread.  Thread A
; can call a C procedure which calls back into Scheme.  At that point a context
; switch occurs and we start running thread B, which promptly does the same
; calls.  The process stack then looks like this:
; 
;  <C frame for VM running B1>
;  <C frame for s48_call_scheme>
;  <C frame for B's C code>
;  <C frame for s48_external_call>
;  <C frame for VM running A1 and then B0>
;  <C frame for s48_call_scheme>
;  <C frame for A's C code>
;  <C frame for s48_external_call>
;  <C frame for VM running A0>
;  <base>
;
; At this point A cannot return from its callback before B does, because B's
; portion of the process stack is above A's.  If A does try to return it must
; block until it again is at the top of the stack.
;
; This is handled by s48_call_scheme, which checks to see if the stack-block
; being returned to is at the top of the stack.  If not, it does a second
; callback to DELAY-CALLBACK-RETURN, defined below, with the same stack-block.
; DELAY-CALLBACK-RETURN creates a placeholder, puts it in the stack-block, and
; then blocks on it.  When the placeholder gets a value the procedure attempts
; another return-from-callback.
;
; This is called with interrupts disabled, as we need to avoid having BLOCK
; reach the top of the stack before the placeholder is installed.

(define (delay-callback-return block value)
  (let ((placeholder (make-placeholder)))
    (set-stack-block-placeholder! block placeholder)
    (enable-interrupts!)
    (placeholder-value placeholder)
    value))

(define-exported-binding "s48-delay-callback-return" delay-callback-return)

; Finally, s48_external_call looks to see if the top stack-block has a
; placeholder.  If it does, it raises an exception instead of doing a normal
; return.  The exception handler sets the placeholder's value, allowing the
; blocked thread to continue.  The handler then returns the external call's
; value to its own thread, or, if the callback-return-uncovered is piggybacked
; on another exception, we raise that exception.
;
; Because of the all of the games played above, the callback-return-uncovered
; exception may appear to have come from either the call-external-value, or
; return-from-callback opcodes.

(define uncovered-return-handler
  (lambda (opcode reason . args)

    (define (blow-up con extract-message)
      ;; look at external.c for why this is all so strangely reversed
      (let ((rev (reverse args)))
	(raise
	 (condition
	 con
	 (make-external-exception)
	 (make-who-condition (cadr rev))
	 (make-message-condition
	  (os-string->string (byte-vector->os-string (extract-message (car rev)))))
	 (make-irritants-condition (reverse (cddr rev)))))))

    (enum-case exception reason
	       ((external-error)
		(blow-up (make-error) values))
	       ((external-assertion-violation)
		(blow-up (make-assertion-violation) values))
	       ((external-os-error)
		(blow-up (make-error) os-error-message))
	       ((out-of-memory)
		(raise
		 (condition
		  (make-implementation-restriction-violation)
		  (make-who-condition 'call-external-value)
		  (make-message-condition "out of memory"))))
	       ((callback-return-uncovered)
		(call-with-values
		    (lambda ()
		      (if (= 2 (length args))
			  (values (car args)
				  (cadr args)
				  #f)
			  (let ((args (reverse args)))
			    (values (car args)
				    (cadr args)
				    (reverse (cddr args))))))
		  (lambda (block return-value exception-args)
		    (let ((placeholder (stack-block-placeholder block)))
		      (set-stack-block-placeholder! block #f)
		      (placeholder-set! placeholder #t)
		      (if exception-args
			  (apply signal-vm-exception opcode return-value exception-args)
			  return-value)))))
	       (else
		(apply signal-vm-exception opcode reason args)))))

(define-condition-type &external-exception &serious
  make-external-exception external-exception?)

(define (block-depth block)
  (if block
      (+ 1 (block-depth (stack-block-next block)))
      0))

(for-each (lambda (opcode)
	    (define-vm-exception-handler opcode uncovered-return-handler))
	  (list (enum op call-external-value)
		(enum op return-from-callback)))

;----------------
; Utility for the common case of calling an imported binding.

(define (call-imported-binding proc . args)
  (if (and (shared-binding? proc)
	   (shared-binding-is-import? proc))
      (let ((value (shared-binding-ref proc)))
	(if (byte-vector? value)
	    (apply call-external-value
		   value
		   (shared-binding-name proc)
		   args)
	    (apply assertion-violation 'call-imported-binding "bad procedure"
		   proc args)))
      (apply assertion-violation 'call-imported-binding "procedure not defined"
	     proc args)))

(define (call-imported-binding-2 proc . args)
  (if (and (shared-binding? proc)
	   (shared-binding-is-import? proc))
      (let ((value (shared-binding-ref proc)))
	(if (byte-vector? value)
	    (apply call-external-value-2
		   value
		   (shared-binding-name proc)
		   args)
	    (apply assertion-violation 'call-imported-binding-2 "bad procedure"
		   proc args)))
      (apply assertion-violation 'call-imported-binding-2 "procedure not defined"
	     proc args)))

;----------------
; We export the record-type type so that external code can check to see if
; supposed record types really are such.

(define-exported-binding "s48-the-record-type" :record-type)

;----------------
; Testing
;
; `s48_trampoline' is a C routine that calls its Scheme argument with between
; zero and three arguments.  The arguments are 100, 200, and 300.
;
;(import-lambda-definition trampoline (proc nargs)
;                          "s48_trampoline")
;
;(define (foo . args)
;  (for-each display (list "[foo " args "]"))
;  (newline)
;  (cons 'foo-return args))
;
;; This should return 1100.
;
;(define (test0)
;  (trampoline (lambda ()
;                (call-with-current-continuation
;                  (lambda (c)
;                    (trampoline (lambda (x)
;                                  (c (+ x 1000)))
;                                1))))
;              0))
;
;; ,open threads locks debug-messages
;
;(define (test1 error?)
;  (let ((lock (make-lock))
;        (repl-lock (make-lock)))
;    (obtain-lock repl-lock)
;    (spawn (lambda ()
;             (obtain-lock lock)
;             (debug-message "A returned "
;                           (trampoline (lambda ()
;                                         (obtain-lock lock) ; we block
;                                         'a)
;                                       0))
;             (release-lock repl-lock))
;           'thread-a)
;    (spawn (lambda ()
;             (debug-message "B returned "
;                           (trampoline (lambda ()
;                                         (release-lock lock)    ; A can run
;                                         (relinquish-timeslice) ; let A run
;                                         (if error? #f 'b))
;                                       0)))
;           'thread-b)
;    (obtain-lock repl-lock)))