104104#if MICROPY_ENABLE_FINALISER
105105// FTB = finaliser table byte
106106// if set, then the corresponding block may have a finaliser
107-
108107#define BLOCKS_PER_FTB (8)
109-
110108#define FTB_GET (area , block ) ((area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1)
111109#define FTB_SET (area , block ) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0)
112110#define FTB_CLEAR (area , block ) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0)
113111#endif
114112
113+ #if MICROPY_PY_WEAKREF
114+ // WTB = weakref table byte
115+ // if set, then the corresponding block may have a weakref in MP_STATE_VM(mp_weakref_map).
116+ #define BLOCKS_PER_WTB (8)
117+ #define WTB_GET (area , block ) ((area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] >> ((block) & 7)) & 1)
118+ #define WTB_SET (area , block ) do { area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] |= (1 << ((block) & 7)); } while (0)
119+ #define WTB_CLEAR (area , block ) do { area->gc_weakref_table_start[(block) / BLOCKS_PER_WTB] &= (~(1 << ((block) & 7))); } while (0)
120+ #endif
121+
115122#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
116123#define GC_MUTEX_INIT () mp_thread_recursive_mutex_init(&MP_STATE_MEM(gc_mutex))
117124#define GC_ENTER () mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1)
@@ -138,17 +145,23 @@ static void gc_sweep_free_blocks(void);
138145// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool
139146static void gc_setup_area (mp_state_mem_area_t * area , void * start , void * end ) {
140147 // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes):
141- // T = A + F + P
148+ // T = A + F + W + P
142149 // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB
150+ // W = A * BLOCKS_PER_ATB / BLOCKS_PER_WTB
143151 // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK
144- // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
152+ // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB / BLOCKS_PER_WTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
145153 size_t total_byte_len = (byte * )end - (byte * )start ;
146- #if MICROPY_ENABLE_FINALISER
154+ #if MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF
147155 area -> gc_alloc_table_byte_len = (total_byte_len - ALLOC_TABLE_GAP_BYTE )
148156 * MP_BITS_PER_BYTE
149157 / (
150158 MP_BITS_PER_BYTE
159+ #if MICROPY_ENABLE_FINALISER
151160 + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB
161+ #endif
162+ #if MICROPY_PY_WEAKREF
163+ + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_WTB
164+ #endif
152165 + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK
153166 );
154167 #else
@@ -157,26 +170,36 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
157170
158171 area -> gc_alloc_table_start = (byte * )start ;
159172
173+ // Allocate FTB and WTB blocks if they are enabled.
174+ byte * next_table = area -> gc_alloc_table_start + area -> gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE ;
175+ (void )next_table ;
160176 #if MICROPY_ENABLE_FINALISER
161177 size_t gc_finaliser_table_byte_len = (area -> gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1 ) / BLOCKS_PER_FTB ;
162- area -> gc_finaliser_table_start = area -> gc_alloc_table_start + area -> gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE ;
178+ area -> gc_finaliser_table_start = next_table ;
179+ next_table += gc_finaliser_table_byte_len ;
180+ #endif
181+ #if MICROPY_PY_WEAKREF
182+ size_t gc_weakref_table_byte_len = (area -> gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_WTB - 1 ) / BLOCKS_PER_WTB ;
183+ area -> gc_weakref_table_start = next_table ;
184+ next_table += gc_weakref_table_byte_len ;
163185 #endif
164186
187+ // Allocate the GC pool of heap blocks.
165188 size_t gc_pool_block_len = area -> gc_alloc_table_byte_len * BLOCKS_PER_ATB ;
166189 area -> gc_pool_start = (byte * )end - gc_pool_block_len * BYTES_PER_BLOCK ;
167190 area -> gc_pool_end = end ;
191+ assert (area -> gc_pool_start >= next_table );
168192
169- #if MICROPY_ENABLE_FINALISER
170- assert (area -> gc_pool_start >= area -> gc_finaliser_table_start + gc_finaliser_table_byte_len );
171- #endif
172-
173- #if MICROPY_ENABLE_FINALISER
174- // clear ATB's and FTB's
175- memset (area -> gc_alloc_table_start , 0 , gc_finaliser_table_byte_len + area -> gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE );
176- #else
177- // clear ATB's
178- memset (area -> gc_alloc_table_start , 0 , area -> gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE );
179- #endif
193+ // Clear ATB's, and FTB's and WTB's if they are enabled.
194+ memset (area -> gc_alloc_table_start , 0 ,
195+ area -> gc_alloc_table_byte_len + ALLOC_TABLE_GAP_BYTE
196+ #if MICROPY_ENABLE_FINALISER
197+ + gc_finaliser_table_byte_len
198+ #endif
199+ #if MICROPY_PY_WEAKREF
200+ + gc_weakref_table_byte_len
201+ #endif
202+ );
180203
181204 area -> gc_last_free_atb_index = 0 ;
182205 area -> gc_last_used_block = 0 ;
@@ -196,6 +219,12 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
196219 gc_finaliser_table_byte_len ,
197220 gc_finaliser_table_byte_len * BLOCKS_PER_FTB );
198221 #endif
222+ #if MICROPY_PY_WEAKREF
223+ DEBUG_printf (" weakref table at %p, length " UINT_FMT " bytes, "
224+ UINT_FMT " blocks\n" , area -> gc_weakref_table_start ,
225+ gc_weakref_table_byte_len ,
226+ gc_weakref_table_byte_len * BLOCKS_PER_WTB );
227+ #endif
199228 DEBUG_printf (" pool at %p, length " UINT_FMT " bytes, "
200229 UINT_FMT " blocks\n" , area -> gc_pool_start ,
201230 gc_pool_block_len * BYTES_PER_BLOCK , gc_pool_block_len );
@@ -310,6 +339,9 @@ static bool gc_try_add_heap(size_t failed_alloc) {
310339 #if MICROPY_ENABLE_FINALISER
311340 + total_blocks / BLOCKS_PER_FTB
312341 #endif
342+ #if MICROPY_PY_WEAKREF
343+ + total_blocks / BLOCKS_PER_WTB
344+ #endif
313345 + total_blocks * BYTES_PER_BLOCK
314346 + ALLOC_TABLE_GAP_BYTE
315347 + sizeof (mp_state_mem_area_t );
@@ -556,6 +588,9 @@ void gc_collect_end(void) {
556588 }
557589 MP_STATE_THREAD (gc_lock_depth ) &= ~GC_COLLECT_FLAG ;
558590 GC_EXIT ();
591+ #if MICROPY_PY_WEAKREF
592+ gc_weakref_sweep ();
593+ #endif
559594}
560595
561596static void gc_deal_with_stack_overflow (void ) {
@@ -581,12 +616,16 @@ static void gc_deal_with_stack_overflow(void) {
581616
582617// Run finalisers for all to-be-freed blocks
583618static void gc_sweep_run_finalisers (void ) {
584- #if MICROPY_ENABLE_FINALISER
619+ #if MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF
620+ #if MICROPY_ENABLE_FINALISER && MICROPY_PY_WEAKREF
621+ MP_STATIC_ASSERT (BLOCKS_PER_FTB == BLOCKS_PER_WTB );
622+ #endif
585623 for (const mp_state_mem_area_t * area = & MP_STATE_MEM (area ); area != NULL ; area = NEXT_AREA (area )) {
586624 assert (area -> gc_last_used_block <= area -> gc_alloc_table_byte_len * BLOCKS_PER_ATB );
587625 // Small speed optimisation: skip over empty FTB blocks
588626 size_t ftb_end = area -> gc_last_used_block / BLOCKS_PER_FTB ; // index is inclusive
589627 for (size_t ftb_idx = 0 ; ftb_idx <= ftb_end ; ftb_idx ++ ) {
628+ #if MICROPY_ENABLE_FINALISER
590629 byte ftb = area -> gc_finaliser_table_start [ftb_idx ];
591630 size_t block = ftb_idx * BLOCKS_PER_FTB ;
592631 while (ftb ) {
@@ -616,9 +655,26 @@ static void gc_sweep_run_finalisers(void) {
616655 ftb >>= 1 ;
617656 block ++ ;
618657 }
658+ #endif
659+ #if MICROPY_PY_WEAKREF
660+ byte wtb = area -> gc_weakref_table_start [ftb_idx ];
661+ block = ftb_idx * BLOCKS_PER_WTB ;
662+ while (wtb ) {
663+ MICROPY_GC_HOOK_LOOP (block );
664+ if (wtb & 1 ) { // WTB_GET(area, block) shortcut
665+ if (ATB_GET_KIND (area , block ) == AT_HEAD ) {
666+ mp_obj_base_t * obj = (mp_obj_base_t * )PTR_FROM_BLOCK (area , block );
667+ gc_weakref_about_to_be_freed (obj );
668+ WTB_CLEAR (area , block );
669+ }
670+ }
671+ wtb >>= 1 ;
672+ block ++ ;
673+ }
674+ #endif
619675 }
620676 }
621- #endif // MICROPY_ENABLE_FINALISER
677+ #endif // MICROPY_ENABLE_FINALISER || MICROPY_PY_WEAKREF
622678}
623679
624680// Free unmarked heads and their tails
@@ -769,6 +825,25 @@ void gc_info(gc_info_t *info) {
769825 GC_EXIT ();
770826}
771827
828+ #if MICROPY_PY_WEAKREF
829+ // Mark the GC heap pointer as having a weakref.
830+ void gc_weakref_mark (void * ptr ) {
831+ mp_state_mem_area_t * area ;
832+ #if MICROPY_GC_SPLIT_HEAP
833+ area = gc_get_ptr_area (ptr );
834+ assert (area );
835+ #else
836+ assert (VERIFY_PTR (ptr ));
837+ area = & MP_STATE_MEM (area );
838+ #endif
839+
840+ size_t block = BLOCK_FROM_PTR (area , ptr );
841+ assert (ATB_GET_KIND (area , block ) == AT_HEAD );
842+
843+ WTB_SET (area , block );
844+ }
845+ #endif
846+
772847void * gc_alloc (size_t n_bytes , unsigned int alloc_flags ) {
773848 bool has_finaliser = alloc_flags & GC_ALLOC_FLAG_HAS_FINALISER ;
774849 size_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1 ) & (~(BYTES_PER_BLOCK - 1 ))) / BYTES_PER_BLOCK ;
@@ -967,6 +1042,11 @@ void gc_free(void *ptr) {
9671042 FTB_CLEAR (area , block );
9681043 #endif
9691044
1045+ #if MICROPY_PY_WEAKREF
1046+ // Objects that have a weak reference should not be explicitly freed.
1047+ assert (!WTB_GET (area , block ));
1048+ #endif
1049+
9701050 #if MICROPY_GC_SPLIT_HEAP
9711051 if (MP_STATE_MEM (gc_last_free_area ) != area ) {
9721052 // We freed something but it isn't the current area. Reset the
0 commit comments