Files
scriptc/packages/runtime/src/scr_union.c
T

142 lines
5.5 KiB
C

/* Tagged unions — immutable refcounted boxes: tag + one payload slot (see
* scr_runtime.h). Ref arms own their payload and carry its RC entry points
* as function pointers (mirroring ScrBox's SCR_BOX_OBJ mechanism), so this
* file needs to know nothing about per-class/per-record struct layouts.
*
* Every union carries a cycle header: an arm can hold a class/record/
* promise payload that points back (the compiler passes arm_trace for
* exactly those arm types), and instances of one union type must be
* uniform, scalar-armed values included. Only boxes with an arm_trace can
* be cycle roots, so only those are buffered as collector candidates. */
#include "scr_runtime.h"
#include <stdio.h>
#include <stdlib.h>
/* Live union count for the RC audit lane (-DSCR_RC_AUDIT); same contract
* as scr_str_live_count in scr_string.c. */
#ifdef SCR_RC_AUDIT
static SCR_TL long scr_live_unions = 0;
long scr_union_live_count(void) { return scr_live_unions; }
/* A published union box is immortal: it is never freed, so it leaves the count. */
void scr_union_live_forget(void) { scr_live_unions--; }
#endif
static void scr_union_trace(void *o, ScrTraceVisit visit, void *ctx) {
ScrUnion *u = (ScrUnion *)o;
if (u->arm_trace) visit(scr_union_peek(u), ctx);
}
/* Teardown for the collector: release the payload only when the trace does
* NOT visit it (the complement — see the contract in scr_runtime.h). */
static void scr_union_gcfree(void *o) {
ScrUnion *u = (ScrUnion *)o;
if (u->arm_release && !u->arm_trace) u->arm_release(scr_union_peek(u));
#ifdef SCR_RC_AUDIT
scr_live_unions--;
#endif
scr_cyc_free_inline(u);
}
static ScrUnion *scr_union_alloc(uint32_t tag) {
static ScrCycType cyc_type = { &scr_union_trace, &scr_union_gcfree, SCR_CYC_KIND_UNION };
ScrUnion *u = scr_cyc_alloc_inline(sizeof(ScrUnion), &cyc_type);
u->rc = 1;
u->tag = tag;
#ifdef SCR_RC_AUDIT
scr_live_unions++;
#endif
return u;
}
ScrUnion *scr_union_new_f64(uint32_t tag, double v) {
ScrUnion *u = scr_union_alloc(tag);
memcpy(&u->slot, &v, sizeof v);
return u;
}
/* Boolean arms carry no payload references, and unions are immutable, so
* the common low tags share immortal headerless instances, like the
* compiler's static unit-arm constants: retain/release skip SIZE_MAX and
* the collector never visits immortals. Maybe-undefined reads of boolean[]
* elements (sieves, visited sets) otherwise allocate and free one box per
* read. Higher tags keep a fresh box. */
#define SCR_UNION_BOOL_TAGS 16
#define SCR_UNION_BOOL_PAIR(t) \
{{SIZE_MAX, (t), NULL, NULL, NULL, 0}, \
{SIZE_MAX, (t), NULL, NULL, NULL, 1}}
static ScrUnion scr_union_bools[SCR_UNION_BOOL_TAGS][2] = {
SCR_UNION_BOOL_PAIR(0), SCR_UNION_BOOL_PAIR(1), SCR_UNION_BOOL_PAIR(2),
SCR_UNION_BOOL_PAIR(3), SCR_UNION_BOOL_PAIR(4), SCR_UNION_BOOL_PAIR(5),
SCR_UNION_BOOL_PAIR(6), SCR_UNION_BOOL_PAIR(7), SCR_UNION_BOOL_PAIR(8),
SCR_UNION_BOOL_PAIR(9), SCR_UNION_BOOL_PAIR(10), SCR_UNION_BOOL_PAIR(11),
SCR_UNION_BOOL_PAIR(12), SCR_UNION_BOOL_PAIR(13), SCR_UNION_BOOL_PAIR(14),
SCR_UNION_BOOL_PAIR(15),
};
ScrUnion *scr_union_new_bool(uint32_t tag, bool v) {
if (tag < SCR_UNION_BOOL_TAGS) return &scr_union_bools[tag][v ? 1 : 0];
ScrUnion *u = scr_union_alloc(tag);
u->slot = v ? 1 : 0;
return u;
}
ScrUnion *scr_union_new_ref(uint32_t tag, void *v, void *(*retain)(void *),
void (*release)(void *), ScrTraceFn trace) {
ScrUnion *u = scr_union_alloc(tag);
memcpy(&u->slot, &v, sizeof v); /* ownership of v moves in (+1 arrived) */
u->arm_retain = retain;
u->arm_release = release;
u->arm_trace = trace;
return u;
}
static void scr_union_destroy(void *object) {
ScrUnion *u = object;
if (u->arm_release) u->arm_release(scr_union_peek(u));
#ifdef SCR_RC_AUDIT
scr_live_unions--;
#endif
scr_cyc_free_inline(u);
}
void scr_union_release(ScrUnion *u) {
if (!u || u->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */
if (--u->rc == 0) {
/* Check the header inline: most boxes were never buffered. A box can
* have been buffered under an earlier arm (typed-ref cache commits swap
* arm contents between boxes of one union type), so this reads the
* header rather than arm_trace. */
scr_cyc_dead(u);
scr_rc_destroy(u, scr_union_destroy);
} else if (u->arm_trace) {
/* Possible cycle root; may collect — u is done. A box whose trace
* visits nothing (scalar arms, and ref arms the compiler proved
* acyclic) cannot be on a collectable cycle, so it is never a root. */
scr_cyc_released(u);
}
}
double scr_union_get_f64(ScrUnion *u) {
double v;
memcpy(&v, &u->slot, sizeof v);
return v;
}
bool scr_union_get_bool(ScrUnion *u) { return u->slot != 0; }
/* ── void*-signature RC adapters (stored in ScrUnion / ScrBox) ─────────── */
void *scr_str_retain_v(void *s) { return scr_str_retain((ScrStr *)s); }
void scr_str_release_v(void *s) { scr_str_release((ScrStr *)s); }
void *scr_arr_retain_v(void *a) { return scr_arr_retain((ScrArr *)a); }
void scr_arr_release_v(void *a) { scr_arr_release((ScrArr *)a); }
void *scr_closure_retain_v(void *c) { return scr_closure_retain((ScrClosure *)c); }
void scr_closure_release_v(void *c) { scr_closure_release((ScrClosure *)c); }
void *scr_union_retain_v(void *u) { return scr_union_retain((ScrUnion *)u); }
void scr_union_release_v(void *u) { scr_union_release((ScrUnion *)u); }
void scr_union_trace_v(void *u, ScrTraceVisit visit, void *ctx) {
scr_union_trace(u, visit, ctx);
}