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