mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-31 07:33:01 +00:00 
			
		
		
		
	Add weak references in the form of weak tables.
Any references exclusively held by a weak table may be collected without the programmer needing to free references manually. A table can be setup to have weak keys, weak values, or both.
This commit is contained in:
		
							
								
								
									
										20
									
								
								examples/weak-tables.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								examples/weak-tables.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | (def weak-k (table/new 10 :k)) | ||||||
|  | (def weak-v (table/new 10 :v)) | ||||||
|  | (def weak-kv (table/new 10 :kv)) | ||||||
|  |  | ||||||
|  | (put weak-kv (gensym) 10) | ||||||
|  | (put weak-kv :hello :world) | ||||||
|  | (put weak-k :abc123zz77asda :stuff) | ||||||
|  | (put weak-k true :abc123zz77asda) | ||||||
|  | (put weak-k :zyzzyz false) | ||||||
|  | (put weak-v (gensym) 10) | ||||||
|  | (put weak-v 20 (gensym)) | ||||||
|  | (print "before gc") | ||||||
|  | (tracev weak-k) | ||||||
|  | (tracev weak-v) | ||||||
|  | (tracev weak-kv) | ||||||
|  | (gccollect) | ||||||
|  | (print "after gc") | ||||||
|  | (tracev weak-k) | ||||||
|  | (tracev weak-v) | ||||||
|  | (tracev weak-kv) | ||||||
							
								
								
									
										121
									
								
								src/core/gc.c
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								src/core/gc.c
									
									
									
									
									
								
							| @@ -132,6 +132,24 @@ static void janet_mark_many(const Janet *values, int32_t n) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Mark a bunch of key values items in memory */ | ||||||
|  | static void janet_mark_keys(const JanetKV *kvs, int32_t n) { | ||||||
|  |     const JanetKV *end = kvs + n; | ||||||
|  |     while (kvs < end) { | ||||||
|  |         janet_mark(kvs->key); | ||||||
|  |         kvs++; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Mark a bunch of key values items in memory */ | ||||||
|  | static void janet_mark_values(const JanetKV *kvs, int32_t n) { | ||||||
|  |     const JanetKV *end = kvs + n; | ||||||
|  |     while (kvs < end) { | ||||||
|  |         janet_mark(kvs->value); | ||||||
|  |         kvs++; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Mark a bunch of key values items in memory */ | /* Mark a bunch of key values items in memory */ | ||||||
| static void janet_mark_kvs(const JanetKV *kvs, int32_t n) { | static void janet_mark_kvs(const JanetKV *kvs, int32_t n) { | ||||||
|     const JanetKV *end = kvs + n; |     const JanetKV *end = kvs + n; | ||||||
| @@ -154,7 +172,15 @@ recur: /* Manual tail recursion */ | |||||||
|     if (janet_gc_reachable(table)) |     if (janet_gc_reachable(table)) | ||||||
|         return; |         return; | ||||||
|     janet_gc_mark(table); |     janet_gc_mark(table); | ||||||
|     janet_mark_kvs(table->data, table->capacity); |     enum JanetMemoryType memtype = janet_gc_type(table); | ||||||
|  |     if (memtype == JANET_MEMORY_TABLE_WEAKK) { | ||||||
|  |         janet_mark_values(table->data, table->capacity); | ||||||
|  |     } else if (memtype == JANET_MEMORY_TABLE_WEAKV) { | ||||||
|  |         janet_mark_keys(table->data, table->capacity); | ||||||
|  |     } else if (memtype == JANET_MEMORY_TABLE) { | ||||||
|  |         janet_mark_kvs(table->data, table->capacity); | ||||||
|  |     } | ||||||
|  |     /* do nothing for JANET_MEMORY_TABLE_WEAKKV */ | ||||||
|     if (table->proto) { |     if (table->proto) { | ||||||
|         table = table->proto; |         table = table->proto; | ||||||
|         goto recur; |         goto recur; | ||||||
| @@ -329,12 +355,89 @@ static void janet_deinit_block(JanetGCObject *mem) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Check that a value x has been visited in the mark phase */ | ||||||
|  | static int janet_check_liveref(Janet x) { | ||||||
|  |     switch (janet_type(x)) { | ||||||
|  |         default: | ||||||
|  |             return 1; | ||||||
|  |         case JANET_ARRAY: | ||||||
|  |         case JANET_TABLE: | ||||||
|  |         case JANET_FUNCTION: | ||||||
|  |         case JANET_BUFFER: | ||||||
|  |         case JANET_FIBER: | ||||||
|  |             return janet_gc_reachable(janet_unwrap_pointer(x)); | ||||||
|  |         case JANET_STRING: | ||||||
|  |         case JANET_SYMBOL: | ||||||
|  |         case JANET_KEYWORD: | ||||||
|  |             return janet_gc_reachable(janet_string_head(janet_unwrap_string(x))); | ||||||
|  |         case JANET_ABSTRACT: | ||||||
|  |             return janet_gc_reachable(janet_abstract_head(janet_unwrap_abstract(x))); | ||||||
|  |         case JANET_TUPLE: | ||||||
|  |             return janet_gc_reachable(janet_tuple_head(janet_unwrap_tuple(x))); | ||||||
|  |         case JANET_STRUCT: | ||||||
|  |             return janet_gc_reachable(janet_struct_head(janet_unwrap_struct(x))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Iterate over all allocated memory, and free memory that is not | /* Iterate over all allocated memory, and free memory that is not | ||||||
|  * marked as reachable. Flip the gc color flag for next sweep. */ |  * marked as reachable. Flip the gc color flag for next sweep. */ | ||||||
| void janet_sweep() { | void janet_sweep() { | ||||||
|     JanetGCObject *previous = NULL; |     JanetGCObject *previous = NULL; | ||||||
|     JanetGCObject *current = janet_vm.blocks; |     JanetGCObject *current = janet_vm.weak_blocks; | ||||||
|     JanetGCObject *next; |     JanetGCObject *next; | ||||||
|  |  | ||||||
|  |     /* Sweep weak heap to drop weak refs */ | ||||||
|  |     while (NULL != current) { | ||||||
|  |         next = current->data.next; | ||||||
|  |         if (current->flags & JANET_MEM_REACHABLE) { | ||||||
|  |             /* Check for dead references */ | ||||||
|  |             enum JanetMemoryType type = janet_gc_type(current); | ||||||
|  |             JanetTable *table = (JanetTable *) current; | ||||||
|  |             int check_values = (type == JANET_MEMORY_TABLE_WEAKV) || (type == JANET_MEMORY_TABLE_WEAKKV); | ||||||
|  |             int check_keys = (type == JANET_MEMORY_TABLE_WEAKK) || (type == JANET_MEMORY_TABLE_WEAKKV); | ||||||
|  |             JanetKV *end = table->data + table->capacity; | ||||||
|  |             JanetKV *kvs = table->data; | ||||||
|  |             while (kvs < end) { | ||||||
|  |                 int drop = 0; | ||||||
|  |                 if (check_keys && !janet_check_liveref(kvs->key)) drop = 1; | ||||||
|  |                 if (check_values && !janet_check_liveref(kvs->value)) drop = 1; | ||||||
|  |                 if (drop) { | ||||||
|  |                     /* Inlined from janet_table_remove without search */ | ||||||
|  |                     table->count--; | ||||||
|  |                     table->deleted++; | ||||||
|  |                     kvs->key = janet_wrap_nil(); | ||||||
|  |                     kvs->value = janet_wrap_false(); | ||||||
|  |                 } | ||||||
|  |                 kvs++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         current = next; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Sweep weak heap to free blocks */ | ||||||
|  |     previous = NULL; | ||||||
|  |     current = janet_vm.weak_blocks; | ||||||
|  |     while (NULL != current) { | ||||||
|  |         next = current->data.next; | ||||||
|  |         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { | ||||||
|  |             previous = current; | ||||||
|  |             current->flags &= ~JANET_MEM_REACHABLE; | ||||||
|  |         } else { | ||||||
|  |             janet_vm.block_count--; | ||||||
|  |             janet_deinit_block(current); | ||||||
|  |             if (NULL != previous) { | ||||||
|  |                 previous->data.next = next; | ||||||
|  |             } else { | ||||||
|  |                 janet_vm.weak_blocks = next; | ||||||
|  |             } | ||||||
|  |             janet_free(current); | ||||||
|  |         } | ||||||
|  |         current = next; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Sweep main heap to free blocks */ | ||||||
|  |     previous = NULL; | ||||||
|  |     current = janet_vm.blocks; | ||||||
|     while (NULL != current) { |     while (NULL != current) { | ||||||
|         next = current->data.next; |         next = current->data.next; | ||||||
|         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { |         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { | ||||||
| @@ -352,6 +455,7 @@ void janet_sweep() { | |||||||
|         } |         } | ||||||
|         current = next; |         current = next; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| #ifdef JANET_EV | #ifdef JANET_EV | ||||||
|     /* Sweep threaded abstract types for references to decrement */ |     /* Sweep threaded abstract types for references to decrement */ | ||||||
|     JanetKV *items = janet_vm.threaded_abstracts.data; |     JanetKV *items = janet_vm.threaded_abstracts.data; | ||||||
| @@ -409,8 +513,15 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) { | |||||||
|  |  | ||||||
|     /* Prepend block to heap list */ |     /* Prepend block to heap list */ | ||||||
|     janet_vm.next_collection += size; |     janet_vm.next_collection += size; | ||||||
|     mem->data.next = janet_vm.blocks; |     if (type < JANET_MEMORY_TABLE_WEAKK) { | ||||||
|     janet_vm.blocks = mem; |         /* normal heap */ | ||||||
|  |         mem->data.next = janet_vm.blocks; | ||||||
|  |         janet_vm.blocks = mem; | ||||||
|  |     } else { | ||||||
|  |         /* weak heap */ | ||||||
|  |         mem->data.next = janet_vm.weak_blocks; | ||||||
|  |         janet_vm.weak_blocks = mem; | ||||||
|  |     } | ||||||
|     janet_vm.block_count++; |     janet_vm.block_count++; | ||||||
|  |  | ||||||
|     return (void *)mem; |     return (void *)mem; | ||||||
| @@ -442,7 +553,7 @@ void janet_collect(void) { | |||||||
|     if (janet_vm.gc_suspend) return; |     if (janet_vm.gc_suspend) return; | ||||||
|     depth = JANET_RECURSION_GUARD; |     depth = JANET_RECURSION_GUARD; | ||||||
|     janet_vm.gc_mark_phase = 1; |     janet_vm.gc_mark_phase = 1; | ||||||
|     /* Try and prevent many major collections back to back. |     /* Try to prevent many major collections back to back. | ||||||
|      * A full collection will take O(janet_vm.block_count) time. |      * A full collection will take O(janet_vm.block_count) time. | ||||||
|      * If we have a large heap, make sure our interval is not too |      * If we have a large heap, make sure our interval is not too | ||||||
|      * small so we won't make many collections over it. This is just a |      * small so we won't make many collections over it. This is just a | ||||||
|   | |||||||
| @@ -57,6 +57,9 @@ enum JanetMemoryType { | |||||||
|     JANET_MEMORY_FUNCENV, |     JANET_MEMORY_FUNCENV, | ||||||
|     JANET_MEMORY_FUNCDEF, |     JANET_MEMORY_FUNCDEF, | ||||||
|     JANET_MEMORY_THREADED_ABSTRACT, |     JANET_MEMORY_THREADED_ABSTRACT, | ||||||
|  |     JANET_MEMORY_TABLE_WEAKK, | ||||||
|  |     JANET_MEMORY_TABLE_WEAKV, | ||||||
|  |     JANET_MEMORY_TABLE_WEAKKV | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /* To allocate collectable memory, one must call janet_alloc, initialize the memory, | /* To allocate collectable memory, one must call janet_alloc, initialize the memory, | ||||||
|   | |||||||
| @@ -121,6 +121,7 @@ struct JanetVM { | |||||||
|  |  | ||||||
|     /* Garbage collection */ |     /* Garbage collection */ | ||||||
|     void *blocks; |     void *blocks; | ||||||
|  |     void *weak_blocks; | ||||||
|     size_t gc_interval; |     size_t gc_interval; | ||||||
|     size_t next_collection; |     size_t next_collection; | ||||||
|     size_t block_count; |     size_t block_count; | ||||||
|   | |||||||
| @@ -87,11 +87,27 @@ void janet_table_deinit(JanetTable *table) { | |||||||
| } | } | ||||||
|  |  | ||||||
| /* Create a new table */ | /* Create a new table */ | ||||||
|  |  | ||||||
| JanetTable *janet_table(int32_t capacity) { | JanetTable *janet_table(int32_t capacity) { | ||||||
|     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable)); |     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable)); | ||||||
|     return janet_table_init_impl(table, capacity, 0); |     return janet_table_init_impl(table, capacity, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | JanetTable *janet_table_weakk(int32_t capacity) { | ||||||
|  |     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKK, sizeof(JanetTable)); | ||||||
|  |     return janet_table_init_impl(table, capacity, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | JanetTable *janet_table_weakv(int32_t capacity) { | ||||||
|  |     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKV, sizeof(JanetTable)); | ||||||
|  |     return janet_table_init_impl(table, capacity, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | JanetTable *janet_table_weakkv(int32_t capacity) { | ||||||
|  |     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKKV, sizeof(JanetTable)); | ||||||
|  |     return janet_table_init_impl(table, capacity, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Find the bucket that contains the given key. Will also return | /* Find the bucket that contains the given key. Will also return | ||||||
|  * bucket where key should go if not in the table. */ |  * bucket where key should go if not in the table. */ | ||||||
| JanetKV *janet_table_find(JanetTable *t, Janet key) { | JanetKV *janet_table_find(JanetTable *t, Janet key) { | ||||||
| @@ -293,14 +309,23 @@ JanetTable *janet_table_proto_flatten(JanetTable *t) { | |||||||
| /* C Functions */ | /* C Functions */ | ||||||
|  |  | ||||||
| JANET_CORE_FN(cfun_table_new, | JANET_CORE_FN(cfun_table_new, | ||||||
|               "(table/new capacity)", |               "(table/new capacity &opt weak)", | ||||||
|               "Creates a new empty table with pre-allocated memory " |               "Creates a new empty table with pre-allocated memory " | ||||||
|               "for `capacity` entries. This means that if one knows the number of " |               "for `capacity` entries. This means that if one knows the number of " | ||||||
|               "entries going into a table on creation, extra memory allocation " |               "entries going into a table on creation, extra memory allocation " | ||||||
|               "can be avoided. Returns the new table.") { |               "can be avoided. Optionally provide a keyword flags `:kv` to create a table with " | ||||||
|     janet_fixarity(argc, 1); |               "weak referenecs to keys, values, or both. " | ||||||
|  |               "Returns the new table.") { | ||||||
|  |     janet_arity(argc, 1, 2); | ||||||
|     int32_t cap = janet_getnat(argv, 0); |     int32_t cap = janet_getnat(argv, 0); | ||||||
|     return janet_wrap_table(janet_table(cap)); |     if (argc == 1) { | ||||||
|  |         return janet_wrap_table(janet_table(cap)); | ||||||
|  |     } | ||||||
|  |     uint32_t flags = janet_getflags(argv, 1, "kv"); | ||||||
|  |     if (flags == 0) return janet_wrap_table(janet_table(cap)); | ||||||
|  |     if (flags == 1) return janet_wrap_table(janet_table_weakk(cap)); | ||||||
|  |     if (flags == 2) return janet_wrap_table(janet_table_weakv(cap)); | ||||||
|  |     return janet_wrap_table(janet_table_weakkv(cap)); | ||||||
| } | } | ||||||
|  |  | ||||||
| JANET_CORE_FN(cfun_table_getproto, | JANET_CORE_FN(cfun_table_getproto, | ||||||
|   | |||||||
| @@ -1585,6 +1585,7 @@ int janet_init(void) { | |||||||
|  |  | ||||||
|     /* Garbage collection */ |     /* Garbage collection */ | ||||||
|     janet_vm.blocks = NULL; |     janet_vm.blocks = NULL; | ||||||
|  |     janet_vm.weak_blocks = NULL; | ||||||
|     janet_vm.next_collection = 0; |     janet_vm.next_collection = 0; | ||||||
|     janet_vm.gc_interval = 0x400000; |     janet_vm.gc_interval = 0x400000; | ||||||
|     janet_vm.block_count = 0; |     janet_vm.block_count = 0; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Calvin Rose
					Calvin Rose