- 16 tasks complete: arena, object model, borrow word, containers, RC + budgeted cycle collector, wob_build, validating loader, interpreter core (dual dispatch), object opcodes, drop-map unwinding, builtins + DB_STUB + TRAP, ICALL, wovm CLI + just recipes - 13 test suites × 2 dispatch flavors (ASan+UBSan) + CLI smoke, all green - .wob v1 format pinned in src/wob.h + docs/plan/oop-vm/00-wob-format.md - wo-rt.c reference event-loop preserved for sub-project 2 This is Iteration 2 of the OOP milestone; compiler front (Iteration 3) is in progress on this branch. They meet at Iteration 4 (emitter+e2e).
115 lines
3.7 KiB
C
115 lines
3.7 KiB
C
/* test_cycle — budgeted Bacon–Rajan cycle collection.
|
||
* Cycle classes use the malloc-path trick (~130 fields) so ASan proves
|
||
* every free. Budget semantics: a step consumes candidates from the buffer
|
||
* (roots popped + whites purged) up to `budget`, whole components atomic. */
|
||
#include "cont.h"
|
||
#include "gc.h"
|
||
#include "t.h"
|
||
|
||
#define BIG 130
|
||
|
||
/* @gc class "GNode": field0 GCREF, field1 MULTI, rest scalars */
|
||
static uint8_t gnode_kinds[BIG];
|
||
static wo_classdesc CLASSES[1];
|
||
|
||
static void setup(void) {
|
||
gnode_kinds[0] = WO_K_GCREF;
|
||
gnode_kinds[1] = WO_K_MULTI;
|
||
CLASSES[0] = (wo_classdesc){
|
||
.name = 0, .flags = WO_CLASSF_GC, .field_cnt = BIG, .kinds = gnode_kinds};
|
||
}
|
||
|
||
/* helper: link a->f0 = b, taking a reference on b */
|
||
static void link(wo_hdr *a, wo_hdr *b) {
|
||
wo_fields(a)[0] = (uint64_t)(uintptr_t)b;
|
||
wo_rc_inc(b);
|
||
}
|
||
|
||
static void test_two_object_cycle_collects(void) {
|
||
wo_rt rt;
|
||
T_EQ(wo_rt_init(&rt, 1 << 16, CLASSES, 1), 0);
|
||
wo_hdr *a = wo_obj_new(&rt, 0);
|
||
wo_hdr *b = wo_obj_new(&rt, 0);
|
||
link(a, b);
|
||
link(b, a);
|
||
/* drop both external handles: objects survive on cycle edges alone */
|
||
wo_rc_dec(&rt, a);
|
||
wo_rc_dec(&rt, b);
|
||
T_EQ(rt.cycbuf.len, 2); /* both buffered as candidates */
|
||
T_EQ(wo_gc_step(&rt, 16), 2); /* whole cycle freed (ASan proves it) */
|
||
T_EQ(rt.cycbuf.len, 0);
|
||
wo_rt_destroy(&rt);
|
||
}
|
||
|
||
static void test_budget_one_cycle_per_step(void) {
|
||
wo_rt rt;
|
||
T_EQ(wo_rt_init(&rt, 1 << 16, CLASSES, 1), 0);
|
||
wo_hdr *a = wo_obj_new(&rt, 0), *b = wo_obj_new(&rt, 0);
|
||
wo_hdr *c = wo_obj_new(&rt, 0), *d = wo_obj_new(&rt, 0);
|
||
link(a, b);
|
||
link(b, a);
|
||
link(c, d);
|
||
link(d, c);
|
||
wo_rc_dec(&rt, a);
|
||
wo_rc_dec(&rt, b);
|
||
wo_rc_dec(&rt, c);
|
||
wo_rc_dec(&rt, d);
|
||
T_EQ(rt.cycbuf.len, 4);
|
||
/* budget 2 = one two-object component per step */
|
||
T_EQ(wo_gc_step(&rt, 2), 2);
|
||
T_EQ(rt.cycbuf.len, 2);
|
||
T_EQ(wo_gc_step(&rt, 2), 2);
|
||
T_EQ(rt.cycbuf.len, 0);
|
||
/* nothing left: a further step frees nothing */
|
||
T_EQ(wo_gc_step(&rt, 2), 0);
|
||
wo_rt_destroy(&rt);
|
||
}
|
||
|
||
static void test_externally_held_cycle_survives_then_dies(void) {
|
||
wo_rt rt;
|
||
T_EQ(wo_rt_init(&rt, 1 << 16, CLASSES, 1), 0);
|
||
wo_hdr *a = wo_obj_new(&rt, 0), *b = wo_obj_new(&rt, 0);
|
||
link(a, b);
|
||
link(b, a);
|
||
/* keep the external handle on a; drop only b's */
|
||
wo_rc_dec(&rt, b);
|
||
T_EQ(rt.cycbuf.len, 1);
|
||
T_EQ(wo_gc_step(&rt, 16), 0); /* held from outside: survives */
|
||
T_EQ(rt.cycbuf.len, 0); /* candidate consumed, flag cleared */
|
||
/* counts fully restored */
|
||
T_EQ(a->rc, 2);
|
||
T_EQ(b->rc, 1);
|
||
T_EQ(a->flags & (WO_F_BUF | WO_F_COLOR), 0);
|
||
T_EQ(b->flags & (WO_F_BUF | WO_F_COLOR), 0);
|
||
/* still usable, then truly dead */
|
||
wo_rc_dec(&rt, a);
|
||
T_EQ(rt.cycbuf.len, 1); /* re-buffered on the last external decrement */
|
||
T_EQ(wo_gc_step(&rt, 16), 2);
|
||
wo_rt_destroy(&rt);
|
||
}
|
||
|
||
static void test_cycle_through_multi_elements(void) {
|
||
wo_rt rt;
|
||
T_EQ(wo_rt_init(&rt, 1 << 16, CLASSES, 1), 0);
|
||
wo_hdr *a = wo_obj_new(&rt, 0), *b = wo_obj_new(&rt, 0);
|
||
/* a --(multi element)--> b --(gcref field)--> a */
|
||
wo_multi *m = wo_multi_new(&rt, WO_K_GCREF);
|
||
T_EQ(wo_multi_push(m, (uint64_t)(uintptr_t)b), 0);
|
||
wo_rc_inc(b);
|
||
wo_fields(a)[1] = (uint64_t)(uintptr_t)m;
|
||
link(b, a);
|
||
wo_rc_dec(&rt, a);
|
||
wo_rc_dec(&rt, b);
|
||
T_EQ(wo_gc_step(&rt, 16), 2); /* multi head + backing freed with a */
|
||
T_EQ(rt.cycbuf.len, 0);
|
||
wo_rt_destroy(&rt);
|
||
}
|
||
|
||
int main(void) {
|
||
setup();
|
||
test_two_object_cycle_collects();
|
||
test_budget_one_cycle_per_step();
|
||
test_externally_held_cycle_survives_then_dies();
|
||
test_cycle_through_multi_elements();
|
||
return t_report("test_cycle");
|
||
}
|