Commit | Line | Data |
---|---|---|
d90ca38d | 1 | /* Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. |
7bfd3b9e JB |
2 | * |
3 | * This program is free software; you can redistribute it and/or modify | |
4 | * it under the terms of the GNU General Public License as published by | |
5 | * the Free Software Foundation; either version 2, or (at your option) | |
6 | * any later version. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this software; see the file COPYING. If not, write to | |
82892bed JB |
15 | * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, |
16 | * Boston, MA 02111-1307 USA | |
7bfd3b9e JB |
17 | * |
18 | * As a special exception, the Free Software Foundation gives permission | |
19 | * for additional uses of the text contained in its release of GUILE. | |
20 | * | |
21 | * The exception is that, if you link the GUILE library with other files | |
22 | * to produce an executable, this does not by itself cause the | |
23 | * resulting executable to be covered by the GNU General Public License. | |
24 | * Your use of that executable is in no way restricted on account of | |
25 | * linking the GUILE library code into it. | |
26 | * | |
27 | * This exception does not however invalidate any other reasons why | |
28 | * the executable file might be covered by the GNU General Public License. | |
29 | * | |
30 | * This exception applies only to the code released by the | |
31 | * Free Software Foundation under the name GUILE. If you copy | |
32 | * code from other Free Software Foundation releases into a copy of | |
33 | * GUILE, as the General Public License permits, the exception does | |
34 | * not apply to the code that you add in this way. To avoid misleading | |
35 | * anyone as to the status of such modified files, you must delete | |
36 | * this exception notice from them. | |
37 | * | |
38 | * If you write modifications of your own for GUILE, it is your choice | |
39 | * whether to permit this exception to apply to your modifications. | |
82892bed | 40 | * If you do not wish that, delete this exception notice. */ |
7bfd3b9e JB |
41 | \f |
42 | ||
43 | #include "coop-threads.h" | |
44 | ||
45 | /* A counter of the current number of threads */ | |
46 | size_t scm_thread_count = 0; | |
47 | ||
48 | /* This is included rather than compiled separately in order | |
49 | to simplify the configuration mechanism. */ | |
50 | #include "coop.c" | |
51 | ||
52 | /* A count-down counter used to determine when to switch | |
53 | contexts */ | |
54 | size_t scm_switch_counter = SCM_THREAD_SWITCH_COUNT; | |
55 | ||
56 | coop_m scm_critical_section_mutex; | |
57 | ||
58 | #ifdef __STDC__ | |
6d71500e | 59 | static size_t |
7bfd3b9e JB |
60 | scm_threads_free_thread (SCM t) |
61 | #else | |
6d71500e | 62 | static size_t |
7bfd3b9e JB |
63 | scm_threads_free_thread (t) |
64 | SCM t; | |
65 | #endif | |
66 | { | |
67 | scm_must_free (SCM_THREAD_DATA (t)); | |
68 | return sizeof (coop_t); | |
69 | } | |
70 | ||
71 | #ifdef __STDC__ | |
6d71500e | 72 | static size_t |
7bfd3b9e JB |
73 | scm_threads_free_mutex (SCM m) |
74 | #else | |
6d71500e | 75 | static size_t |
7bfd3b9e JB |
76 | scm_threads_free_mutex (m) |
77 | SCM m; | |
78 | #endif | |
79 | { | |
80 | scm_must_free (SCM_MUTEX_DATA (m)); | |
81 | return sizeof (coop_m); | |
82 | } | |
83 | ||
84 | #ifdef __STDC__ | |
6d71500e | 85 | static size_t |
7bfd3b9e JB |
86 | scm_threads_free_condvar (SCM c) |
87 | #else | |
6d71500e | 88 | static size_t |
7bfd3b9e JB |
89 | scm_threads_free_condvar (c) |
90 | SCM c; | |
91 | #endif | |
92 | { | |
93 | scm_must_free (SCM_CONDVAR_DATA (c)); | |
94 | return sizeof (coop_c); | |
95 | } | |
96 | ||
97 | #ifdef __STDC__ | |
98 | void | |
99 | scm_threads_init (SCM_STACKITEM *i) | |
100 | #else | |
101 | void | |
102 | scm_threads_init (i) | |
103 | SCM_STACKITEM *i; | |
104 | #endif | |
105 | { | |
106 | coop_init(); | |
107 | ||
108 | scm_thread_count = 1; | |
109 | ||
110 | coop_global_main.sto = i; | |
111 | coop_global_main.base = i; | |
112 | coop_global_curr = &coop_global_main; | |
113 | coop_all_qput (&coop_global_allq, coop_global_curr); | |
114 | ||
115 | coop_mutex_init (&scm_critical_section_mutex); | |
116 | ||
117 | coop_global_main.data = 0; /* Initialized in init.c */ | |
118 | } | |
119 | ||
120 | #ifdef __STDC__ | |
121 | void | |
122 | scm_threads_mark_stacks () | |
123 | #else | |
124 | void | |
125 | scm_threads_mark_stacks () | |
126 | #endif | |
127 | { | |
128 | coop_t *thread; | |
129 | ||
130 | for (thread = coop_global_allq.t.all_next; | |
131 | thread != NULL; thread = thread->all_next) | |
132 | { | |
133 | if (thread == coop_global_curr) | |
134 | { | |
135 | /* Active thread */ | |
136 | /* stack_len is long rather than sizet in order to guarantee | |
137 | that &stack_len is long aligned */ | |
138 | #ifdef STACK_GROWS_UP | |
139 | long stack_len = ((SCM_STACKITEM *) (&thread) - | |
140 | (SCM_STACKITEM *) thread->base); | |
141 | ||
142 | /* Protect from the C stack. This must be the first marking | |
143 | * done because it provides information about what objects | |
144 | * are "in-use" by the C code. "in-use" objects are those | |
145 | * for which the values from SCM_LENGTH and SCM_CHARS must remain | |
146 | * usable. This requirement is stricter than a liveness | |
147 | * requirement -- in particular, it constrains the implementation | |
148 | * of scm_resizuve. | |
149 | */ | |
150 | SCM_FLUSH_REGISTER_WINDOWS; | |
151 | /* This assumes that all registers are saved into the jmp_buf */ | |
152 | setjmp (scm_save_regs_gc_mark); | |
153 | scm_mark_locations ((SCM_STACKITEM *) scm_save_regs_gc_mark, | |
154 | ((scm_sizet) sizeof scm_save_regs_gc_mark | |
155 | / sizeof (SCM_STACKITEM))); | |
156 | ||
157 | scm_mark_locations (((size_t) thread->base, | |
158 | (sizet) stack_len)); | |
159 | #else | |
160 | long stack_len = ((SCM_STACKITEM *) thread->base - | |
161 | (SCM_STACKITEM *) (&thread)); | |
162 | ||
163 | /* Protect from the C stack. This must be the first marking | |
164 | * done because it provides information about what objects | |
165 | * are "in-use" by the C code. "in-use" objects are those | |
166 | * for which the values from SCM_LENGTH and SCM_CHARS must remain | |
167 | * usable. This requirement is stricter than a liveness | |
168 | * requirement -- in particular, it constrains the implementation | |
169 | * of scm_resizuve. | |
170 | */ | |
171 | SCM_FLUSH_REGISTER_WINDOWS; | |
172 | /* This assumes that all registers are saved into the jmp_buf */ | |
173 | setjmp (scm_save_regs_gc_mark); | |
174 | scm_mark_locations ((SCM_STACKITEM *) scm_save_regs_gc_mark, | |
175 | ((scm_sizet) sizeof scm_save_regs_gc_mark | |
176 | / sizeof (SCM_STACKITEM))); | |
177 | ||
178 | scm_mark_locations ((SCM_STACKITEM *) &thread, | |
179 | stack_len); | |
180 | #endif | |
181 | } | |
182 | else | |
183 | { | |
184 | /* Suspended thread */ | |
185 | #ifdef STACK_GROWS_UP | |
186 | long stack_len = ((SCM_STACKITEM *) (thread->sp) - | |
187 | (SCM_STACKITEM *) thread->base); | |
188 | ||
189 | scm_mark_locations ((size_t)thread->base, | |
190 | (sizet) stack_len); | |
191 | #else | |
192 | long stack_len = ((SCM_STACKITEM *) thread->base - | |
193 | (SCM_STACKITEM *) (thread->sp)); | |
194 | ||
195 | /* Registers are already on the stack. No need to mark. */ | |
196 | ||
197 | scm_mark_locations ((SCM_STACKITEM *) (size_t)thread->sp, | |
198 | stack_len); | |
199 | #endif | |
200 | } | |
201 | ||
202 | /* Mark this thread's root */ | |
203 | scm_gc_mark (((scm_root_state *) thread->data) -> handle); | |
204 | } | |
205 | } | |
206 | ||
df366c26 MD |
207 | /* NOTE: There are TWO mechanisms for starting a thread: The first one |
208 | is used when spawning a thread from Scheme, while the second one is | |
209 | used from C. | |
210 | ||
211 | It might be argued that the first should be implemented in terms of | |
212 | the second. The reason it isn't is that that would require an | |
213 | extra unnecessary malloc (the thread_args structure). By providing | |
214 | one pair of extra functions (c_launch_thread, scm_spawn_thread) the | |
215 | Scheme threads are started more efficiently. */ | |
216 | ||
217 | /* This is the first thread spawning mechanism: threads from Scheme */ | |
218 | ||
0a1a92ab MD |
219 | typedef struct scheme_launch_data { |
220 | SCM rootcont; | |
221 | SCM body; | |
222 | SCM handler; | |
223 | } scheme_launch_data; | |
224 | ||
225 | extern SCM scm_apply (SCM, SCM, SCM); | |
226 | ||
227 | static SCM | |
39752bec | 228 | scheme_body_bootstrip (scheme_launch_data* data) |
0a1a92ab MD |
229 | { |
230 | /* First save the new root continuation */ | |
231 | data->rootcont = scm_root->rootcont; | |
232 | return scm_apply (data->body, SCM_EOL, SCM_EOL); | |
233 | } | |
234 | ||
235 | static SCM | |
236 | scheme_handler_bootstrip (scheme_launch_data* data, SCM tag, SCM throw_args) | |
237 | { | |
238 | scm_root->rootcont = data->rootcont; | |
239 | return scm_apply (data->handler, scm_cons (tag, throw_args), SCM_EOL); | |
240 | } | |
241 | ||
df366c26 MD |
242 | static void |
243 | scheme_launch_thread (void *p) | |
7bfd3b9e JB |
244 | { |
245 | /* The thread object will be GC protected by being a member of the | |
246 | list given as argument to launch_thread. It will be marked | |
247 | during the conservative sweep of the stack. */ | |
0a1a92ab MD |
248 | register SCM argl = (SCM) p; |
249 | SCM thread = SCM_CAR (argl); | |
250 | scheme_launch_data data; | |
251 | data.rootcont = SCM_BOOL_F; | |
252 | data.body = SCM_CADR (argl); | |
253 | data.handler = SCM_CADDR (argl); | |
254 | scm_internal_cwdr ((scm_catch_body_t) scheme_body_bootstrip, | |
255 | &data, | |
256 | (scm_catch_handler_t) scheme_handler_bootstrip, | |
257 | &data, | |
258 | &thread); | |
7bfd3b9e | 259 | scm_thread_count--; |
0a1a92ab | 260 | SCM_DEFER_INTS; |
7bfd3b9e JB |
261 | } |
262 | ||
263 | #ifdef __STDC__ | |
264 | SCM | |
265 | scm_call_with_new_thread (SCM argl) | |
266 | #else | |
267 | SCM | |
268 | scm_call_with_new_thread (argl) | |
269 | SCM argl; | |
270 | #endif | |
271 | { | |
272 | SCM thread; | |
273 | ||
274 | /* Check arguments. */ | |
275 | { | |
276 | register SCM args = argl; | |
277 | SCM thunk, handler; | |
0824b524 MD |
278 | SCM_ASSERT (SCM_NIMP (args), |
279 | scm_makfrom0str (s_call_with_new_thread), | |
280 | SCM_WNA, NULL); | |
7bfd3b9e JB |
281 | thunk = SCM_CAR (args); |
282 | SCM_ASSERT (SCM_NFALSEP (scm_thunk_p (thunk)), | |
283 | thunk, | |
284 | SCM_ARG1, | |
285 | s_call_with_new_thread); | |
286 | args = SCM_CDR (args); | |
0824b524 MD |
287 | SCM_ASSERT (SCM_NIMP (args), |
288 | scm_makfrom0str (s_call_with_new_thread), | |
289 | SCM_WNA, NULL); | |
7bfd3b9e JB |
290 | handler = SCM_CAR (args); |
291 | SCM_ASSERT (SCM_NFALSEP (scm_procedure_p (handler)), | |
292 | handler, | |
293 | SCM_ARG2, | |
294 | s_call_with_new_thread); | |
0824b524 MD |
295 | SCM_ASSERT (SCM_NULLP (SCM_CDR (args)), |
296 | scm_makfrom0str (s_call_with_new_thread), | |
297 | SCM_WNA, NULL); | |
7bfd3b9e JB |
298 | } |
299 | ||
300 | /* Make new thread. */ | |
301 | { | |
302 | coop_t *t; | |
303 | SCM root, old_winds; | |
304 | ||
305 | /* Unwind wind chain. */ | |
306 | old_winds = scm_dynwinds; | |
307 | scm_dowinds (SCM_EOL, scm_ilength (scm_root->dynwinds)); | |
308 | ||
309 | /* Allocate thread locals. */ | |
310 | root = scm_make_root (scm_root->handle); | |
311 | /* Make thread. */ | |
312 | SCM_NEWCELL (thread); | |
313 | SCM_DEFER_INTS; | |
314 | SCM_SETCAR (thread, scm_tc16_thread); | |
315 | argl = scm_cons (thread, argl); | |
0a1a92ab MD |
316 | /* Note that we couldn't pass a pointer to argl as data since the |
317 | argl variable may not exist in memory when the thread starts. */ | |
df366c26 | 318 | t = coop_create (scheme_launch_thread, (void *) argl); |
7bfd3b9e JB |
319 | t->data = SCM_ROOT_STATE (root); |
320 | SCM_SETCDR (thread, t); | |
321 | scm_thread_count++; | |
322 | /* Note that the following statement also could cause coop_yield.*/ | |
323 | SCM_ALLOW_INTS; | |
324 | ||
325 | /* We're now ready for the thread to begin. */ | |
326 | coop_yield(); | |
327 | ||
328 | /* Return to old dynamic context. */ | |
329 | scm_dowinds (old_winds, - scm_ilength (old_winds)); | |
330 | } | |
331 | ||
332 | return thread; | |
333 | } | |
334 | ||
df366c26 MD |
335 | /* This is the second thread spawning mechanism: threads from C */ |
336 | ||
0a1a92ab MD |
337 | typedef struct c_launch_data { |
338 | union { | |
339 | SCM thread; | |
340 | SCM rootcont; | |
341 | } u; | |
df366c26 MD |
342 | scm_catch_body_t body; |
343 | void *body_data; | |
344 | scm_catch_handler_t handler; | |
345 | void *handler_data; | |
0a1a92ab MD |
346 | } c_launch_data; |
347 | ||
348 | static SCM | |
39752bec | 349 | c_body_bootstrip (c_launch_data* data) |
0a1a92ab MD |
350 | { |
351 | /* First save the new root continuation */ | |
352 | data->u.rootcont = scm_root->rootcont; | |
39752bec | 353 | return (data->body) (data->body_data); |
0a1a92ab MD |
354 | } |
355 | ||
356 | static SCM | |
357 | c_handler_bootstrip (c_launch_data* data, SCM tag, SCM throw_args) | |
358 | { | |
359 | scm_root->rootcont = data->u.rootcont; | |
360 | return (data->handler) (data->handler_data, tag, throw_args); | |
361 | } | |
df366c26 MD |
362 | |
363 | static void | |
364 | c_launch_thread (void *p) | |
365 | { | |
0a1a92ab | 366 | register c_launch_data *data = (c_launch_data *) p; |
df366c26 | 367 | /* The thread object will be GC protected by being on this stack */ |
0a1a92ab | 368 | SCM thread = data->u.thread; |
df366c26 MD |
369 | /* We must use the address of `thread', otherwise the compiler will |
370 | optimize it away. This is OK since the longest SCM_STACKITEM | |
371 | also is a long. */ | |
0a1a92ab MD |
372 | scm_internal_cwdr ((scm_catch_body_t) c_body_bootstrip, |
373 | data, | |
374 | (scm_catch_handler_t) c_handler_bootstrip, | |
375 | data, | |
df366c26 MD |
376 | &thread); |
377 | scm_thread_count--; | |
0a1a92ab | 378 | scm_must_free ((char *) data); |
df366c26 MD |
379 | } |
380 | ||
381 | SCM | |
382 | scm_spawn_thread (scm_catch_body_t body, void *body_data, | |
383 | scm_catch_handler_t handler, void *handler_data) | |
384 | { | |
385 | SCM thread; | |
386 | coop_t *t; | |
387 | SCM root, old_winds; | |
0a1a92ab MD |
388 | c_launch_data *data = (c_launch_data *) scm_must_malloc (sizeof (*data), |
389 | "scm_spawn_thread"); | |
df366c26 MD |
390 | |
391 | /* Unwind wind chain. */ | |
392 | old_winds = scm_dynwinds; | |
393 | scm_dowinds (SCM_EOL, scm_ilength (scm_root->dynwinds)); | |
394 | ||
395 | /* Allocate thread locals. */ | |
396 | root = scm_make_root (scm_root->handle); | |
397 | /* Make thread. */ | |
398 | SCM_NEWCELL (thread); | |
399 | SCM_DEFER_INTS; | |
400 | SCM_SETCAR (thread, scm_tc16_thread); | |
401 | ||
0a1a92ab MD |
402 | data->u.thread = thread; |
403 | data->body = body; | |
404 | data->body_data = body_data; | |
405 | data->handler = handler; | |
406 | data->handler_data = handler_data; | |
df366c26 | 407 | |
0a1a92ab | 408 | t = coop_create (c_launch_thread, (void *) data); |
df366c26 MD |
409 | |
410 | t->data = SCM_ROOT_STATE (root); | |
411 | SCM_SETCDR (thread, t); | |
412 | scm_thread_count++; | |
413 | /* Note that the following statement also could cause coop_yield.*/ | |
414 | SCM_ALLOW_INTS; | |
415 | ||
416 | /* We're now ready for the thread to begin. */ | |
417 | coop_yield(); | |
418 | ||
419 | /* Return to old dynamic context. */ | |
420 | scm_dowinds (old_winds, - scm_ilength (old_winds)); | |
421 | ||
422 | return thread; | |
423 | } | |
424 | ||
7bfd3b9e JB |
425 | #ifdef __STDC__ |
426 | SCM | |
427 | scm_join_thread (SCM t) | |
428 | #else | |
429 | SCM | |
430 | scm_join_thread (t) | |
431 | SCM t; | |
432 | #endif | |
433 | { | |
434 | SCM_ASSERT (SCM_NIMP (t) && SCM_THREADP (t), t, SCM_ARG1, s_join_thread); | |
435 | coop_join (SCM_THREAD_DATA (t)); | |
436 | return SCM_BOOL_T; | |
437 | } | |
438 | ||
439 | #ifdef __STDC__ | |
440 | SCM | |
441 | scm_yield () | |
442 | #else | |
443 | SCM | |
444 | scm_yield () | |
445 | #endif | |
446 | { | |
447 | /* Yield early */ | |
448 | scm_switch_counter = SCM_THREAD_SWITCH_COUNT; | |
449 | coop_yield(); | |
450 | ||
451 | return SCM_BOOL_T; | |
452 | } | |
453 | ||
454 | #ifdef __STDC__ | |
455 | SCM | |
456 | scm_single_thread_p () | |
457 | #else | |
458 | SCM | |
459 | scm_single_thread_p () | |
460 | #endif | |
461 | { | |
462 | return (coop_global_runq.tail == &coop_global_runq.t | |
463 | ? SCM_BOOL_T | |
464 | : SCM_BOOL_F); | |
465 | } | |
466 | ||
467 | #ifdef __STDC__ | |
468 | SCM | |
469 | scm_make_mutex () | |
470 | #else | |
471 | SCM | |
472 | scm_make_mutex () | |
473 | #endif | |
474 | { | |
475 | SCM m; | |
476 | coop_m *data = (coop_m *) scm_must_malloc (sizeof (coop_m), "mutex"); | |
477 | SCM_NEWCELL (m); | |
478 | SCM_DEFER_INTS; | |
479 | SCM_SETCAR (m, scm_tc16_mutex); | |
480 | SCM_SETCDR (m, data); | |
481 | SCM_ALLOW_INTS; | |
482 | coop_mutex_init (data); | |
483 | return m; | |
484 | } | |
485 | ||
486 | #ifdef __STDC__ | |
487 | SCM | |
488 | scm_lock_mutex (SCM m) | |
489 | #else | |
490 | SCM | |
491 | scm_lock_mutex (m) | |
492 | SCM m; | |
493 | #endif | |
494 | { | |
495 | SCM_ASSERT (SCM_NIMP (m) && SCM_MUTEXP (m), m, SCM_ARG1, s_lock_mutex); | |
496 | coop_mutex_lock (SCM_MUTEX_DATA (m)); | |
497 | return SCM_BOOL_T; | |
498 | } | |
499 | ||
500 | #ifdef __STDC__ | |
501 | SCM | |
502 | scm_unlock_mutex (SCM m) | |
503 | #else | |
504 | SCM | |
505 | scm_unlock_mutex (m) | |
506 | SCM m; | |
507 | #endif | |
508 | { | |
509 | SCM_ASSERT (SCM_NIMP (m) && SCM_MUTEXP (m), m, SCM_ARG1, s_unlock_mutex); | |
510 | coop_mutex_unlock(SCM_MUTEX_DATA (m)); | |
511 | ||
512 | /* Yield early */ | |
513 | scm_switch_counter = SCM_THREAD_SWITCH_COUNT; | |
514 | coop_yield(); | |
515 | ||
516 | return SCM_BOOL_T; | |
517 | } | |
518 | ||
519 | #ifdef __STDC__ | |
520 | SCM | |
521 | scm_make_condition_variable () | |
522 | #else | |
523 | SCM | |
524 | scm_make_condition_variable () | |
525 | #endif | |
526 | { | |
527 | SCM c; | |
528 | coop_c *data = (coop_c *) scm_must_malloc (sizeof (coop_c), "condvar"); | |
529 | SCM_NEWCELL (c); | |
530 | SCM_DEFER_INTS; | |
531 | SCM_SETCAR (c, scm_tc16_condvar); | |
532 | SCM_SETCDR (c, data); | |
533 | SCM_ALLOW_INTS; | |
534 | coop_condition_variable_init (SCM_CONDVAR_DATA (c)); | |
535 | return c; | |
536 | } | |
537 | ||
538 | #ifdef __STDC__ | |
539 | SCM | |
540 | scm_wait_condition_variable (SCM c, SCM m) | |
541 | #else | |
542 | SCM | |
543 | scm_wait_condition_variable (c, m) | |
544 | SCM c; | |
545 | SCM m; | |
546 | #endif | |
547 | { | |
548 | SCM_ASSERT (SCM_NIMP (c) && SCM_CONDVARP (c), | |
549 | c, | |
550 | SCM_ARG1, | |
551 | s_wait_condition_variable); | |
552 | SCM_ASSERT (SCM_NIMP (m) && SCM_MUTEXP (m), | |
553 | m, | |
554 | SCM_ARG2, | |
555 | s_wait_condition_variable); | |
c8bf4ecd MD |
556 | coop_condition_variable_wait_mutex (SCM_CONDVAR_DATA (c), |
557 | SCM_MUTEX_DATA (m)); | |
7bfd3b9e JB |
558 | return SCM_BOOL_T; |
559 | } | |
560 | ||
561 | #ifdef __STDC__ | |
562 | SCM | |
563 | scm_signal_condition_variable (SCM c) | |
564 | #else | |
565 | SCM | |
566 | scm_signal_condition_variable (c) | |
567 | SCM c; | |
568 | #endif | |
569 | { | |
570 | SCM_ASSERT (SCM_NIMP (c) && SCM_CONDVARP (c), | |
571 | c, | |
572 | SCM_ARG1, | |
573 | s_signal_condition_variable); | |
574 | coop_condition_variable_signal (SCM_CONDVAR_DATA (c)); | |
575 | return SCM_BOOL_T; | |
576 | } |