A release build's dyn view carries no dev record, the dev registry grows instead of dropping notes, a container compared with itself still traps on a stale view inside it, has-key? traps at its site, and a trap clears the walk site it leaves.
This commit is contained in:
parent
ee6fa5297a
commit
0a05d83b3b
@ -3084,7 +3084,9 @@ A typed container crosses into dyn as a view of its storage wherever that storag
|
|||||||
and above everything its callees push, so the owner is the innermost frame whose address is above it. That is how a
|
and above everything its callees push, so the owner is the innermost frame whose address is above it. That is how a
|
||||||
slice of a local, or a slice parameter over a caller's array, is tied to the right activation. Otherwise it asks
|
slice of a local, or a slice parameter over a caller's array, is tied to the right activation. Otherwise it asks
|
||||||
the allocation registry, through its address index (64 KiB chunks to the bases that overlap them), for the
|
the allocation registry, through its address index (64 KiB chunks to the bases that overlap them), for the
|
||||||
smallest live block holding the address and keeps that block's base and note sequence.
|
smallest live block holding the address and keeps that block's base and note sequence. The registry grows when three
|
||||||
|
quarters of it is live rather than dropping notes, since a dropped note would stop this check without a word; the
|
||||||
|
old table is kept, because a listing on the agent's thread may still be reading it.
|
||||||
- Every read or write checks first. A frame is alive when it is still on the chain from `flan_frame_head` *and* has
|
- Every read or write checks first. A frame is alive when it is still on the chain from `flan_frame_head` *and* has
|
||||||
the same serial: the walk is needed because dead stack keeps its old bytes, serial included, and the serial is
|
the same serial: the walk is needed because dead stack keeps its old bytes, serial included, and the serial is
|
||||||
needed because the next call at the same depth lands at the same address. A block is alive when the registry probe
|
needed because the next call at the same depth lands at the same address. A block is alive when the registry probe
|
||||||
|
|||||||
@ -1293,9 +1293,17 @@ void *flan_dev_frame_slot(const void *frame, int32_t i) {
|
|||||||
* header says a module is never dlclose'd, so they outlive the table.
|
* header says a module is never dlclose'd, so they outlive the table.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* A power of two: the probe wraps with a mask. Fixed, and full is not fatal —
|
/* A power of two: the probe wraps with a mask. The table starts here and
|
||||||
* see flan_dev_reg_note. */
|
* doubles when three quarters of it is live ([flan_reg_grow]): a dyn view's
|
||||||
#define FLAN_REG_CAP 4096
|
* dev check asks it whether a block is still alive, and a table that dropped
|
||||||
|
* notes would answer "never heard of it" for a block that has since been
|
||||||
|
* freed, which is the check silently stopping. Read as [FLAN_REG_CAP]: the
|
||||||
|
* capacity is loaded before the table's address, and [flan_reg_grow] stores
|
||||||
|
* them the other way round, so a reader never pairs the larger capacity with
|
||||||
|
* the smaller table. */
|
||||||
|
#define FLAN_REG_CAP0 4096
|
||||||
|
static int64_t flan_reg_capv = FLAN_REG_CAP0;
|
||||||
|
#define FLAN_REG_CAP (__atomic_load_n(&flan_reg_capv, __ATOMIC_ACQUIRE))
|
||||||
|
|
||||||
/* How many dead entries make a compaction worth running. Not a tuning knob: it
|
/* How many dead entries make a compaction worth running. Not a tuning knob: it
|
||||||
* is the difference between a diagnostic that works on a big program and one
|
* is the difference between a diagnostic that works on a big program and one
|
||||||
@ -1523,10 +1531,10 @@ static void flan_reg_say_full(const char *why) {
|
|||||||
if (flan_reg_full) return;
|
if (flan_reg_full) return;
|
||||||
flan_reg_full = 1;
|
flan_reg_full = 1;
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"flan: the allocation registry is full (%d blocks) — %s. Blocks "
|
"flan: the allocation registry is full (%lld blocks) — %s. Blocks "
|
||||||
"noted from here on are dropped, so every listing is a floor and "
|
"noted from here on are dropped, so every listing is a floor and "
|
||||||
"not a count.\n",
|
"not a count.\n",
|
||||||
FLAN_REG_CAP, why);
|
(long long)FLAN_REG_CAP, why);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Armed by the program's entry in a dev build. The free-side hooks in
|
/* Armed by the program's entry in a dev build. The free-side hooks in
|
||||||
@ -1537,14 +1545,17 @@ static void flan_reg_say_full(const char *why) {
|
|||||||
* repeating the claim that a release build carries nothing. */
|
* repeating the claim that a release build carries nothing. */
|
||||||
static void flan_reg_report(void); /* the exit report, at the bottom */
|
static void flan_reg_report(void); /* the exit report, at the bottom */
|
||||||
|
|
||||||
|
extern int flan_dev_views_checked; /* defined below */
|
||||||
|
|
||||||
void flan_dev_reg_enable(void) {
|
void flan_dev_reg_enable(void) {
|
||||||
if (flan_reg_on) return;
|
if (flan_reg_on) return;
|
||||||
flan_reg = (flan_reg_entry *)calloc(FLAN_REG_CAP, sizeof *flan_reg);
|
flan_reg = (flan_reg_entry *)calloc((size_t)FLAN_REG_CAP, sizeof *flan_reg);
|
||||||
/* A registry that could not be made is not worth dying over; the flag stays
|
/* A registry that could not be made is not worth dying over; the flag stays
|
||||||
off and every question about an address answers "never heard of it",
|
off and every question about an address answers "never heard of it",
|
||||||
which is what a release build answers too. */
|
which is what a release build answers too. */
|
||||||
if (flan_reg == NULL) return;
|
if (flan_reg == NULL) return;
|
||||||
flan_reg_on = 1;
|
flan_reg_on = 1;
|
||||||
|
flan_dev_views_checked = 1;
|
||||||
/* Here and not at file scope: a destructor attribute would run in every
|
/* Here and not at file scope: a destructor attribute would run in every
|
||||||
build, since this file is linked into every build, and that would be a
|
build, since this file is linked into every build, and that would be a
|
||||||
third place a release build is not free. Registered from inside the one
|
third place a release build is not free. Registered from inside the one
|
||||||
@ -1555,6 +1566,10 @@ void flan_dev_reg_enable(void) {
|
|||||||
|
|
||||||
int flan_dev_reg_enabled(void) { return flan_reg_on; }
|
int flan_dev_reg_enabled(void) { return flan_reg_on; }
|
||||||
|
|
||||||
|
/* The same flag as a word flan_dyn.c reads on every view's crossing: a dyn
|
||||||
|
* view carries its dev record only when this is set. */
|
||||||
|
int flan_dev_views_checked;
|
||||||
|
|
||||||
/* A block a resize moved away from, filled with 0xDEADBEEF words in a dev
|
/* A block a resize moved away from, filled with 0xDEADBEEF words in a dev
|
||||||
* build. A slice is a pointer and a length and carries nothing that could say
|
* build. A slice is a pointer and a length and carries nothing that could say
|
||||||
* the Vec under it grew, so a slice taken before a push that moved the storage
|
* the Vec under it grew, so a slice taken before a push that moved the storage
|
||||||
@ -1700,6 +1715,33 @@ static flan_reg_entry *flan_reg_live_at(uintptr_t base) {
|
|||||||
* losing the live half. When they are not the bulk of it this does nothing but
|
* losing the live half. When they are not the bulk of it this does nothing but
|
||||||
* move live entries around and hold the epoch odd while it does — see
|
* move live entries around and hold the epoch odd while it does — see
|
||||||
* FLAN_REG_RECLAIM for the wrong answers that bought. */
|
* FLAN_REG_RECLAIM for the wrong answers that bought. */
|
||||||
|
/* Twice the room, every entry carried over, live and dead alike (a dead one
|
||||||
|
* still names what died). Under the table-wide counter, as a compaction is,
|
||||||
|
* so a listing that overlapped it starts again. The old table is not freed:
|
||||||
|
* a listing on the listener thread may still be reading it, and a dev build
|
||||||
|
* can afford the half it keeps. 0 when the memory could not be had, and the
|
||||||
|
* table stays as it was. */
|
||||||
|
static int flan_reg_grow(void) {
|
||||||
|
int64_t cap = FLAN_REG_CAP, ncap = cap * 2, i;
|
||||||
|
flan_reg_entry *n = (flan_reg_entry *)calloc((size_t)ncap, sizeof *n);
|
||||||
|
if (n == NULL) return 0;
|
||||||
|
__atomic_store_n(&flan_reg_epoch, flan_reg_epoch | 1, __ATOMIC_RELAXED);
|
||||||
|
__atomic_thread_fence(__ATOMIC_RELEASE);
|
||||||
|
for (i = 0; i < cap; i++) {
|
||||||
|
size_t j;
|
||||||
|
if (flan_reg[i].base == 0) continue;
|
||||||
|
j = (size_t)(((flan_reg[i].base >> 3) * 11400714819323198485ULL) >> 40)
|
||||||
|
& (size_t)(ncap - 1);
|
||||||
|
while (n[j].base != 0) j = (j + 1) & (size_t)(ncap - 1);
|
||||||
|
n[j] = flan_reg[i];
|
||||||
|
n[j].gen = 0;
|
||||||
|
}
|
||||||
|
__atomic_store_n(&flan_reg, n, __ATOMIC_RELEASE);
|
||||||
|
__atomic_store_n(&flan_reg_capv, ncap, __ATOMIC_RELEASE);
|
||||||
|
__atomic_store_n(&flan_reg_epoch, (flan_reg_epoch | 1) + 1, __ATOMIC_RELEASE);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void flan_reg_compact(void) {
|
static void flan_reg_compact(void) {
|
||||||
size_t bytes = FLAN_REG_CAP * sizeof(flan_reg_entry);
|
size_t bytes = FLAN_REG_CAP * sizeof(flan_reg_entry);
|
||||||
flan_reg_entry *old = (flan_reg_entry *)malloc(bytes);
|
flan_reg_entry *old = (flan_reg_entry *)malloc(bytes);
|
||||||
@ -1793,6 +1835,9 @@ static void flan_reg_note_full(void *base, int64_t bytes, int64_t elem,
|
|||||||
if (flan_reg_used * 4 > (int64_t)FLAN_REG_CAP * 3
|
if (flan_reg_used * 4 > (int64_t)FLAN_REG_CAP * 3
|
||||||
&& flan_reg_dead >= FLAN_REG_RECLAIM)
|
&& flan_reg_dead >= FLAN_REG_RECLAIM)
|
||||||
flan_reg_compact();
|
flan_reg_compact();
|
||||||
|
/* Still three quarters full once the dead are gone: the live set outgrew
|
||||||
|
the table, so the table grows rather than drop what comes next. */
|
||||||
|
if (flan_reg_used * 4 > (int64_t)FLAN_REG_CAP * 3) flan_reg_grow();
|
||||||
s = flan_reg_slot(a);
|
s = flan_reg_slot(a);
|
||||||
for (probe = 0; probe < FLAN_REG_CAP; probe++) {
|
for (probe = 0; probe < FLAN_REG_CAP; probe++) {
|
||||||
size_t j = (s + (size_t)probe) & (FLAN_REG_CAP - 1);
|
size_t j = (s + (size_t)probe) & (FLAN_REG_CAP - 1);
|
||||||
|
|||||||
@ -63,6 +63,41 @@ void flan_dev_watch_emit(const uint8_t *bytes, int64_t len);
|
|||||||
* program die where it stands", which that file went to some trouble to have
|
* program die where it stands", which that file went to some trouble to have
|
||||||
* only one of. So flan_rt.c exports a thin wrapper and this calls it. */
|
* only one of. So flan_rt.c exports a thin wrapper and this calls it. */
|
||||||
_Noreturn void flan_trap(const uint8_t *name, int64_t namelen);
|
_Noreturn void flan_trap(const uint8_t *name, int64_t namelen);
|
||||||
|
|
||||||
|
/* The site and operation a walk over a view reports a trap at — a print, an
|
||||||
|
* equality, a length — set by the entry point that has them and read by the
|
||||||
|
* element readers the walk calls. NULL when the entry point has no site.
|
||||||
|
* Every trap in this file goes through [dyn_trap], which clears them first:
|
||||||
|
* a trap does not return to the [walk_leave] that would have, and a later
|
||||||
|
* walk must not name the site of one that was abandoned. */
|
||||||
|
static const uint8_t *walk_loc;
|
||||||
|
static int64_t walk_len;
|
||||||
|
static const char *walk_op = "print";
|
||||||
|
|
||||||
|
typedef struct { const uint8_t *loc; int64_t len; const char *op; } walk_site;
|
||||||
|
|
||||||
|
static walk_site walk_enter(const uint8_t *loc, int64_t len, const char *op) {
|
||||||
|
walk_site was;
|
||||||
|
was.loc = walk_loc; was.len = walk_len; was.op = walk_op;
|
||||||
|
walk_loc = loc; walk_len = loc != NULL ? len : 0; walk_op = op;
|
||||||
|
return was;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_leave(walk_site was) {
|
||||||
|
walk_loc = was.loc; walk_len = was.len; walk_op = was.op;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flan_dev_reg_enabled(void);
|
||||||
|
/* runtime/flan_dev.c: set with the registry, so a view's crossing reads a
|
||||||
|
* word rather than making a call to learn it is in a release build. */
|
||||||
|
extern int flan_dev_views_checked;
|
||||||
|
|
||||||
|
static _Noreturn void dyn_trap(const uint8_t *name, int64_t namelen) {
|
||||||
|
walk_loc = NULL;
|
||||||
|
walk_len = 0;
|
||||||
|
walk_op = "print";
|
||||||
|
flan_trap(name, namelen);
|
||||||
|
}
|
||||||
/* A trap's sentence, printed after its site and kept for the break loop, which
|
/* A trap's sentence, printed after its site and kept for the break loop, which
|
||||||
* shows it beside the trap's name (flan_rt.c). */
|
* shows it beside the trap's name (flan_rt.c). */
|
||||||
void flan_say(const uint8_t *loc, int64_t loclen, const char *fmt, ...);
|
void flan_say(const uint8_t *loc, int64_t loclen, const char *fmt, ...);
|
||||||
@ -538,7 +573,7 @@ int32_t flan_dyn_tag(flan_dyn v) {
|
|||||||
case OBJ_VEC: return FLAN_DYN_TAG_VEC;
|
case OBJ_VEC: return FLAN_DYN_TAG_VEC;
|
||||||
/* ...and a struct's view answers a map's, [gen] being its shape
|
/* ...and a struct's view answers a map's, [gen] being its shape
|
||||||
(VIEW_STRUCT, below). */
|
(VIEW_STRUCT, below). */
|
||||||
case OBJ_VIEW: return o->gen == 2 ? FLAN_DYN_TAG_MAP : FLAN_DYN_TAG_VEC;
|
case OBJ_VIEW: return (o->gen & 0xff) == 2 ? FLAN_DYN_TAG_MAP : FLAN_DYN_TAG_VEC;
|
||||||
case OBJ_MAP: return FLAN_DYN_TAG_MAP;
|
case OBJ_MAP: return FLAN_DYN_TAG_MAP;
|
||||||
default: return FLAN_DYN_TAG_INT;
|
default: return FLAN_DYN_TAG_INT;
|
||||||
}
|
}
|
||||||
@ -649,7 +684,7 @@ static double dyn_num_value(flan_dyn v);
|
|||||||
* they are defined, alongside the container operations below */
|
* they are defined, alongside the container operations below */
|
||||||
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
flan_obj *o);
|
flan_obj *o);
|
||||||
static void view_guard_check(const uint8_t *loc, int64_t loclen,
|
static inline void view_guard_check(const uint8_t *loc, int64_t loclen,
|
||||||
const char *op, flan_obj *o);
|
const char *op, flan_obj *o);
|
||||||
/* A struct view's fields, for the map arms of the printers. */
|
/* A struct view's fields, for the map arms of the printers. */
|
||||||
static int64_t view_nfields(flan_obj *o);
|
static int64_t view_nfields(flan_obj *o);
|
||||||
@ -668,25 +703,6 @@ static int view_big_u64(flan_obj *o, int64_t i, int field,
|
|||||||
static int64_t vecish_len(flan_obj *o);
|
static int64_t vecish_len(flan_obj *o);
|
||||||
static flan_dyn vecish_at(flan_obj *o, int64_t i);
|
static flan_dyn vecish_at(flan_obj *o, int64_t i);
|
||||||
|
|
||||||
/* The site and operation a walk over a view reports a trap at — a print, an
|
|
||||||
* equality, a length — set by the entry point that has them and read by the
|
|
||||||
* element readers the walk calls. NULL when the entry point has no site. */
|
|
||||||
static const uint8_t *walk_loc;
|
|
||||||
static int64_t walk_len;
|
|
||||||
static const char *walk_op = "print";
|
|
||||||
|
|
||||||
typedef struct { const uint8_t *loc; int64_t len; const char *op; } walk_site;
|
|
||||||
|
|
||||||
static walk_site walk_enter(const uint8_t *loc, int64_t len, const char *op) {
|
|
||||||
walk_site was;
|
|
||||||
was.loc = walk_loc; was.len = walk_len; was.op = walk_op;
|
|
||||||
walk_loc = loc; walk_len = loc != NULL ? len : 0; walk_op = op;
|
|
||||||
return was;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void walk_leave(walk_site was) {
|
|
||||||
walk_loc = was.loc; walk_len = was.len; walk_op = was.op;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
@ -1022,7 +1038,7 @@ static _Noreturn void trap2(const uint8_t *loc, int64_t loclen,
|
|||||||
say(sb, SAY_MAX, b);
|
say(sb, SAY_MAX, b);
|
||||||
flan_say(loc, loclen, "dyn %s: %s and %s, and %s — (%s %s %s)", op,
|
flan_say(loc, loclen, "dyn %s: %s and %s, and %s — (%s %s %s)", op,
|
||||||
tag_of(a), tag_of(b), why, op, sa, sb);
|
tag_of(a), tag_of(b), why, op, sa, sb);
|
||||||
flan_trap((const uint8_t *)name, namelen);
|
dyn_trap((const uint8_t *)name, namelen);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _Noreturn void trap1(const uint8_t *loc, int64_t loclen,
|
static _Noreturn void trap1(const uint8_t *loc, int64_t loclen,
|
||||||
@ -1032,7 +1048,7 @@ static _Noreturn void trap1(const uint8_t *loc, int64_t loclen,
|
|||||||
say(sa, SAY_MAX, a);
|
say(sa, SAY_MAX, a);
|
||||||
flan_say(loc, loclen, "dyn %s: %s, and %s — (%s %s)", op, tag_of(a), why, op,
|
flan_say(loc, loclen, "dyn %s: %s, and %s — (%s %s)", op, tag_of(a), why, op,
|
||||||
sa);
|
sa);
|
||||||
flan_trap((const uint8_t *)name, namelen);
|
dyn_trap((const uint8_t *)name, namelen);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TYPE_TRAP "DynType", 7
|
#define TYPE_TRAP "DynType", 7
|
||||||
@ -1047,7 +1063,7 @@ static _Noreturn void trap_range(const uint8_t *loc, int64_t loclen,
|
|||||||
flan_say(loc, loclen,
|
flan_say(loc, loclen,
|
||||||
"dyn %s: index %lld is out of bounds for %s of length %lld — %s", op,
|
"dyn %s: index %lld is out of bounds for %s of length %lld — %s", op,
|
||||||
(long long)i, tag_of(v), (long long)len, sv);
|
(long long)i, tag_of(v), (long long)len, sv);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Allocation and collection ─────────────────────────────────────────
|
/* ── Allocation and collection ─────────────────────────────────────────
|
||||||
@ -1120,7 +1136,7 @@ static _Noreturn void trap_oom(const uint8_t *loc, int64_t loclen,
|
|||||||
flan_say(loc, loclen,
|
flan_say(loc, loclen,
|
||||||
"dyn heap: %lld bytes could not be allocated, with %lld live",
|
"dyn heap: %lld bytes could not be allocated, with %lld live",
|
||||||
(long long)want, (long long)gc_bytes);
|
(long long)want, (long long)gc_bytes);
|
||||||
flan_trap((const uint8_t *)"DynHeap", 7);
|
dyn_trap((const uint8_t *)"DynHeap", 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
static flan_obj *gc_alloc(uint8_t kind, int64_t extra) {
|
static flan_obj *gc_alloc(uint8_t kind, int64_t extra) {
|
||||||
@ -1541,7 +1557,8 @@ static void gc_sweep(void) {
|
|||||||
} else {
|
} else {
|
||||||
int64_t held = (int64_t)sizeof(flan_obj);
|
int64_t held = (int64_t)sizeof(flan_obj);
|
||||||
if (o->kind == OBJ_TEXT || o->kind == OBJ_ENV) held += o->len;
|
if (o->kind == OBJ_TEXT || o->kind == OBJ_ENV) held += o->len;
|
||||||
if (o->kind == OBJ_VIEW) held += (int64_t)sizeof(view_guard);
|
if (o->kind == OBJ_VIEW && (o->gen & 0x100))
|
||||||
|
held += (int64_t)sizeof(view_guard);
|
||||||
if (o->kind == OBJ_ENV) envset_del((uintptr_t)(o + 1));
|
if (o->kind == OBJ_ENV) envset_del((uintptr_t)(o + 1));
|
||||||
if (o->kind == OBJ_VEC || o->kind == OBJ_MAP) {
|
if (o->kind == OBJ_VEC || o->kind == OBJ_MAP) {
|
||||||
int64_t per = o->kind == OBJ_MAP ? 2 : 1;
|
int64_t per = o->kind == OBJ_MAP ? 2 : 1;
|
||||||
@ -2143,7 +2160,7 @@ static void class_hook(flan_obj *o, flan_dyn inst, flan_dyn added,
|
|||||||
"slots matched by name. Take migrate-by-name, or handle the "
|
"slots matched by name. Take migrate-by-name, or handle the "
|
||||||
"condition inside the method",
|
"condition inside the method",
|
||||||
(int)c->len, (const char *)(c + 1));
|
(int)c->len, (const char *)(c + 1));
|
||||||
flan_trap((const uint8_t *)"DynMigrate", 10);
|
dyn_trap((const uint8_t *)"DynMigrate", 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2869,9 +2886,33 @@ flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b, const uint8_t *loc,
|
|||||||
|
|
||||||
#define EQ_DEPTH 64
|
#define EQ_DEPTH 64
|
||||||
|
|
||||||
|
/* A container compared with itself is equal without reading an element, but
|
||||||
|
* in a dev build that shortcut would let a view inside it that has gone
|
||||||
|
* stale pass unremarked, where comparing any other container holding it
|
||||||
|
* traps. So a dev build walks the one container, as deep as equality would,
|
||||||
|
* and checks every view it holds — the same rule [eq_walk] applies at the
|
||||||
|
* top. A release build keeps no guards, and takes the shortcut. */
|
||||||
|
static void stale_scan(flan_dyn v, int depth) {
|
||||||
|
flan_obj *o;
|
||||||
|
int64_t i, n;
|
||||||
|
if (!dyn_boxed(v) || dyn_box(v) != BOX_OBJ || depth >= EQ_DEPTH) return;
|
||||||
|
o = dyn_obj(v);
|
||||||
|
if (o == NULL) return;
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
view_guard_check(walk_loc, walk_len, walk_op, o);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (o->kind != OBJ_VEC && o->kind != OBJ_MAP) return;
|
||||||
|
n = o->kind == OBJ_MAP ? o->len * 2 : o->len;
|
||||||
|
for (i = 0; i < n; i++) stale_scan(o->u.v.items[i], depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
static int dyn_equal(flan_dyn a, flan_dyn b, int depth) {
|
static int dyn_equal(flan_dyn a, flan_dyn b, int depth) {
|
||||||
int32_t ta = flan_dyn_tag(a), tb = flan_dyn_tag(b);
|
int32_t ta = flan_dyn_tag(a), tb = flan_dyn_tag(b);
|
||||||
if (a == b && ta != FLAN_DYN_TAG_FLOAT) return 1;
|
if (a == b && ta != FLAN_DYN_TAG_FLOAT) {
|
||||||
|
if (flan_dev_views_checked) stale_scan(a, depth);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (is_num(a) && is_num(b)) {
|
if (is_num(a) && is_num(b)) {
|
||||||
if (ta == FLAN_DYN_TAG_INT && tb == FLAN_DYN_TAG_INT)
|
if (ta == FLAN_DYN_TAG_INT && tb == FLAN_DYN_TAG_INT)
|
||||||
return dyn_int_value(a) == dyn_int_value(b);
|
return dyn_int_value(a) == dyn_int_value(b);
|
||||||
@ -2887,7 +2928,10 @@ static int dyn_equal(flan_dyn a, flan_dyn b, int depth) {
|
|||||||
if (ta == FLAN_DYN_TAG_VEC) {
|
if (ta == FLAN_DYN_TAG_VEC) {
|
||||||
flan_obj *x = dyn_obj(a), *y = dyn_obj(b);
|
flan_obj *x = dyn_obj(a), *y = dyn_obj(b);
|
||||||
int64_t i, xn, yn;
|
int64_t i, xn, yn;
|
||||||
if (x == y) return 1;
|
if (x == y) {
|
||||||
|
if (flan_dev_views_checked) stale_scan(a, depth);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (depth >= EQ_DEPTH) return 0;
|
if (depth >= EQ_DEPTH) return 0;
|
||||||
/* [x]/[y] may each be an ordinary heap vec or a view (M2 item 3) — the
|
/* [x]/[y] may each be an ordinary heap vec or a view (M2 item 3) — the
|
||||||
tag does not say which, so [vecish_len]/[vecish_at] below read either
|
tag does not say which, so [vecish_len]/[vecish_at] below read either
|
||||||
@ -2915,7 +2959,10 @@ static int dyn_equal(flan_dyn a, flan_dyn b, int depth) {
|
|||||||
if (ta == FLAN_DYN_TAG_MAP) {
|
if (ta == FLAN_DYN_TAG_MAP) {
|
||||||
flan_obj *x = dyn_obj(a), *y = dyn_obj(b);
|
flan_obj *x = dyn_obj(a), *y = dyn_obj(b);
|
||||||
int64_t i, j;
|
int64_t i, j;
|
||||||
if (x == y) return 1;
|
if (x == y) {
|
||||||
|
if (flan_dev_views_checked) stale_scan(a, depth);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (depth >= EQ_DEPTH) return 0;
|
if (depth >= EQ_DEPTH) return 0;
|
||||||
/* A struct's view is equal to another view of the same struct type with
|
/* A struct's view is equal to another view of the same struct type with
|
||||||
equal fields, and to nothing else — the answer an instance gets beside
|
equal fields, and to nothing else — the answer an instance gets beside
|
||||||
@ -3083,8 +3130,15 @@ static void desc_lay(const uint8_t *d, int64_t *size, int64_t *align) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t desc_size(const uint8_t *d) {
|
static inline int64_t desc_size(const uint8_t *d) {
|
||||||
int64_t s, a;
|
int64_t s, a;
|
||||||
|
switch (*d) { /* the scalars, without the walk */
|
||||||
|
case 'b': case 'B': case '?': return 1;
|
||||||
|
case 'h': case 'H': return 2;
|
||||||
|
case 'i': case 'I': case 'f': return 4;
|
||||||
|
case 'l': case 'L': case 'd': return 8;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
desc_lay(d, &s, &a);
|
desc_lay(d, &s, &a);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@ -3210,8 +3264,14 @@ extern struct flan_frame *flan_frame_head; /* runtime/flan_dev.c */
|
|||||||
#define VIEW_VEC 1 /* [base] is a Vec's header, read live */
|
#define VIEW_VEC 1 /* [base] is a Vec's header, read live */
|
||||||
#define VIEW_STRUCT 2 /* [base] is the struct, [desc] the struct's own */
|
#define VIEW_STRUCT 2 /* [base] is the struct, [desc] the struct's own */
|
||||||
|
|
||||||
static inline view_guard *view_g(flan_obj *o) { return (view_guard *)(o + 1); }
|
/* A view carries a guard only when the dev registry is on: a release build
|
||||||
static inline int view_shape(flan_obj *o) { return (int)o->gen; }
|
* would allocate and zero it for nothing, and a crossing is on the hot path.
|
||||||
|
* VIEW_GUARDED in [gen] says the guard is there. */
|
||||||
|
#define VIEW_GUARDED 0x100 /* bits 16-31 hold the element size */
|
||||||
|
static inline view_guard *view_g(flan_obj *o) {
|
||||||
|
return (o->gen & VIEW_GUARDED) ? (view_guard *)(o + 1) : NULL;
|
||||||
|
}
|
||||||
|
static inline int view_shape(flan_obj *o) { return (int)(o->gen & 0xff); }
|
||||||
|
|
||||||
void *flan_dev_frame_owner(const void *p);
|
void *flan_dev_frame_owner(const void *p);
|
||||||
|
|
||||||
@ -3236,20 +3296,35 @@ static void guard_storage(view_guard *g, const void *p) {
|
|||||||
/* A new view record, its guard empty. */
|
/* A new view record, its guard empty. */
|
||||||
static flan_obj *view_new(void *base, int64_t len, const uint8_t *desc,
|
static flan_obj *view_new(void *base, int64_t len, const uint8_t *desc,
|
||||||
int shape) {
|
int shape) {
|
||||||
flan_obj *o = gc_alloc(OBJ_VIEW, (int64_t)sizeof(view_guard));
|
int dev = flan_dev_views_checked;
|
||||||
|
flan_obj *o =
|
||||||
|
gc_alloc(OBJ_VIEW, dev ? (int64_t)sizeof(view_guard) : 0);
|
||||||
o->u.view.base = base;
|
o->u.view.base = base;
|
||||||
o->u.view.desc = desc;
|
o->u.view.desc = desc;
|
||||||
o->u.view.nul = NULL;
|
o->u.view.nul = NULL;
|
||||||
o->gen = (uint32_t)shape;
|
o->gen = (uint32_t)shape | (dev ? VIEW_GUARDED : 0);
|
||||||
|
/* A flat or Vec view's element size, kept so an access does not read the
|
||||||
|
descriptor again; 0 when it does not fit the 16 bits, and then it does. */
|
||||||
|
if (shape != VIEW_STRUCT) {
|
||||||
|
int64_t sz = desc_size(desc);
|
||||||
|
if (sz > 0 && sz < 0x10000) o->gen |= (uint32_t)sz << 16;
|
||||||
|
}
|
||||||
o->len = len;
|
o->len = len;
|
||||||
memset(view_g(o), 0, sizeof(view_guard));
|
if (dev) memset(view_g(o), 0, sizeof(view_guard));
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The stale-storage check. The sentence never renders the view: it has just
|
/* The stale-storage check. The sentence never renders the view: it has just
|
||||||
* been found to point at storage that is gone, and rendering reads it. */
|
* been found to point at storage that is gone, and rendering reads it. */
|
||||||
static void view_guard_check(const uint8_t *loc, int64_t loclen,
|
static __attribute__((noinline)) void view_guard_slow(const uint8_t *loc, int64_t loclen,
|
||||||
|
const char *op, flan_obj *o);
|
||||||
|
static inline __attribute__((always_inline)) void view_guard_check(const uint8_t *loc, int64_t loclen,
|
||||||
const char *op, flan_obj *o) {
|
const char *op, flan_obj *o) {
|
||||||
|
if (o->gen & VIEW_GUARDED) view_guard_slow(loc, loclen, op, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((noinline)) void view_guard_slow(const uint8_t *loc, int64_t loclen,
|
||||||
|
const char *op, flan_obj *o) {
|
||||||
view_guard *g = view_g(o);
|
view_guard *g = view_g(o);
|
||||||
if (g->frame != NULL && !flan_dev_frame_alive(g->frame, g->serial)) {
|
if (g->frame != NULL && !flan_dev_frame_alive(g->frame, g->serial)) {
|
||||||
flan_say(loc, loclen,
|
flan_say(loc, loclen,
|
||||||
@ -3257,7 +3332,7 @@ static void view_guard_check(const uint8_t *loc, int64_t loclen,
|
|||||||
"has returned. A view of a local lasts as long as the call that "
|
"has returned. A view of a local lasts as long as the call that "
|
||||||
"made it",
|
"made it",
|
||||||
op, (int)g->fnamelen, g->fname);
|
op, (int)g->fnamelen, g->fname);
|
||||||
flan_trap((const uint8_t *)"DynStale", 8);
|
dyn_trap((const uint8_t *)"DynStale", 8);
|
||||||
}
|
}
|
||||||
if (g->rbase != 0 && !flan_dev_reg_alive(g->rbase, g->rseq)) {
|
if (g->rbase != 0 && !flan_dev_reg_alive(g->rbase, g->rseq)) {
|
||||||
flan_say(loc, loclen,
|
flan_say(loc, loclen,
|
||||||
@ -3265,7 +3340,7 @@ static void view_guard_check(const uint8_t *loc, int64_t loclen,
|
|||||||
"released — freed, cleared by free-all, or left behind when a "
|
"released — freed, cleared by free-all, or left behind when a "
|
||||||
"Vec grew. Take the view again after the change",
|
"Vec grew. Take the view again after the change",
|
||||||
op, (int)g->rtypelen, g->rtype);
|
op, (int)g->rtypelen, g->rtype);
|
||||||
flan_trap((const uint8_t *)"DynStale", 8);
|
dyn_trap((const uint8_t *)"DynStale", 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3285,7 +3360,7 @@ static void view_vec_check(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
"dyn %s: this view's container's allocator was released — the "
|
"dyn %s: this view's container's allocator was released — the "
|
||||||
"Vec was made at epoch %lld and the allocator is at %lld now",
|
"Vec was made at epoch %lld and the allocator is at %lld now",
|
||||||
op, (long long)h->epoch, (long long)(int64_t)a->epoch);
|
op, (long long)h->epoch, (long long)(int64_t)a->epoch);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3323,7 +3398,7 @@ static flan_dyn view_child(flan_obj *parent, const uint8_t *d, uint8_t *p) {
|
|||||||
memcpy(&data, p, 8);
|
memcpy(&data, p, 8);
|
||||||
memcpy(&n, p + 8, 8);
|
memcpy(&n, p + 8, 8);
|
||||||
c = view_new(data, n, d + 1, VIEW_FLAT);
|
c = view_new(data, n, d + 1, VIEW_FLAT);
|
||||||
guard_storage(view_g(c), data);
|
if (view_g(c) != NULL) guard_storage(view_g(c), data);
|
||||||
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
||||||
}
|
}
|
||||||
case 'a': {
|
case 'a': {
|
||||||
@ -3335,8 +3410,9 @@ static flan_dyn view_child(flan_obj *parent, const uint8_t *d, uint8_t *p) {
|
|||||||
case 'v': c = view_new(p, 0, d + 1, VIEW_VEC); break;
|
case 'v': c = view_new(p, 0, d + 1, VIEW_VEC); break;
|
||||||
default: c = view_new(p, 0, d, VIEW_STRUCT); break;
|
default: c = view_new(p, 0, d, VIEW_STRUCT); break;
|
||||||
}
|
}
|
||||||
if (view_shape(parent) == VIEW_VEC) guard_storage(view_g(c), p);
|
if (view_g(c) == NULL) {}
|
||||||
else *view_g(c) = *view_g(parent);
|
else if (view_shape(parent) == VIEW_VEC) guard_storage(view_g(c), p);
|
||||||
|
else if (view_g(parent) != NULL) *view_g(c) = *view_g(parent);
|
||||||
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3360,7 +3436,7 @@ static flan_dyn view_read(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
"dyn %s: this u64 element is %llu, above the largest dyn int "
|
"dyn %s: this u64 element is %llu, above the largest dyn int "
|
||||||
"(9223372036854775807), so it has no dyn value",
|
"(9223372036854775807), so it has no dyn value",
|
||||||
op, (unsigned long long)x);
|
op, (unsigned long long)x);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
return flan_dyn_from_i64((int64_t)x);
|
return flan_dyn_from_i64((int64_t)x);
|
||||||
}
|
}
|
||||||
@ -3435,7 +3511,7 @@ static _Noreturn void field_refuse(const uint8_t *loc, int64_t loclen,
|
|||||||
said_add("(put %s :%.*s %s)", sv, (int)k->len, (const char *)kw_bytes(k),
|
said_add("(put %s :%.*s %s)", sv, (int)k->len, (const char *)kw_bytes(k),
|
||||||
sx);
|
sx);
|
||||||
flan_say(loc, loclen, "%s", said_buf);
|
flan_say(loc, loclen, "%s", said_buf);
|
||||||
flan_trap((const uint8_t *)trap, (int64_t)strlen(trap));
|
dyn_trap((const uint8_t *)trap, (int64_t)strlen(trap));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ", and 1.5 is a float": what the refused value is, for a field's sentence. */
|
/* ", and 1.5 is a float": what the refused value is, for a field's sentence. */
|
||||||
@ -3497,7 +3573,7 @@ static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
"dyn %s: %lld does not fit %s %s element, which holds %lld "
|
"dyn %s: %lld does not fit %s %s element, which holds %lld "
|
||||||
"to %lld", op, (long long)n, an(ty), ty, (long long)lo,
|
"to %lld", op, (long long)n, an(ty), ty, (long long)lo,
|
||||||
(long long)hi);
|
(long long)hi);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
switch (*d) {
|
switch (*d) {
|
||||||
case 'b': case 'B': { uint8_t b = (uint8_t)n; memcpy(p, &b, 1); return; }
|
case 'b': case 'B': { uint8_t b = (uint8_t)n; memcpy(p, &b, 1); return; }
|
||||||
@ -3526,7 +3602,7 @@ static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
"dyn %s: %lld has no exact %s, so it does not go into this "
|
"dyn %s: %lld has no exact %s, so it does not go into this "
|
||||||
"element. Write it as a float, as in %lld.0",
|
"element. Write it as a float, as in %lld.0",
|
||||||
op, (long long)n, ty, (long long)n);
|
op, (long long)n, ty, (long long)n);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
} else if (flan_dyn_tag(x) == FLAN_DYN_TAG_FLOAT)
|
} else if (flan_dyn_tag(x) == FLAN_DYN_TAG_FLOAT)
|
||||||
f = dyn_num_value(x);
|
f = dyn_num_value(x);
|
||||||
@ -3569,14 +3645,16 @@ static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
"it whole — write into its own elements or fields instead of "
|
"it whole — write into its own elements or fields instead of "
|
||||||
"storing %s",
|
"storing %s",
|
||||||
op, an(ty), ty, sx);
|
op, an(ty), ty, sx);
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
dyn_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Element [i] of a vec-shaped view. The caller has checked the bounds. */
|
/* Element [i] of a vec-shaped view. The caller has checked the bounds. */
|
||||||
static uint8_t *view_elem_at(flan_obj *o, int64_t i) {
|
static uint8_t *view_elem_at(flan_obj *o, int64_t i) {
|
||||||
return (uint8_t *)view_base(o) + i * desc_size(o->u.view.desc);
|
int64_t sz = (int64_t)(o->gen >> 16);
|
||||||
|
if (sz == 0) sz = desc_size(o->u.view.desc);
|
||||||
|
return (uint8_t *)view_base(o) + i * sz;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A length and an element reader that answer correctly whether [o] is an
|
/* A length and an element reader that answer correctly whether [o] is an
|
||||||
@ -3615,7 +3693,7 @@ static uint8_t *view_field(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
while (desc_next(&at, &o2, &name, &namelen, &foff, &t))
|
while (desc_next(&at, &o2, &name, &namelen, &foff, &t))
|
||||||
said_add(" :%.*s", (int)namelen, (const char *)name);
|
said_add(" :%.*s", (int)namelen, (const char *)name);
|
||||||
flan_say(loc, loclen, "%s", said_buf);
|
flan_say(loc, loclen, "%s", said_buf);
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
dyn_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
}
|
||||||
return (uint8_t *)o->u.view.base + off;
|
return (uint8_t *)o->u.view.base + off;
|
||||||
}
|
}
|
||||||
@ -3630,7 +3708,8 @@ static flan_dyn view_make(void *base, int64_t len, const uint8_t *desc,
|
|||||||
int shape, int32_t here) {
|
int shape, int32_t here) {
|
||||||
flan_obj *o = view_new(base, len, desc, shape);
|
flan_obj *o = view_new(base, len, desc, shape);
|
||||||
view_guard *g = view_g(o);
|
view_guard *g = view_g(o);
|
||||||
if (here) {
|
if (g == NULL) {}
|
||||||
|
else if (here) {
|
||||||
if (flan_frame_head != NULL) {
|
if (flan_frame_head != NULL) {
|
||||||
g->frame = flan_frame_head;
|
g->frame = flan_frame_head;
|
||||||
g->serial =
|
g->serial =
|
||||||
@ -3849,7 +3928,7 @@ flan_dyn flan_dyn_slice(flan_dyn v, flan_dyn lo, flan_dyn hi,
|
|||||||
flan_say(loc, loclen,
|
flan_say(loc, loclen,
|
||||||
"dyn slice: [%lld %lld) is out of bounds for text of length %lld "
|
"dyn slice: [%lld %lld) is out of bounds for text of length %lld "
|
||||||
"— %s", (long long)a, (long long)b, (long long)len, sv);
|
"— %s", (long long)a, (long long)b, (long long)len, sv);
|
||||||
flan_trap((const uint8_t *)"DynRange", 8);
|
dyn_trap((const uint8_t *)"DynRange", 8);
|
||||||
}
|
}
|
||||||
return flan_dyn_from_bytes(obj_text_bytes(o) + a, b - a);
|
return flan_dyn_from_bytes(obj_text_bytes(o) + a, b - a);
|
||||||
}
|
}
|
||||||
@ -3957,9 +4036,10 @@ static int64_t map_find(flan_obj *o, flan_dyn k) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static flan_obj *want_map(const char *op, flan_dyn m, flan_dyn k) {
|
static flan_obj *want_map(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
|
flan_dyn m, flan_dyn k) {
|
||||||
flan_obj *o;
|
flan_obj *o;
|
||||||
if (!is_map(m)) trap2(NULL, 0, TYPE_TRAP, op, "only a map answers it", m, k);
|
if (!is_map(m)) trap2(loc, loclen, TYPE_TRAP, op, "only a map answers it", m, k);
|
||||||
o = dyn_obj(m);
|
o = dyn_obj(m);
|
||||||
/* The lazy half of the redefinition protocol: [get], [put] and [has-key?]
|
/* The lazy half of the redefinition protocol: [get], [put] and [has-key?]
|
||||||
all arrive here, and CLHS 4.3.6 asks for the update to happen no later
|
all arrive here, and CLHS 4.3.6 asks for the update to happen no later
|
||||||
@ -3970,7 +4050,7 @@ static flan_obj *want_map(const char *op, flan_dyn m, flan_dyn k) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k) {
|
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k) {
|
||||||
flan_obj *o = want_map("get", m, k);
|
flan_obj *o = want_map(NULL, 0, "get", m, k);
|
||||||
if (o->kind == OBJ_VIEW) {
|
if (o->kind == OBJ_VIEW) {
|
||||||
const uint8_t *fty;
|
const uint8_t *fty;
|
||||||
uint8_t *p = view_field(NULL, 0, "get", o, k, &fty);
|
uint8_t *p = view_field(NULL, 0, "get", o, k, &fty);
|
||||||
@ -4005,7 +4085,7 @@ flan_dyn flan_dyn_get(flan_dyn m, flan_dyn k, const uint8_t *loc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static flan_dyn contains_walk(flan_dyn m, flan_dyn k) {
|
static flan_dyn contains_walk(flan_dyn m, flan_dyn k) {
|
||||||
flan_obj *o = want_map("has-key?", m, k);
|
flan_obj *o = want_map(walk_loc, walk_len, walk_op, m, k);
|
||||||
if (o->kind == OBJ_VIEW) {
|
if (o->kind == OBJ_VIEW) {
|
||||||
int64_t off;
|
int64_t off;
|
||||||
view_guard_check(walk_loc, walk_len, walk_op, o);
|
view_guard_check(walk_loc, walk_len, walk_op, o);
|
||||||
@ -4069,7 +4149,7 @@ static _Noreturn void trap_slot_type(const uint8_t *loc, int64_t loclen,
|
|||||||
flan_say(by == BY_NEW && site_building != NULL ? site_building : loc,
|
flan_say(by == BY_NEW && site_building != NULL ? site_building : loc,
|
||||||
by == BY_NEW && site_building != NULL ? site_building_len : loclen,
|
by == BY_NEW && site_building != NULL ? site_building_len : loclen,
|
||||||
"%s", said_buf);
|
"%s", said_buf);
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
dyn_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The value a store into [o] under [k] actually stores: [v], or the float an
|
/* The value a store into [o] under [k] actually stores: [v], or the float an
|
||||||
@ -4110,7 +4190,7 @@ static _Noreturn void trap_no_slot(const uint8_t *loc, int64_t loclen,
|
|||||||
said_add(" :%.*s", (int)e->slots[i]->len,
|
said_add(" :%.*s", (int)e->slots[i]->len,
|
||||||
(const char *)(e->slots[i] + 1));
|
(const char *)(e->slots[i] + 1));
|
||||||
flan_say(loc, loclen, "%s", said_buf);
|
flan_say(loc, loclen, "%s", said_buf);
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
dyn_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A constructor's stores: [flan_dyn_map_set]'s, with the refusal worded for
|
/* A constructor's stores: [flan_dyn_map_set]'s, with the refusal worded for
|
||||||
@ -4118,7 +4198,7 @@ static _Noreturn void trap_no_slot(const uint8_t *loc, int64_t loclen,
|
|||||||
* wrote, and placed at the slot's declaration. */
|
* wrote, and placed at the slot's declaration. */
|
||||||
void flan_dyn_slot_init(flan_dyn m, flan_dyn k, flan_dyn v,
|
void flan_dyn_slot_init(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||||
const uint8_t *loc, int64_t loclen) {
|
const uint8_t *loc, int64_t loclen) {
|
||||||
flan_obj *o = want_map("construct", m, k);
|
flan_obj *o = want_map(loc, loclen, "construct", m, k);
|
||||||
class_entry *e = o->u.v.klass == NULL ? NULL : class_find(o->u.v.klass);
|
class_entry *e = o->u.v.klass == NULL ? NULL : class_find(o->u.v.klass);
|
||||||
map_store(o, k, check_slot(loc, loclen, BY_NEW, o, e, m, k, v));
|
map_store(o, k, check_slot(loc, loclen, BY_NEW, o, e, m, k, v));
|
||||||
}
|
}
|
||||||
@ -4148,7 +4228,7 @@ void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
|||||||
"this is %s%s — %s. A map's entries are written with put",
|
"this is %s%s — %s. A map's entries are written with put",
|
||||||
is_map(m) ? "a map with no class" : "a ",
|
is_map(m) ? "a map with no class" : "a ",
|
||||||
is_map(m) ? "" : tag_of(m), sm);
|
is_map(m) ? "" : tag_of(m), sm);
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
dyn_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
}
|
||||||
o = dyn_obj(m);
|
o = dyn_obj(m);
|
||||||
e = class_sync(o);
|
e = class_sync(o);
|
||||||
|
|||||||
@ -348,6 +348,15 @@ flan_dyn flan_dyn_view_slice(void *data, int64_t len, const uint8_t *desc,
|
|||||||
flan_dyn flan_dyn_view_at(void *addr, int64_t len, const uint8_t *desc,
|
flan_dyn flan_dyn_view_at(void *addr, int64_t len, const uint8_t *desc,
|
||||||
int64_t desclen, int32_t shape, int32_t here);
|
int64_t desclen, int32_t shape, int32_t here);
|
||||||
|
|
||||||
|
/* print, =, length and has-key? with the site they were written at: a view
|
||||||
|
* that traps inside one names it. */
|
||||||
|
void flan_dyn_print_at(flan_dyn v, const uint8_t *loc, int64_t loclen);
|
||||||
|
flan_dyn flan_dyn_eq_at(flan_dyn a, flan_dyn b, const uint8_t *loc,
|
||||||
|
int64_t loclen);
|
||||||
|
flan_dyn flan_dyn_len_at(flan_dyn v, const uint8_t *loc, int64_t loclen);
|
||||||
|
flan_dyn flan_dyn_map_contains_at(flan_dyn m, flan_dyn k, const uint8_t *loc,
|
||||||
|
int64_t loclen);
|
||||||
|
|
||||||
/* ── The collector ─────────────────────────────────────────────────────
|
/* ── The collector ─────────────────────────────────────────────────────
|
||||||
*
|
*
|
||||||
* Mark-sweep, precise, and never moving. [flan_gc_init] is idempotent, and the
|
* Mark-sweep, precise, and never moving. [flan_gc_init] is idempotent, and the
|
||||||
|
|||||||
@ -264,12 +264,9 @@ static int regchurn(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* And a table that really is full, which is the case the trigger above must
|
/* A live set larger than the table's first size: 4096 live blocks and
|
||||||
* not paper over: 4096 live blocks and nothing dead anywhere, then one more.
|
* nothing dead anywhere, then three more. The table grows, so none is
|
||||||
* The note is dropped — that is the standing decision, and dying because a
|
* dropped and the overflow flag stays clear. */
|
||||||
* diagnostic ran out of room would be worse — and what is checked here is that
|
|
||||||
* the drop is *said*, once, rather than only being discoverable by asking the
|
|
||||||
* flag. A second and third dropped note must add nothing. */
|
|
||||||
static int regoverflow(void) {
|
static int regoverflow(void) {
|
||||||
flan_dev_reg_enable();
|
flan_dev_reg_enable();
|
||||||
for (int i = 0; i < CAP; i++)
|
for (int i = 0; i < CAP; i++)
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
/* Resolved because [Build] drops runtime/flan_dyn.h into the directory it
|
/* Resolved because [Build] drops runtime/flan_dyn.h into the directory it
|
||||||
* compiles each translation unit in, beside the .c it writes there. That is
|
* compiles each translation unit in, beside the .c it writes there. That is
|
||||||
@ -1372,6 +1373,41 @@ static void hook_reentry(void) {
|
|||||||
printf(failures == 0 ? "hook ok\n" : "hook failed\n");
|
printf(failures == 0 ? "hook ok\n" : "hook failed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A trap inside a walk that a trap hook leaves by longjmp — what the dev
|
||||||
|
* agent does when it abandons an evaluation — must not leave that walk's site
|
||||||
|
* behind for the next walk that has none. The first comparison traps at
|
||||||
|
* "walk-site:1:1" reading a u64 too wide for a dyn int; the map lookup after
|
||||||
|
* it compares the same two views with no site of its own, and traps again.
|
||||||
|
* The site may appear once, in the first sentence, and not in the second. */
|
||||||
|
extern void (*flan_trap_hook)(const uint8_t *name, int64_t namelen);
|
||||||
|
static jmp_buf walk_out;
|
||||||
|
static void walk_hook(const uint8_t *name, int64_t namelen) {
|
||||||
|
(void)name; (void)namelen;
|
||||||
|
longjmp(walk_out, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walkreset(void) {
|
||||||
|
static uint64_t big[1] = { UINT64_MAX };
|
||||||
|
flan_dyn v, w, m;
|
||||||
|
flan_gc_init();
|
||||||
|
v = flan_dyn_view_at(big, 1, (const uint8_t *)"L", 1, 0, 0);
|
||||||
|
flan_dyn_root_push(&v);
|
||||||
|
w = flan_dyn_view_at(big, 1, (const uint8_t *)"L", 1, 0, 0);
|
||||||
|
flan_dyn_root_push(&w);
|
||||||
|
m = flan_dyn_map_new();
|
||||||
|
flan_dyn_root_push(&m);
|
||||||
|
flan_dyn_map_set(m, v, flan_dyn_from_i64(1));
|
||||||
|
flan_trap_hook = walk_hook;
|
||||||
|
if (setjmp(walk_out) == 0)
|
||||||
|
(void)flan_dyn_eq_at(v, w, (const uint8_t *)"walk-site:1:1", 13);
|
||||||
|
fflush(stderr);
|
||||||
|
fprintf(stderr, "--\n");
|
||||||
|
if (setjmp(walk_out) == 0) (void)flan_dyn_map_get(m, w);
|
||||||
|
flan_trap_hook = NULL;
|
||||||
|
flan_dyn_root_pop(3);
|
||||||
|
printf("walkreset done\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
flan_rt_init(argc, argv);
|
flan_rt_init(argc, argv);
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
@ -1401,6 +1437,7 @@ int main(int argc, char **argv) {
|
|||||||
view();
|
view();
|
||||||
return failures == 0 ? 0 : 1;
|
return failures == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
if (strcmp(argv[1], "walkreset") == 0) { walkreset(); return 0; }
|
||||||
if (strcmp(argv[1], "layout") == 0) {
|
if (strcmp(argv[1], "layout") == 0) {
|
||||||
layout();
|
layout();
|
||||||
return failures == 0 ? 0 : 1;
|
return failures == 0 ? 0 : 1;
|
||||||
|
|||||||
@ -289,4 +289,25 @@
|
|||||||
;; An element's range, with its article.
|
;; An element's range, with its article.
|
||||||
(= n 20)
|
(= n 20)
|
||||||
(let [a [(i8 1)]] (set (at (keep a) 0) 200) 0)
|
(let [a [(i8 1)]] (set (at (keep a) 0) 200) 0)
|
||||||
|
;; More live blocks than the registry's first size, then a free: the
|
||||||
|
;; registry grows rather than dropping notes, so the check still holds.
|
||||||
|
(= n 21)
|
||||||
|
(let [hold (vec-new [i64])]
|
||||||
|
(dotimes [i 6000] (push hold (clone (slice [(i64 i) 1] 0 2))))
|
||||||
|
(let [c (at hold 5999)
|
||||||
|
d (keep c)]
|
||||||
|
(println (at d 0))
|
||||||
|
(free c)
|
||||||
|
(println (at d 0)))
|
||||||
|
0)
|
||||||
|
;; A container holding a stale view, compared with itself.
|
||||||
|
(= n 22)
|
||||||
|
(do (leak-local)
|
||||||
|
(println (clobber))
|
||||||
|
(let [box (the dyn [held 1])]
|
||||||
|
(println (= box box)))
|
||||||
|
0)
|
||||||
|
;; has-key? on a value that is not a map, at its site.
|
||||||
|
(= n 23)
|
||||||
|
(do (println (has-key? (keep 5) :x)) 0)
|
||||||
:else (do (println "?") 1))))
|
:else (do (println "?") 1))))
|
||||||
|
|||||||
@ -8,8 +8,10 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; The argument is the frame count. A negative one runs that many frames and
|
;;;; The argument is the frame count. A negative one runs that many frames and
|
||||||
;;;; never calls free-temp, which is the control: memory grows and a dev
|
;;;; never calls free-temp, which is the control: memory grows and a dev
|
||||||
;;;; build's registry fills. The last line is whether the registry overflowed.
|
;;;; build's registry fills. The last line is whether more blocks are live
|
||||||
(declare-c reg-overflowed [] i32 "flan_dev_reg_overflowed")
|
;;;; than the registry's first size, 4096 — it grows past that rather than
|
||||||
|
;;;; drop notes, so the live count is what says it filled.
|
||||||
|
(declare-c reg-live [live-only i32] i64 "flan_dev_reg_count")
|
||||||
|
|
||||||
(defn main [args [str]] i32
|
(defn main [args [str]] i32
|
||||||
(let [arg (bytes->i64 (bytes-view (at args 1)))
|
(let [arg (bytes->i64 (bytes-view (at args 1)))
|
||||||
@ -26,5 +28,5 @@
|
|||||||
(free-temp)))
|
(free-temp)))
|
||||||
(println total)
|
(println total)
|
||||||
(println (str kept))
|
(println (str kept))
|
||||||
(println (reg-overflowed)))
|
(println (if (> (reg-live 1) 4096) 1 0)))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -5946,7 +5946,8 @@ level "1"
|
|||||||
("19", "#Wide{:a 18000000000000000000 :b 1}\n");
|
("19", "#Wide{:a 18000000000000000000 :b 1}\n");
|
||||||
("19", "dyn-view-any.flan:287:18: dyn get: this u64 element is \
|
("19", "dyn-view-any.flan:287:18: dyn get: this u64 element is \
|
||||||
18000000000000000000");
|
18000000000000000000");
|
||||||
("20", "200 does not fit an i8 element, which holds -128 to 127") ]
|
("20", "200 does not fit an i8 element, which holds -128 to 127");
|
||||||
|
("23", "dyn-view-any.flan:312:20: dyn has-key?: int and keyword") ]
|
||||||
and any_stale =
|
and any_stale =
|
||||||
[ ("4", "this view points into a local of leak-local, and that call \
|
[ ("4", "this view points into a local of leak-local, and that call \
|
||||||
has returned");
|
has returned");
|
||||||
@ -5960,7 +5961,10 @@ level "1"
|
|||||||
("17", "dyn-view-any.flan:279:44: dyn print: this view points into a \
|
("17", "dyn-view-any.flan:279:44: dyn print: this view points into a \
|
||||||
local of leak-local");
|
local of leak-local");
|
||||||
("18", "dyn-view-any.flan:281:53: dyn length: this view points into \
|
("18", "dyn-view-any.flan:281:53: dyn length: this view points into \
|
||||||
a local of leak-local") ]
|
a local of leak-local");
|
||||||
|
("21", "5999\n");
|
||||||
|
("21", "this view's storage, a block of i64, has been released");
|
||||||
|
("22", "dyn =: this view points into a local of leak-local") ]
|
||||||
in
|
in
|
||||||
let dyn_view_any ?opt ?(x86 = false) ?(dev = false) () =
|
let dyn_view_any ?opt ?(x86 = false) ?(dev = false) () =
|
||||||
let exe = compile ?opt ~x86 ~dev "programs/dyn-view-any.flan" in
|
let exe = compile ?opt ~x86 ~dev "programs/dyn-view-any.flan" in
|
||||||
|
|||||||
@ -197,6 +197,26 @@ let () =
|
|||||||
(* The three restatements of flan_vec's layout, compared field by field —
|
(* The three restatements of flan_vec's layout, compared field by field —
|
||||||
see dyn_ops.c's [layout] and [hand_vec]'s comment for what ties them
|
see dyn_ops.c's [layout] and [hand_vec]'s comment for what ties them
|
||||||
together and why nothing at compile time otherwise does. *)
|
together and why nothing at compile time otherwise does. *)
|
||||||
|
(* A walk's site does not outlive a trap that leaves the walk: the second
|
||||||
|
sentence, from a lookup with no site, must not carry the first's. *)
|
||||||
|
let code, out, err = run "walkreset" in
|
||||||
|
(match String.split_on_char '-' err with
|
||||||
|
| _ when code <> 0 || out <> "walkreset done\n" ->
|
||||||
|
fail "a walk left by a trap\n got: %S (exit %d, err %S)" out
|
||||||
|
code err
|
||||||
|
| _ ->
|
||||||
|
let after =
|
||||||
|
match String.index_opt err '\n' with
|
||||||
|
| Some i -> String.sub err i (String.length err - i)
|
||||||
|
| None -> ""
|
||||||
|
in
|
||||||
|
if not (has err "walk-site:1:1") then
|
||||||
|
fail "the first walk's trap did not name its site: %S" err
|
||||||
|
else if has after "walk-site" then
|
||||||
|
fail "a later walk named an abandoned walk's site: %S" err
|
||||||
|
else if not (has after "above the largest dyn int") then
|
||||||
|
fail "the second walk did not trap: %S" err);
|
||||||
|
|
||||||
let code, out, err = run "layout" in
|
let code, out, err = run "layout" in
|
||||||
if code <> 0 || out <> "layout ok\n" then
|
if code <> 0 || out <> "layout ok\n" then
|
||||||
fail "flan_vec's three restatements\n got: %S (exit %d, err %S)"
|
fail "flan_vec's three restatements\n got: %S (exit %d, err %S)"
|
||||||
|
|||||||
@ -640,30 +640,18 @@ let () =
|
|||||||
"a listing taken during a compaction\n got: %S (exit %d, err %S)\n wanted no zero-row and no wrong-count answers"
|
"a listing taken during a compaction\n got: %S (exit %d, err %S)\n wanted no zero-row and no wrong-count answers"
|
||||||
out code err;
|
out code err;
|
||||||
|
|
||||||
(* And a table that is genuinely full, which is the state the trigger above
|
(* And a table whose live set outgrows it: 4096 live blocks and nothing
|
||||||
must not paper over. The note is dropped — a diagnostic that killed the
|
dead, then three more. The table grows rather than dropping them — a
|
||||||
program because it ran out of room would be the diagnostic shooting the
|
dyn view's dev check asks it whether a block is alive, and a dropped
|
||||||
patient — and the decision this pins is that the drop is *said*, once.
|
note would stop that check without a word — so nothing overflows and
|
||||||
Once matters: this is the game thread inside the allocation hook, and a
|
every block is counted. *)
|
||||||
line per dropped note would be sixty a second down a pipe nobody drains
|
|
||||||
while a request is being served. *)
|
|
||||||
let code, out, err = mode "regoverflow" in
|
let code, out, err = mode "regoverflow" in
|
||||||
let want_over = "live 4096\noverflowed 0\noverflowed 1\nlive 4096\n" in
|
let want_over = "live 4096\noverflowed 0\noverflowed 0\nlive 4099\n" in
|
||||||
if code <> 0 || out <> want_over then
|
if code <> 0 || out <> want_over then
|
||||||
fail "a full registry\n got: %S (exit %d)\n wanted: %S" out
|
fail "a registry past its first size\n got: %S (exit %d)\n wanted: %S"
|
||||||
code want_over;
|
out code want_over;
|
||||||
let said_full =
|
if has err "the allocation registry is full" then
|
||||||
let needle = "the allocation registry is full" in
|
fail "a registry that grows said it was full: %S" err;
|
||||||
let rec go i n =
|
|
||||||
if i + String.length needle > String.length err then n
|
|
||||||
else if String.sub err i (String.length needle) = needle then
|
|
||||||
go (i + 1) (n + 1)
|
|
||||||
else go (i + 1) n
|
|
||||||
in
|
|
||||||
go 0 0
|
|
||||||
in
|
|
||||||
if said_full <> 1 then
|
|
||||||
fail "a full registry said so %d times, not once: %S" said_full err;
|
|
||||||
|
|
||||||
Printf.printf
|
Printf.printf
|
||||||
"reload: emit %.1fms llc %.1fms ld %.1fms (v2: emit %.1fms llc %.1fms ld %.1fms) host run %.1fms\n"
|
"reload: emit %.1fms llc %.1fms ld %.1fms (v2: emit %.1fms llc %.1fms ld %.1fms) host run %.1fms\n"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user