diff --git a/moo/kernel/Process.moo b/moo/kernel/Process.moo index e114f33..60bb6ff 100644 --- a/moo/kernel/Process.moo +++ b/moo/kernel/Process.moo @@ -19,7 +19,7 @@ class(#pointer,#final,#limited) Process(Object) method terminate { -##search from the top contextof the process down to intial_context and find ensure blocks and execute them. +##search from the top context of the process down to intial_context and find ensure blocks and execute them. ## if a different process calls 'terminate' on a process, ## the ensureblock is not executed in the context of the ## process being terminated, but in the context of terminatig process. @@ -42,8 +42,8 @@ class(#pointer,#final,#limited) Process(Object) ## the process must not be scheduled. ## ---------------------------------------------------------------------------------------------------------- - ##(Processor activeProcess ~~ self) ifTrue: [ self _suspend ]. - (thisProcess ~~ self) ifTrue: [ self _suspend ]. + ##if (Processor activeProcess ~~ self) { self _suspend }. + if (thisProcess ~~ self) { self _suspend }. self.current_context unwindTo: self.initial_context return: nil. ^self _terminate } @@ -68,7 +68,7 @@ class Semaphore(Object) fireTimeSec := 0, fireTimeNsec := 0, ioIndex := -1, - ioData := nil, + ioHandle := nil, ioMask := 0. method(#class) forMutualExclusion @@ -129,6 +129,11 @@ class Semaphore(Object) { ^self.fireTimeSec < (aSemaphore fireTime) } + + method notYoungerThan: aSemaphore + { + ^self.fireTimeSec >= (aSemaphore fireTime) + } } class SemaphoreHeap(Object) @@ -190,36 +195,30 @@ class SemaphoreHeap(Object) self.arr at: anIndex put: aSemaphore. aSemaphore heapIndex: anIndex. - ^(aSemaphore youngerThan: item) - ifTrue: [ self siftUp: anIndex ] - ifFalse: [ self siftDown: anIndex ]. + ^if (aSemaphore youngerThan: item) { self siftUp: anIndex } else { self siftDown: anIndex }. } method deleteAt: anIndex { - | item | + | item xitem | item := self.arr at: anIndex. item heapIndex: -1. self.size := self.size - 1. - (anIndex == self.size) - ifTrue: [ - "the last item" - self.arr at: self.size put: nil. - ] - ifFalse: [ - | xitem | - - xitem := self.arr at: self.size. - self.arr at: anIndex put: xitem. - xitem heapIndex: anIndex. - self.arr at: self.size put: nil. - - (xitem youngerThan: item) - ifTrue: [self siftUp: anIndex ] - ifFalse: [self siftDown: anIndex ] - ] + if (anIndex == self.size) + { + ## the last item + self.arr at: self.size put: nil. + } + else + { + xitem := self.arr at: self.size. + self.arr at: anIndex put: xitem. + xitem heapIndex: anIndex. + self.arr at: self.size put: nil. + if (xitem youngerThan: item) { self siftUp: anIndex } else { self siftDown: anIndex }. + } } method parentIndex: anIndex @@ -239,34 +238,31 @@ class SemaphoreHeap(Object) method siftUp: anIndex { - | pindex cindex par item stop | + | pindex cindex par item | - (anIndex <= 0) ifTrue: [ ^anIndex ]. + if (anIndex <= 0) { ^anIndex }. pindex := anIndex. item := self.arr at: anIndex. - stop := false. - [ stop ] whileFalse: [ - + while (true) + { cindex := pindex. - (cindex > 0) - ifTrue: [ - pindex := self parentIndex: cindex. - par := self.arr at: pindex. + if (pindex <= 0) { break }. - (item youngerThan: par) - ifTrue: [ - ## move the parent down - self.arr at: cindex put: par. - par heapIndex: cindex. - ] - ifFalse: [ stop := true ]. - ] - ifFalse: [ stop := true ]. - ]. + pindex := self parentIndex: cindex. + par := self.arr at: pindex. + if (item notYoungerThan: par) { break }. + + ## item is younger than the parent. + ## move the parent down + self.arr at: cindex put: par. + par heapIndex: cindex. + }. + + ## place the item as high as it can self.arr at: cindex put: item. item heapIndex: cindex. @@ -275,36 +271,29 @@ class SemaphoreHeap(Object) method siftDown: anIndex { - | base capa cindex item | + | base capa cindex item + left right younger xitem | base := self.size quo: 2. - (anIndex >= base) ifTrue: [^anIndex]. + if (anIndex >= base) { ^anIndex }. cindex := anIndex. item := self.arr at: cindex. - [ cindex < base ] whileTrue: [ - | left right younger xitem | - + while (cindex < base) + { left := self leftChildIndex: cindex. right := self rightChildIndex: cindex. - ((right < self.size) and: [(self.arr at: right) youngerThan: (self.arr at: left)]) - ifTrue: [ younger := right ] - ifFalse: [ younger := left ]. + younger := if ((right < self.size) and: [(self.arr at: right) youngerThan: (self.arr at: left)]) { right } else { left }. xitem := self.arr at: younger. - (item youngerThan: xitem) - ifTrue: [ - "break the loop" - base := anIndex - ] - ifFalse: [ - self.arr at: cindex put: xitem. - xitem heapIndex: cindex. - cindex := younger. - ] - ]. + if (item youngerThan: xitem) { break }. + + self.arr at: cindex put: xitem. + xitem heapIndex: cindex. + cindex := younger. + }. self.arr at: cindex put: item. item heapIndex: cindex. @@ -315,13 +304,8 @@ class SemaphoreHeap(Object) class(#final,#limited) ProcessScheduler(Object) { - var tally, active, runnable_head, runnable_tail, sem_heap. - - method new - { - "instantiation is not allowed" - ^nil. "TODO: raise an exception" - } + var(#get) tally, active. + var runnable_head, runnable_tail (*, sem_heap*). method activeProcess { @@ -333,8 +317,8 @@ class(#final,#limited) ProcessScheduler(Object) self primitiveFailed. - "The primitive does something like the following in principle: - (self.tally = 0) + (* The primitive does something like the following in principle: + (self.tally == 0) ifTrue: [ self.head := process. self.tail := process. @@ -346,16 +330,16 @@ class(#final,#limited) ProcessScheduler(Object) self.head := process. self.tally := self.tally + 1. ]. - " + *) } - " + (* ------------------- method yield { self primitiveFailed } - " + ----------------- *) method signal: semaphore after: secs { diff --git a/moo/kernel/System.moo b/moo/kernel/System.moo index 6eba8ce..5b2145b 100644 --- a/moo/kernel/System.moo +++ b/moo/kernel/System.moo @@ -32,23 +32,36 @@ class System(Apex) method(#class) __gc_finalizer { - | tmp | + | tmp gc | + gc := false. while (true) { -## TODO: exit from this loop when there are no other processes running. +## TODO: exit from this loop when there are no other processes running except this finalizer process while ((tmp := self _popCollectable) notError) { ## TODO: Do i have to protected this in an exception handler??? - tmp finalize. + if (tmp respondsTo: #finalize) { tmp finalize }. }. - System logNl: 'gc_waiting....'. - Processor sleepFor: 1. ## TODO: wait on semaphore instead.. + (* + if (Processor tally == 1 and: [Processor active == thisProcess]) + { + if (gc) { break }. + + self collectGarbage. + 'GC GC GC GC' dump. + Processor tally dump. + gc := true. + }.*) + + ##System logNl: 'gc_waiting....'. + ##Processor sleepFor: 1. ## TODO: wait on semaphore instead.. } } method(#class,#primitive) _popCollectable. + method(#class,#primitive) collectGarbage. } pooldic System.Log diff --git a/moo/kernel/X11.moo b/moo/kernel/X11.moo index 3eca5b9..4b700c0 100644 --- a/moo/kernel/X11.moo +++ b/moo/kernel/X11.moo @@ -618,10 +618,11 @@ extend X11 'CLOSING X11 EVENT LOOP' dump. Processor unsignal: self.event_loop_sem. + ## TODO: LOOK HERE FOR RACE CONDITION self.event_loop_sem := nil. - + self.event_loop_proc := nil. + self dispose. - ] fork. } } @@ -630,6 +631,7 @@ extend X11 { if (self.event_loop_sem notNil) { + ## TODO: handle race-condition with the part maked 'LOOK HERE FOR RACE CONDITION' self.event_loop_proc terminate. self.event_loop_proc := nil. self.event_loop_sem := nil. @@ -704,14 +706,19 @@ extend X11 class Fx(Object) { + var(#class) X := 20. + var x. + method initialize { + self.X := self.X + 1. + self.x := self.X. self addToBeFinalized. } method finalize { - System logNl: 'Greate... FX instance finalized'. + System logNl: ('Greate... FX instance finalized' & self.x asString). } } @@ -722,6 +729,7 @@ class MyObject(Object) method main1 { | comp1 | + self.disp1 := X11 new. self.disp2 := X11 new. @@ -755,11 +763,9 @@ class MyObject(Object) self.disp1 enterEventLoop. ## this is not a blocking call. it spawns another process. self.disp2 enterEventLoop. - - comp1 := Fx new. Fx new. - Fx new. + comp1 := Fx new. Fx new. } diff --git a/moo/kernel/test-002.moo b/moo/kernel/test-002.moo index 1c7d963..a33fe24 100644 --- a/moo/kernel/test-002.moo +++ b/moo/kernel/test-002.moo @@ -17,6 +17,38 @@ class MyObject(Object) ^a } + method(#class) test_semaphore_heap + { + | sempq a | + sempq := SemaphoreHeap new. + + '--------------------------' dump. + + 1 to: 200 by: 1 do: [ :i | + | sem | + sem := Semaphore new. + sem fireTime: (200 - i). + sempq insert: sem + ]. + + '--------------------------' dump. + sempq deleteAt: 40. + sempq deleteAt: 50. + sempq deleteAt: 100. + + + a := -100. + [sempq size > 0] whileTrue: [ + | sem b | + sem := sempq popTop. + b := sem fireTime. + if (a > b) { ^false }. + a := b. + ]. + + ^true + } + method(#class) main { | tc limit | @@ -24,7 +56,8 @@ class MyObject(Object) tc := %( ## 0 - 4 [ self proc1 == 100 ], - [ Processor sleepFor: 2. self proc1 == 200 ] + [ Processor sleepFor: 2. self proc1 == 200 ], + [ self test_semaphore_heap == true ] ). limit := tc size. @@ -33,7 +66,7 @@ class MyObject(Object) | tb | tb := tc at: idx. System log(System.Log.INFO, idx asString, (if (tb value) { ' PASS' } else { ' FAIL' }), S'\n'). - ] + ]. } } diff --git a/moo/lib/exec.c b/moo/lib/exec.c index c806256..c42c4ff 100644 --- a/moo/lib/exec.c +++ b/moo/lib/exec.c @@ -171,6 +171,7 @@ static moo_oop_process_t make_process (moo_t* moo, moo_oop_context_t c) #if defined(MOO_DEBUG_VM_PROCESSOR) MOO_LOG2 (moo, MOO_LOG_IC | MOO_LOG_DEBUG, "Processor - made process %O of size %zu\n", proc, MOO_OBJ_GET_SIZE(proc)); #endif + return proc; } @@ -254,9 +255,9 @@ static MOO_INLINE int chain_into_processor (moo_t* moo, moo_oop_process_t proc) MOO_ASSERT (moo, tally >= 0); if (tally >= MOO_SMOOI_MAX) { -#if defined(MOO_DEBUG_VM_PROCESSOR) - MOO_LOG0 (moo, MOO_LOG_IC | MOO_LOG_FATAL, "Processor - too many process\n"); -#endif + #if defined(MOO_DEBUG_VM_PROCESSOR) + MOO_LOG0 (moo, MOO_LOG_IC | MOO_LOG_FATAL, "Processor - too many processes\n"); + #endif moo_seterrnum (moo, MOO_EPFULL); return -1; } @@ -713,11 +714,13 @@ static void delete_from_sem_heap (moo_t* moo, moo_ooi_t index) { moo_oop_semaphore_t sem, lastsem; + MOO_ASSERT (moo, index >= 0 && index < moo->sem_heap_count); + sem = moo->sem_heap[index]; sem->heap_index = MOO_SMOOI_TO_OOP(-1); moo->sem_heap_count--; - if (moo->sem_heap_count > 0 && index != moo->sem_heap_count) + if (/*moo->sem_heap_count > 0 &&*/ index != moo->sem_heap_count) { /* move the last item to the deletion position */ lastsem = moo->sem_heap[moo->sem_heap_count]; @@ -808,6 +811,7 @@ static int delete_from_sem_io (moo_t* moo, moo_ooi_t index) moo_oop_semaphore_t sem; int x; + MOO_ASSERT (moo, index >= 0 && index < moo->sem_io_count); sem = moo->sem_io[index]; MOO_ASSERT (moo, index == MOO_OOP_TO_SMOOI(sem->io_index)); @@ -824,7 +828,7 @@ static int delete_from_sem_io (moo_t* moo, moo_ooi_t index) sem->io_index = MOO_SMOOI_TO_OOP(-1); moo->sem_io_count--; - if (moo->sem_io_count > 0 && index != moo->sem_io_count) + if (/*moo->sem_io_count > 0 &&*/ index != moo->sem_io_count) { moo_oop_semaphore_t lastsem; @@ -3071,11 +3075,18 @@ static moo_pfrc_t pf_system_log (moo_t* moo, moo_ooi_t nargs) return MOO_PF_SUCCESS; } +static moo_pfrc_t pf_system_collect_garbage (moo_t* moo, moo_ooi_t nargs) +{ + moo_gc (moo); + MOO_STACK_SETRETTORCV (moo, nargs); + return MOO_PF_SUCCESS; +} + static moo_pfrc_t pf_system_pop_collectable (moo_t* moo, moo_ooi_t nargs) { if (moo->collectable.first) { - moo_collectable_t* first; + moo_finalizable_t* first; first = moo->collectable.first; @@ -3086,11 +3097,8 @@ static moo_pfrc_t pf_system_pop_collectable (moo_t* moo, moo_ooi_t nargs) MOO_STACK_SETRET (moo, nargs, first->oop); MOO_OBJ_SET_FLAGS_GCFIN (first->oop, MOO_OBJ_GET_FLAGS_GCFIN(first->oop) | MOO_GCFIN_FINALIZED); -MOO_DEBUG1 (moo, "POPPING FINALIZABLE...%O\n", first->oop); MOO_DELETE_FROM_LIST (&moo->collectable, first); - moo_freemem (moo, first); - -MOO_DEBUG1 (moo, "POPPED FINALIZABLE...%p\n", moo->collectable.first); + moo_freemem (moo, first); /* TODO: move it to the free list instead... */ } else { @@ -3982,6 +3990,7 @@ static pf_t pftab[] = { "System__putUint32", { pf_system_put_uint32, 3, 3 } }, { "System__putUint64", { pf_system_put_uint64, 3, 3 } }, + { "System_collectGarbage", { pf_system_collect_garbage, 0, 0 } }, { "System_log", { pf_system_log, 2, MA } } }; diff --git a/moo/lib/gc.c b/moo/lib/gc.c index 456f84e..cbf6a53 100644 --- a/moo/lib/gc.c +++ b/moo/lib/gc.c @@ -551,6 +551,9 @@ static moo_uint8_t* scan_new_heap (moo_t* moo, moo_uint8_t* ptr) moo_oop_oop_t xtmp; moo_oow_t size; + /* TODO: is it better to use a flag bit in the header to + * determine that it is an instance of process? + * for example, if (MOO_OBJ_GET_FLAGS_PROC(oop))... */ if (moo->_process && MOO_OBJ_GET_CLASS(oop) == moo->_process) { /* the stack in a process object doesn't need to be @@ -620,7 +623,7 @@ void moo_gc (moo_t* moo) * move objects pointed to by the fields to the new heap. * finally perform some tricky symbol table clean-up. */ - moo_uint8_t* ptr; + moo_uint8_t* scan_ptr; moo_heap_t* tmp; moo_oop_t old_nil; moo_oow_t i; @@ -643,6 +646,8 @@ void moo_gc (moo_t* moo) "Starting GC curheap base %p ptr %p newheap base %p ptr %p\n", moo->curheap->base, moo->curheap->ptr, moo->newheap->base, moo->newheap->ptr); + scan_ptr = (moo_uint8_t*) MOO_ALIGN ((moo_uintptr_t)moo->newheap->base, MOO_SIZEOF(moo_oop_t)); + /* TODO: allocate common objects like _nil and the root dictionary * in the permanant heap. minimize moving around */ old_nil = moo->_nil; @@ -701,12 +706,11 @@ void moo_gc (moo_t* moo) } /* scan the new heap to move referenced objects */ - ptr = (moo_uint8_t*) MOO_ALIGN ((moo_uintptr_t)moo->newheap->base, MOO_SIZEOF(moo_oop_t)); - ptr = scan_new_heap (moo, ptr); + scan_ptr = scan_new_heap (moo, scan_ptr); /* FINALIZATION */ move_finalizable_objects (moo); - ptr = scan_new_heap (moo, ptr); + scan_ptr = scan_new_heap (moo, scan_ptr); /* END FINALIZATION */ /* traverse the symbol table for unreferenced symbols. @@ -721,7 +725,7 @@ void moo_gc (moo_t* moo) /* scan the new heap again from the end position of * the previous scan to move referenced objects by * the symbol table. */ - ptr = scan_new_heap (moo, ptr); + scan_ptr = scan_new_heap (moo, scan_ptr); /* the contents of the current heap is not needed any more. * reset the upper bound to the base. don't forget to align the heap @@ -822,11 +826,10 @@ moo_oop_t moo_shallowcopy (moo_t* moo, moo_oop_t oop) int moo_regfinalizable (moo_t* moo, moo_oop_t oop) { - moo_collectable_t* x; + moo_finalizable_t* x; -MOO_DEBUG1 (moo, "ADDING FINALIZABLE... %O\n", oop); - if (!MOO_OOP_IS_POINTER(oop) || - (MOO_OBJ_GET_FLAGS_GCFIN(oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED))) + + if (!MOO_OOP_IS_POINTER(oop) || (MOO_OBJ_GET_FLAGS_GCFIN(oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED))) { moo_seterrnum (moo, MOO_EINVAL); return -1; @@ -840,17 +843,15 @@ MOO_DEBUG1 (moo, "ADDING FINALIZABLE... %O\n", oop); MOO_APPEND_TO_LIST (&moo->finalizable, x); -MOO_DEBUG1 (moo, "ADDED FINALIZABLE... %O\n", oop); return 0; } int moo_deregfinalizable (moo_t* moo, moo_oop_t oop) { - moo_collectable_t* x; + moo_finalizable_t* x; - if (!MOO_OOP_IS_POINTER(oop) || - ((MOO_OBJ_GET_FLAGS_GCFIN(oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED)) != MOO_GCFIN_FINALIZABLE)) + if (!MOO_OOP_IS_POINTER(oop) || ((MOO_OBJ_GET_FLAGS_GCFIN(oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED)) != MOO_GCFIN_FINALIZABLE)) { moo_seterrnum (moo, MOO_EINVAL); return -1; @@ -861,7 +862,6 @@ int moo_deregfinalizable (moo_t* moo, moo_oop_t oop) { if (x->oop == oop) { -/* TODO: do i need to clear other flags like GC */ MOO_OBJ_SET_FLAGS_GCFIN(oop, (MOO_OBJ_GET_FLAGS_GCFIN(oop) & ~MOO_GCFIN_FINALIZABLE)); MOO_DELETE_FROM_LIST (&moo->finalizable, x); return 0; @@ -874,10 +874,11 @@ int moo_deregfinalizable (moo_t* moo, moo_oop_t oop) static void move_finalizable_objects (moo_t* moo) { - moo_collectable_t* x, * y; + moo_finalizable_t* x, * y; for (x = moo->collectable.first; x; x = x->next) { + MOO_ASSERT (moo, (MOO_OBJ_GET_FLAGS_GCFIN(x->oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED)) == MOO_GCFIN_FINALIZABLE); x->oop = moo_moveoop (moo, x->oop); } @@ -885,15 +886,20 @@ static void move_finalizable_objects (moo_t* moo) { y = x->next; + MOO_ASSERT (moo, (MOO_OBJ_GET_FLAGS_GCFIN(x->oop) & (MOO_GCFIN_FINALIZABLE | MOO_GCFIN_FINALIZED)) == MOO_GCFIN_FINALIZABLE); + if (!MOO_OBJ_GET_FLAGS_MOVED(x->oop)) { -/* TODO: if already finalized, don't move, don't add to collectable -if (MOVE_OBJ_GET_FLAGS_FINALIZED(x->oop)) continue; -* */ - x->oop = moo_moveoop (moo, x->oop); + /* the object has not been moved. it means this object is not reachable + * from the root except this finalizable list. this object would be + * garbage if not for finalizatin. it's almost collectable. but it + * will survive this cycle for finalization. + * + * if garbages consist of finalizable objects only, GC should fail miserably. + * however this is quite unlikely because some key objects for VM execution + * like context objects doesn't require finalization. */ - /* it's almost collectable. but don't collect it yet. - * if garbages consist of finalizable objects only, GC should fail miserably */ + x->oop = moo_moveoop (moo, x->oop); /* remove it from the finalizable list */ MOO_DELETE_FROM_LIST (&moo->finalizable, x); @@ -901,6 +907,7 @@ if (MOVE_OBJ_GET_FLAGS_FINALIZED(x->oop)) continue; /* add it to the collectable list */ MOO_APPEND_TO_LIST (&moo->collectable, x); +// TODO: singal semaphore.. //signal_semaphore (moo, moo->collectable_semaphore); } else diff --git a/moo/lib/moo.h b/moo/lib/moo.h index 3e22491..d73c872 100644 --- a/moo/lib/moo.h +++ b/moo/lib/moo.h @@ -314,7 +314,9 @@ typedef enum moo_obj_type_t moo_obj_type_t; enum moo_gcfin_t { MOO_GCFIN_FINALIZABLE = (1 << 0), - MOO_GCFIN_FINALIZED = (1 << 1) + MOO_GCFIN_FINALIZED = (1 << 1), + MOO_GCFIN_RESERVED_0 = (1 << 2), + MOO_GCFIN_RESERVED_1 = (1 << 3) }; typedef enum moo_gcfin_t moo_gcfin_t; @@ -373,7 +375,7 @@ typedef enum moo_gcfin_t moo_gcfin_t; #define MOO_OBJ_FLAGS_MOVED_BITS 1 #define MOO_OBJ_FLAGS_NGC_BITS 1 #define MOO_OBJ_FLAGS_RDONLY_BITS 1 -#define MOO_OBJ_FLAGS_GCFIN_BITS 2 +#define MOO_OBJ_FLAGS_GCFIN_BITS 4 #define MOO_OBJ_FLAGS_TRAILER_BITS 1 #define MOO_OBJ_FLAGS_TYPE_SHIFT (MOO_OBJ_FLAGS_UNIT_BITS + MOO_OBJ_FLAGS_UNIT_SHIFT) @@ -792,7 +794,7 @@ struct moo_semaphore_t moo_oop_t io_mask; /* SmallInteger */ }; -#define MOO_PROCESS_SCHEDULER_NAMED_INSTVARS 5 +#define MOO_PROCESS_SCHEDULER_NAMED_INSTVARS 4 typedef struct moo_process_scheduler_t moo_process_scheduler_t; typedef struct moo_process_scheduler_t* moo_oop_process_scheduler_t; struct moo_process_scheduler_t @@ -802,7 +804,7 @@ struct moo_process_scheduler_t moo_oop_process_t active; /* pointer to an active process in the runnable process list */ moo_oop_process_t runnable_head; /* runnable process list */ moo_oop_process_t runnable_tail; /* runnable process list */ - moo_oop_t sempq; /* SemaphoreHeap */ + /*moo_oop_t sempq;*/ /* SemaphoreHeap */ }; /** @@ -1001,12 +1003,12 @@ struct moo_sbuf_t }; typedef struct moo_sbuf_t moo_sbuf_t; -typedef struct moo_collectable_t moo_collectable_t; -struct moo_collectable_t +typedef struct moo_finalizable_t moo_finalizable_t; +struct moo_finalizable_t { moo_oop_t oop; - moo_collectable_t* prev; - moo_collectable_t* next; + moo_finalizable_t* prev; + moo_finalizable_t* next; }; /* special callback to be called for trailer */ @@ -1178,14 +1180,14 @@ struct moo_t struct { - moo_collectable_t* first; - moo_collectable_t* last; + moo_finalizable_t* first; + moo_finalizable_t* last; } collectable; struct { - moo_collectable_t* first; - moo_collectable_t* last; + moo_finalizable_t* first; + moo_finalizable_t* last; } finalizable; moo_uintmax_t inst_counter;