The Map header mirror the collector reads is checked against flan_rt.c's, array key pairs cannot share a name, and a program holding only a Map of dyn sets up the collector

This commit is contained in:
Joseph Ferano 2026-09-25 15:52:57 +07:00
parent a615e4ecfc
commit a13c2f9ec3
8 changed files with 62 additions and 4 deletions

View File

@ -522,7 +522,7 @@ Func, Fnptr.
CLOSED: [2026-09-25]
Only a capturing =fn= that may outlive its frame gets a collector environment; one
only called or passed down keeps its stack environment, as every handler does.
Capture stays by value, and a =Map= of function values is refused. Rules out a
Capture stays by value; a =Map= walks its values as a =Vec= does. Rules out a
tag bit on the environment word and a heap environment for every closure.
** WAIT CFn and C's calling convention

View File

@ -1265,8 +1265,8 @@ looked wrong.
### Proved by comparing output, never by reading bytes
`test/survey-x86.sh` builds each program in `test/programs`, the `x86-p*` probes among them, twice — once default, once
`--x86`, **with the same bounds-check setting on both sides** — runs both, and compares stdout, stderr and the exit
`test/survey-x86.sh` builds each program in `test/programs`, the `x86-p*` probes among them, twice — once through LLVM
at `-O0`, once `--x86`, **with the same bounds-check setting on both sides** — runs both, and compares stdout, stderr and the exit
status. stderr is not a detail: every message the condition machinery produces goes there, each carrying a location
this backend emits by hand as a `.rodata` label and a length in a register, and an exit status of 134 with the wrong
text beside it is exactly the failure that reads as a match.

View File

@ -3815,6 +3815,11 @@ and array_key_pair env loc n e =
| _ -> '_')
(Types.to_string aty)
in
(* The mangle is many-to-one — [a+b] and [a_b] come out alike — so a digest
of the printed type, which is an identity, keeps two such keys apart. *)
let tag =
tag ^ "/" ^ String.sub (Digest.to_hex (Digest.string (Types.to_string aty))) 0 12
in
let hname = "map/hash/array/" ^ tag and ename = "map/eq/array/" ^ tag in
let known name =
List.exists (fun (f : Tast.fn) -> f.Tast.name = name) env.lifted

View File

@ -5130,7 +5130,7 @@ let uses_dyn (p : Tast.program) =
let rec carries seen (t : Types.t) =
match t with
| Types.Dyn -> true
| Types.Array (_, e) -> carries seen e
| Types.Array (_, e) | Types.Map (_, e) -> carries seen e
| Types.Named n when not (List.mem n seen) ->
(match Hashtbl.find_opt structs n with
| Some st ->

View File

@ -269,6 +269,21 @@ typedef struct flan_dyn_map_hdr {
#define DYN_MAP_ALIGN 64
#define DYN_MAP_FULL 0x80
/* This mirror's numbers, compared against flan_rt.c's [flan_map_layout] by
* test/dyn_ops.c's "layout" mode. */
void flan_dyn_map_hdr_layout(int64_t out[10]) {
out[0] = (int64_t)sizeof(flan_dyn_map_hdr);
out[1] = (int64_t)offsetof(flan_dyn_map_hdr, data);
out[2] = (int64_t)offsetof(flan_dyn_map_hdr, len);
out[3] = (int64_t)offsetof(flan_dyn_map_hdr, log2cap);
out[4] = (int64_t)offsetof(flan_dyn_map_hdr, alloc);
out[5] = (int64_t)offsetof(flan_dyn_map_hdr, epoch);
out[6] = DYN_MAP_HEAD;
out[7] = DYN_MAP_GROUP;
out[8] = DYN_MAP_ALIGN;
out[9] = DYN_MAP_FULL;
}
/* flan_allocator's prefix, far enough to read the one word a stale-container
* check needs. The struct has more fields after [epoch]; this file never
* touches them; and the alignment of a leading same-typed prefix is the same

View File

@ -508,6 +508,10 @@ void flan_gc_set_floor(int64_t bytes);
* otherwise does. */
void flan_dyn_vec_hdr_layout(int64_t out[6]);
/* flan_dyn.c's mirror of flan_map and of the block geometry the marker reads,
* compared against flan_rt.c's [flan_map_layout] the same way. */
void flan_dyn_map_hdr_layout(int64_t out[10]);
#ifdef __cplusplus
}
#endif

View File

@ -2825,6 +2825,22 @@ typedef struct flan_map {
int64_t epoch;
} flan_map;
/* The header's layout and the block geometry's constants, for the same check
* [flan_vec_layout] exists for: flan_dyn.c restates both to walk a map's full
* slots, and test/dyn_ops.c's "layout" mode compares the two. */
void flan_map_layout(int64_t out[10]) {
out[0] = (int64_t)sizeof(flan_map);
out[1] = (int64_t)offsetof(flan_map, data);
out[2] = (int64_t)offsetof(flan_map, len);
out[3] = (int64_t)offsetof(flan_map, log2cap);
out[4] = (int64_t)offsetof(flan_map, alloc);
out[5] = (int64_t)offsetof(flan_map, epoch);
out[6] = FLAN_MAP_HEAD;
out[7] = FLAN_MAP_GROUP;
out[8] = FLAN_MAP_ALIGN;
out[9] = FLAN_CTRL_FULL;
}
/* ── Hashing ──────────────────────────────────────────────────────────
*
* FNV-1a over the bytes, then a final avalanche. FNV alone leaves the low bits

View File

@ -61,6 +61,7 @@ void flan_rt_init(int32_t argc, char **argv);
void flan_vec_free(void *v, int64_t size, int64_t align, const uint8_t *loc,
int64_t loclen);
void flan_vec_layout(int64_t out[6]);
void flan_map_layout(int64_t out[10]);
static int failures;
@ -597,6 +598,23 @@ static void layout(void) {
fail(msg);
}
}
{
/* The Map header and its block geometry, restated in flan_dyn.c so the
collector can walk a map's slots. Two copies, compared directly. */
int64_t mrt[10], mdyn[10];
static const char *const mnames[10] =
{ "sizeof", "offset of data", "offset of len", "offset of log2cap",
"offset of alloc", "offset of epoch", "head bytes", "group",
"alignment", "full bit" };
flan_map_layout(mrt);
flan_dyn_map_hdr_layout(mdyn);
for (i = 0; i < 10; i++)
if (mrt[i] != mdyn[i]) {
snprintf(msg, sizeof msg, "flan_map vs. flan_dyn_map_hdr's %s: %lld vs. %lld",
mnames[i], (long long)mrt[i], (long long)mdyn[i]);
fail(msg);
}
}
printf(failures == 0 ? "layout ok\n" : "layout failed\n");
}