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] CLOSED: [2026-09-25]
Only a capturing =fn= that may outlive its frame gets a collector environment; one 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. 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. tag bit on the environment word and a heap environment for every closure.
** WAIT CFn and C's calling convention ** WAIT CFn and C's calling convention

View File

@ -1265,8 +1265,8 @@ looked wrong.
### Proved by comparing output, never by reading bytes ### 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 `test/survey-x86.sh` builds each program in `test/programs`, the `x86-p*` probes among them, twice — once through LLVM
`--x86`, **with the same bounds-check setting on both sides** — runs both, and compares stdout, stderr and the exit 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 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 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. 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) (Types.to_string aty)
in 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 hname = "map/hash/array/" ^ tag and ename = "map/eq/array/" ^ tag in
let known name = let known name =
List.exists (fun (f : Tast.fn) -> f.Tast.name = name) env.lifted 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) = let rec carries seen (t : Types.t) =
match t with match t with
| Types.Dyn -> true | 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) -> | Types.Named n when not (List.mem n seen) ->
(match Hashtbl.find_opt structs n with (match Hashtbl.find_opt structs n with
| Some st -> | Some st ->

View File

@ -269,6 +269,21 @@ typedef struct flan_dyn_map_hdr {
#define DYN_MAP_ALIGN 64 #define DYN_MAP_ALIGN 64
#define DYN_MAP_FULL 0x80 #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 /* 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 * 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 * 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. */ * otherwise does. */
void flan_dyn_vec_hdr_layout(int64_t out[6]); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -2825,6 +2825,22 @@ typedef struct flan_map {
int64_t epoch; int64_t epoch;
} flan_map; } 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 ────────────────────────────────────────────────────────── /* ── Hashing ──────────────────────────────────────────────────────────
* *
* FNV-1a over the bytes, then a final avalanche. FNV alone leaves the low bits * 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, void flan_vec_free(void *v, int64_t size, int64_t align, const uint8_t *loc,
int64_t loclen); int64_t loclen);
void flan_vec_layout(int64_t out[6]); void flan_vec_layout(int64_t out[6]);
void flan_map_layout(int64_t out[10]);
static int failures; static int failures;
@ -597,6 +598,23 @@ static void layout(void) {
fail(msg); 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"); printf(failures == 0 ? "layout ok\n" : "layout failed\n");
} }