mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
142 lines
5.5 KiB
C
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);
|
|
}
|