Item 12 asks five questions and says to answer them with a spike rather than a rewrite. spike/embed/ is that spike: one script, six binaries, each one built to fail loudly at the thing it is asking about. It is deliberately not a dune target -- the root dune only excludes old-ocaml/, so a dune file here would land in @default and make the spike part of the build. It drives ocamlfind and clang by hand against the flan.cmxa dune already produces. The probes, in the order they would kill the idea: the smallest possible link, a C main() reaching one OCaml function; the whole compiler linked in and doing real work; the same again with lib/dynload_stubs.c from the unmerged dlopen branch, because that is the only C the compiler itself is built from; the game keeping the main thread while caml_startup happens on a pthread beside it; the SIGSEGV disposition read on both sides of caml_startup; and an 8 MiB arena checked byte for byte across a compaction. No result is written down yet. This is the apparatus.
62 lines
2.1 KiB
C
62 lines
2.1 KiB
C
/* Step 6: does OCaml's collector touch memory it does not own?
|
|
*
|
|
* Flan's arenas, Vecs and Maps are plain malloc'd memory. The claim is that
|
|
* OCaml never sees them, so a compaction cannot move or scribble on them. The
|
|
* probe: fill an arena with a checkable pattern, hold raw interior pointers
|
|
* into it across a full major collection AND a compaction, then verify every
|
|
* byte and every pointer.
|
|
*
|
|
* What this proves is narrow and worth stating narrowly: OCaml traces its own
|
|
* roots only. It does NOT license storing an OCaml `value` in this arena --
|
|
* that would need caml_register_global_root, and is the way the assumption
|
|
* actually breaks.
|
|
*/
|
|
#include <caml/callback.h>
|
|
#include <caml/mlvalues.h>
|
|
#include <caml/memory.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define ARENA (8u << 20) /* 8 MiB, the shape of a Flan arena */
|
|
|
|
static uint8_t *arena;
|
|
static uint64_t *interior[64];
|
|
|
|
CAMLprim value spike_note_arena(value p) { (void)p; return Val_unit; }
|
|
|
|
static uint8_t pattern(size_t i) { return (uint8_t)(i * 31u + 7u); }
|
|
|
|
int main(int argc, char **argv) {
|
|
const value *f;
|
|
size_t i, bad = 0;
|
|
uint8_t *before;
|
|
(void)argc;
|
|
|
|
arena = malloc(ARENA);
|
|
if (!arena) return 1;
|
|
for (i = 0; i < ARENA; i++) arena[i] = pattern(i);
|
|
for (i = 0; i < 64; i++) interior[i] = (uint64_t *)(arena + i * 4096);
|
|
before = arena;
|
|
|
|
caml_startup(argv);
|
|
|
|
f = caml_named_value("spike_churn");
|
|
if (!f) { fprintf(stderr, "spike: churn not registered\n"); return 1; }
|
|
printf("%s\n", String_val(caml_callback(*f, Val_int(200))));
|
|
|
|
for (i = 0; i < ARENA; i++) if (arena[i] != pattern(i)) bad++;
|
|
printf("arena base %s (%p -> %p)\n", before == arena ? "unmoved" : "MOVED",
|
|
(void *)before, (void *)arena);
|
|
printf("arena bytes altered by the GC: %zu of %u\n", bad, ARENA);
|
|
|
|
bad = 0;
|
|
for (i = 0; i < 64; i++)
|
|
if (interior[i] != (uint64_t *)(arena + i * 4096)) bad++;
|
|
printf("raw interior pointers invalidated: %zu of 64\n", bad);
|
|
printf("spike: %s\n", bad == 0 ? "foreign memory is invisible to the collector"
|
|
: "FOREIGN MEMORY WAS DISTURBED");
|
|
free(arena);
|
|
return 0;
|
|
}
|