1 /*
2 * weak.c - weak vectors and tables
3 *
4 * Copyright (c) 2000-2020 Shiro Kawai <shiro@acm.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the authors nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34 #define LIBGAUCHE_BODY
35 #include "gauche.h"
36
37 /*=============================================================
38 * Weak vector
39 *
40 * A weak vector is like a vector of Scheme objects, except
41 * it doesn't prevent the referenced objects to be garbage-collected.
42 * Internally, it is implemented using "disappearing link" feature
43 * of Boehm GC; when the referenced object is collected, the pointer
44 * in the vector is set to NULL.
45 * It is important to keep track of whether the entry of the vector
46 * is registered as a disappearing link or not, for you can't register
47 * the same location more than once.
48 */
49
weakvector_print(ScmObj obj,ScmPort * port,ScmWriteContext * ctx)50 static void weakvector_print(ScmObj obj, ScmPort *port, ScmWriteContext *ctx)
51 {
52 ScmWeakVector *v = SCM_WEAK_VECTOR(obj);
53 ScmObj *ptrs = (ScmObj*)v->pointers;
54 Scm_Printf(port, "#,(<weak-vector> %d", v->size);
55 for (ScmSmallInt i=0; i<v->size; i++) {
56 SCM_PUTC(' ', port);
57 if (ptrs[i]) {
58 Scm_Write(ptrs[i], SCM_OBJ(port), Scm_WriteContextMode(ctx));
59 } else {
60 Scm_Write(SCM_FALSE, SCM_OBJ(port), Scm_WriteContextMode(ctx));
61 }
62 }
63 SCM_PUTC(')', port);
64 }
65
weakvector_finalize(ScmObj obj,void * data SCM_UNUSED)66 static void weakvector_finalize(ScmObj obj, void *data SCM_UNUSED)
67 {
68 ScmWeakVector *v = SCM_WEAK_VECTOR(obj);
69 ScmObj *p = (ScmObj*)v->pointers;
70 for (ScmSmallInt i=0; i<v->size; i++) {
71 if (p[i]==NULL || SCM_PTRP(p[i])) {
72 GC_unregister_disappearing_link((void **)&p[i]);
73 }
74 p[i] = SCM_FALSE; /* safety */
75 }
76 }
77
78 SCM_DEFINE_BUILTIN_CLASS(Scm_WeakVectorClass, weakvector_print,
79 NULL, NULL, NULL,
80 SCM_CLASS_SEQUENCE_CPL);
81
Scm_MakeWeakVector(ScmSmallInt size)82 ScmObj Scm_MakeWeakVector(ScmSmallInt size)
83 {
84 ScmWeakVector *v = SCM_NEW(ScmWeakVector);
85
86 SCM_SET_CLASS(v, SCM_CLASS_WEAK_VECTOR);
87 v->size = size;
88 /* Allocate pointer array by ATOMIC, so that GC won't trace the
89 pointers in it. */
90 ScmObj *p = SCM_NEW_ATOMIC2(ScmObj*, size * sizeof(ScmObj));
91 for (ScmSmallInt i=0; i<size; i++) p[i] = SCM_FALSE;
92 v->pointers = (void*)p;
93 Scm_RegisterFinalizer(SCM_OBJ(v), weakvector_finalize, NULL);
94 return SCM_OBJ(v);
95 }
96
Scm_WeakVectorRef(ScmWeakVector * v,ScmSmallInt index,ScmObj fallback)97 ScmObj Scm_WeakVectorRef(ScmWeakVector *v, ScmSmallInt index, ScmObj fallback)
98 {
99 if (index < 0 || index >= v->size) {
100 if (SCM_UNBOUNDP(fallback)) {
101 Scm_Error("argument out of range: %ld", index);
102 } else {
103 return fallback;
104 }
105 }
106 ScmObj *p = (ScmObj*)v->pointers;
107 if (p[index] == NULL) {
108 if (SCM_UNBOUNDP(fallback)) return SCM_FALSE;
109 else return fallback;
110 } else {
111 return p[index];
112 }
113 }
114
Scm_WeakVectorSet(ScmWeakVector * v,ScmSmallInt index,ScmObj value)115 ScmObj Scm_WeakVectorSet(ScmWeakVector *v, ScmSmallInt index, ScmObj value)
116 {
117 if (index < 0 || index >= v->size) {
118 Scm_Error("argument out of range: %ld", index);
119 }
120 ScmObj *p = (ScmObj*)v->pointers;
121
122 /* unregister the location if it was registered before */
123 if (p[index] == NULL || SCM_PTRP(p[index])) {
124 GC_unregister_disappearing_link((void **)&p[index]);
125 }
126
127 p[index] = value;
128 /* register the location if the value is a heap object */
129 if (SCM_PTRP(value)) {
130 GC_general_register_disappearing_link((void **)&p[index], (void *)value);
131 }
132 return SCM_UNDEFINED;
133 }
134
135 /*=============================================================
136 * Weak box
137 */
138
139 /* Weak box is not an ScmObj. It provides a packaged 'weak pointer'
140 feature to C. */
141
142 /* ptr points to the target object weakly.
143 Registered flag becomes TRUE whenever ptr points to a GC_malloced object,
144 thus &wbox->ptr is registered as a disappearing link.
145 Note that we can distinguish a box that containing NULL pointer, and
146 a box whose target has been GCed and hence ptr is cleared---in the
147 former case registered is FALSE, while in the latter case it is TRUE. */
148 struct ScmWeakBoxRec {
149 void *ptr;
150 int registered;
151 };
152
wbox_setvalue(ScmWeakBox * wbox,void * value)153 static void wbox_setvalue(ScmWeakBox *wbox, void *value)
154 {
155 void *base = GC_base((void *)value);
156 wbox->ptr = value;
157 if (base != NULL) {
158 GC_general_register_disappearing_link((void *)&wbox->ptr, base);
159 wbox->registered = TRUE;
160 } else {
161 wbox->registered = FALSE;
162 }
163 }
164
165
Scm_MakeWeakBox(void * value)166 ScmWeakBox *Scm_MakeWeakBox(void *value)
167 {
168 ScmWeakBox *wbox = SCM_NEW_ATOMIC(ScmWeakBox);
169 wbox_setvalue(wbox, value);
170 return wbox;
171 }
172
Scm_WeakBoxEmptyP(ScmWeakBox * wbox)173 int Scm_WeakBoxEmptyP(ScmWeakBox *wbox)
174 {
175 return (wbox->registered && wbox->ptr == NULL);
176 }
177
Scm_WeakBoxSet(ScmWeakBox * wbox,void * newvalue)178 void Scm_WeakBoxSet(ScmWeakBox *wbox, void *newvalue)
179 {
180 if (wbox->registered) {
181 GC_unregister_disappearing_link((void *)&wbox->ptr);
182 wbox->registered = FALSE;
183 }
184 wbox_setvalue(wbox, newvalue);
185 }
186
Scm_WeakBoxRef(ScmWeakBox * wbox)187 void *Scm_WeakBoxRef(ScmWeakBox *wbox)
188 {
189 return wbox->ptr; /* NB: if NULL is retured, you can't know
190 whether box has been containing NULL or
191 the target is GCed. You have to call
192 Scm_WeakBoxEmptyP to check that.
193 IMPORTANT: If you call EmptyP before
194 calling Ref, there is a hazard that the
195 target is GCed between the two calls.
196 ALWAYS call Ref first and keep the
197 result in the register, so that it won't
198 be GCed. */
199 }
200
201 /*=============================================================
202 * Weak Hash Table
203 */
204
205 /* The table can be created with weak key (key can be GC-ed), weak value
206 * (value can be GC-ed), or weak key&value (both key and value can be
207 * GC-ed).
208 *
209 * If a value is GC-ed, the entry returns the default value specified
210 * at the hash table creation time.
211 *
212 * If a key is GC-ed, the entry becomes inaccessible---from outside it
213 * looks as if the entry is deleted. We don't immediately delete the entry
214 * at the time we found its key has been GC-ed, since the caller may not
215 * expect the table is modified. Instead we flag the table and delete
216 * those entries when the table is modified.
217 */
218
219 #define MARK_GONE_ENTRY(ht, e) (ht->goneEntries++)
220
221
weakhash_print(ScmObj obj,ScmPort * port,ScmWriteContext * ctx SCM_UNUSED)222 static void weakhash_print(ScmObj obj, ScmPort *port,
223 ScmWriteContext *ctx SCM_UNUSED)
224 {
225 ScmWeakHashTable *ht = SCM_WEAK_HASH_TABLE(obj);
226 char *type = "";
227
228 switch (ht->type) {
229 case SCM_HASH_EQ: type = "eq?"; break;
230 case SCM_HASH_EQV: type = "eqv?"; break;
231 case SCM_HASH_EQUAL: type = "equal?"; break;
232 case SCM_HASH_STRING: type = "string=?"; break;
233 case SCM_HASH_GENERAL: type = "general"; break;
234 default: Scm_Panic("something wrong with a hash table");
235 }
236 /* should we also print weakness info? */
237 Scm_Printf(port, "#<weak-hash-table %s %p>", type, ht);
238 }
239
240 SCM_DEFINE_BUILTIN_CLASS(Scm_WeakHashTableClass, weakhash_print,
241 NULL, NULL, NULL,
242 SCM_CLASS_DICTIONARY_CPL);
243
244 /* Custom hasher & comparer for key-weak table, in which we insert
245 one indirection to the real key via WeakBox. */
weak_key_hash(const ScmHashCore * hc,intptr_t key)246 static u_long weak_key_hash(const ScmHashCore *hc, intptr_t key)
247 {
248 ScmWeakHashTable *wh = SCM_WEAK_HASH_TABLE(hc->data);
249 ScmWeakBox *box = (ScmWeakBox *)key;
250 intptr_t realkey = (intptr_t)Scm_WeakBoxRef(box);
251 if (Scm_WeakBoxEmptyP(box)) {
252 /* There IS a small possibility that the real key has already been
253 GCed. We return an arbitrary value (0 here); the entry won't
254 match anyway. */
255 return 0;
256 } else {
257 u_long k= wh->hashfn(hc, realkey);
258 return k;
259 }
260 }
261
262
weak_key_compare(const ScmHashCore * hc,intptr_t key,intptr_t entrykey)263 static int weak_key_compare(const ScmHashCore *hc, intptr_t key,
264 intptr_t entrykey)
265 {
266 ScmWeakHashTable *wh = SCM_WEAK_HASH_TABLE(hc->data);
267 ScmWeakBox *box = (ScmWeakBox *)entrykey;
268 intptr_t realkey = (intptr_t)Scm_WeakBoxRef(box);
269 if (Scm_WeakBoxEmptyP(box)) {
270 return FALSE;
271 } else {
272 return wh->cmpfn(hc, key, realkey);
273 }
274 }
275
276 /* Scan through */
277 #if 0
278 static void weak_hash_cleanup(ScmWeakHashTable *wh)
279 {
280 }
281 #endif
282
283
Scm_MakeWeakHashTableSimple(ScmHashType type,ScmWeakness weakness,int initSize,ScmObj defaultValue)284 ScmObj Scm_MakeWeakHashTableSimple(ScmHashType type,
285 ScmWeakness weakness,
286 int initSize,
287 ScmObj defaultValue)
288 {
289 ScmWeakHashTable *wh = SCM_NEW(ScmWeakHashTable);
290 SCM_SET_CLASS(wh, SCM_CLASS_WEAK_HASH_TABLE);
291 wh->weakness = weakness;
292 wh->type = type;
293 wh->defaultValue = defaultValue;
294 wh->goneEntries = 0;
295
296 if (weakness & SCM_WEAK_KEY) {
297 if (!Scm_HashCoreTypeToProcs(type, &wh->hashfn, &wh->cmpfn)) {
298 Scm_Error("[internal error] Scm_MakeWeakHashTableSimple: unsupported type: %d", type);
299 }
300 Scm_HashCoreInitGeneral(&wh->core, weak_key_hash, weak_key_compare,
301 initSize, wh);
302 } else {
303 Scm_HashCoreInitSimple(&wh->core, type, initSize, wh);
304 }
305 return SCM_OBJ(wh);
306 }
307
Scm_WeakHashTableCopy(ScmWeakHashTable * src)308 ScmObj Scm_WeakHashTableCopy(ScmWeakHashTable *src)
309 {
310 ScmWeakHashTable *wh = SCM_NEW(ScmWeakHashTable);
311 SCM_SET_CLASS(wh, SCM_CLASS_WEAK_HASH_TABLE);
312
313 wh->weakness = src->weakness;
314 wh->type = src->type;
315 wh->defaultValue = src->defaultValue;
316 wh->hashfn = src->hashfn;
317 wh->cmpfn = src->cmpfn;
318 wh->goneEntries = 0;
319 Scm_HashCoreCopy(&wh->core, &src->core);
320 return SCM_OBJ(wh);
321 }
322
Scm_WeakHashTableRef(ScmWeakHashTable * ht,ScmObj key,ScmObj fallback)323 ScmObj Scm_WeakHashTableRef(ScmWeakHashTable *ht, ScmObj key, ScmObj fallback)
324 {
325 ScmDictEntry *e = Scm_HashCoreSearch(SCM_WEAK_HASH_TABLE_CORE(ht),
326 (intptr_t)key, SCM_DICT_GET);
327 if (!e) return fallback;
328 if (ht->weakness & SCM_WEAK_VALUE) {
329 void *val = Scm_WeakBoxRef((ScmWeakBox*)e->value);
330 if (Scm_WeakBoxEmptyP((ScmWeakBox*)e->value)) return ht->defaultValue;
331 SCM_ASSERT(val != NULL);
332 return SCM_OBJ(val);
333 } else {
334 return SCM_DICT_VALUE(e);
335 }
336 }
337
Scm_WeakHashTableSet(ScmWeakHashTable * ht,ScmObj key,ScmObj value,int flags)338 ScmObj Scm_WeakHashTableSet(ScmWeakHashTable *ht, ScmObj key, ScmObj value,
339 int flags)
340 {
341 intptr_t proxy;
342
343 if (ht->weakness&SCM_WEAK_KEY) {
344 proxy = (intptr_t)Scm_MakeWeakBox(key);
345 } else {
346 proxy = (intptr_t)key;
347 }
348
349 ScmDictEntry *e = Scm_HashCoreSearch(
350 SCM_WEAK_HASH_TABLE_CORE(ht), proxy,
351 (flags&SCM_DICT_NO_CREATE)?SCM_DICT_GET:SCM_DICT_CREATE);
352 if (!e) return SCM_UNBOUND;
353 if (ht->weakness&SCM_WEAK_VALUE) {
354 if (flags&SCM_DICT_NO_OVERWRITE) {
355 if (e->value) {
356 void *val = Scm_WeakBoxRef((ScmWeakBox*)e->value);
357 if (!Scm_WeakBoxEmptyP((ScmWeakBox*)e->value))
358 return SCM_OBJ(val);
359 }
360 }
361 e->value = (intptr_t)Scm_MakeWeakBox(value);
362 return value;
363 } else {
364 if (flags&SCM_DICT_NO_OVERWRITE && e->value) {
365 return SCM_DICT_VALUE(e);
366 }
367 (void)SCM_DICT_SET_VALUE(e, value);
368 return value;
369 }
370 }
371
Scm_WeakHashTableDelete(ScmWeakHashTable * ht,ScmObj key)372 ScmObj Scm_WeakHashTableDelete(ScmWeakHashTable *ht, ScmObj key)
373 {
374 ScmDictEntry *e = Scm_HashCoreSearch(SCM_WEAK_HASH_TABLE_CORE(ht),
375 (intptr_t)key, SCM_DICT_DELETE);
376 if (e && e->value) {
377 if (ht->weakness&SCM_WEAK_VALUE) {
378 void *val = Scm_WeakBoxRef((ScmWeakBox*)e->value);
379 if (!Scm_WeakBoxEmptyP((ScmWeakBox*)e->value))
380 return SCM_OBJ(val);
381 else
382 return SCM_UNBOUND;
383 } else {
384 return SCM_DICT_VALUE(e);
385 }
386 } else {
387 return SCM_UNBOUND;
388 }
389 }
390
Scm_WeakHashIterInit(ScmWeakHashIter * iter,ScmWeakHashTable * ht)391 void Scm_WeakHashIterInit(ScmWeakHashIter *iter, ScmWeakHashTable *ht)
392 {
393 Scm_HashIterInit(&iter->iter, SCM_WEAK_HASH_TABLE_CORE(ht));
394 iter->table = ht;
395 }
396
Scm_WeakHashIterNext(ScmWeakHashIter * iter,ScmObj * key,ScmObj * value)397 int Scm_WeakHashIterNext(ScmWeakHashIter *iter, ScmObj *key, ScmObj *value)
398 {
399 for (;;) {
400 ScmDictEntry *e = Scm_HashIterNext(&iter->iter);
401 if (e == NULL) return FALSE;
402 if (iter->table->weakness & SCM_WEAK_KEY) {
403 ScmWeakBox *box = (ScmWeakBox*)e->key;
404 ScmObj realkey = SCM_OBJ(Scm_WeakBoxRef(box));
405 if (Scm_WeakBoxEmptyP(box)) {
406 MARK_GONE_ENTRY(iter->table, e);
407 continue;
408 }
409 *key = realkey;
410 } else {
411 *key = (ScmObj)e->key;
412 }
413
414 if (iter->table->weakness & SCM_WEAK_VALUE) {
415 ScmWeakBox *box = (ScmWeakBox*)e->value;
416 ScmObj realval = SCM_OBJ(Scm_WeakBoxRef(box));
417 if (Scm_WeakBoxEmptyP(box)) {
418 *value = iter->table->defaultValue;
419 } else {
420 *value = realval;
421 }
422 } else {
423 *value = (ScmObj)e->value;
424 }
425 return TRUE;
426 }
427 }
428
Scm_WeakHashTableKeys(ScmWeakHashTable * table)429 ScmObj Scm_WeakHashTableKeys(ScmWeakHashTable *table)
430 {
431 ScmWeakHashIter iter;
432 ScmObj h = SCM_NIL, t = SCM_NIL, k, v;
433 Scm_WeakHashIterInit(&iter, table);
434 while (Scm_WeakHashIterNext(&iter, &k, &v)) {
435 SCM_APPEND1(h, t, k);
436 }
437 return h;
438 }
439
Scm_WeakHashTableValues(ScmWeakHashTable * table)440 ScmObj Scm_WeakHashTableValues(ScmWeakHashTable *table)
441 {
442 ScmWeakHashIter iter;
443 ScmObj h = SCM_NIL, t = SCM_NIL, k, v;
444 Scm_WeakHashIterInit(&iter, table);
445 while (Scm_WeakHashIterNext(&iter, &k, &v)) {
446 SCM_APPEND1(h, t, v);
447 }
448 return h;
449 }
450