From d15434231496ec1b740d04c6ad783d6c51ff62a1 Mon Sep 17 00:00:00 2001 From: rajames Date: Fri, 16 Oct 2026 09:58:03 -0400 Subject: [PATCH] feat(v4): the kernel keeps a registry of nodes and checks who may ask (MESH.md 9a.4) Any node may birth and wire its own; only Hera sleeps, wakes and kills, by number. Requests have fixed numbers, -6 to -18. NODE-BORN takes the number; NODE-KILL a number. test_manage.c: 41 checks at both widths. Co-Authored-By: Claude Opus 5.5 --- capsules/v4/hera.4th | 4 +- v4/include/v4/manage.h | 141 ++++++++++++++++++-------------- v4/src/manage.c | 165 +++++++++++++++++++++++++++----------- v4/tests/test_host_unit.c | 24 +++--- v4/tests/test_manage.c | 159 ++++++++++++++++++++++++++++++++++++ 5 files changed, 375 insertions(+), 118 deletions(-) create mode 100644 v4/tests/test_manage.c diff --git a/capsules/v4/hera.4th b/capsules/v4/hera.4th index 1cb35672..66ada367 100644 --- a/capsules/v4/hera.4th +++ b/capsules/v4/hera.4th @@ -57,7 +57,7 @@ Block 8003 ( this one: empty, then the nucleus, then FORTH-79. ) : BIRTH ( number port -- place ) (KIDP) ! (KID#) ! - NODE-BORN DUP 0< IF (FAIL) THEN (KID) ! + (KID#) @ NODE-BORN DUP 0< IF (FAIL) THEN (KID) ! NODE-ME (KIDP) @ (KID) @ 2 NODE-WIRE 0= IF (FAIL) THEN (NUCLEUS) (KID#) @ (KIDP) @ ROUTE (KID#) @ (KIDP) @ NEIGHBOUR (WHO) @@ -105,7 +105,7 @@ Block 8006 : KILL ( n -- ) DUP (WHICH) DUP 0< IF DROP DROP ." KILL: no such node" CR -1 NODE-ERROR ! THEN - DUP (KID@) 1+ @ NODE-KILL (UNLIST) + OVER NODE-KILL (UNLIST) DUP NO-ROUTE (KIDS#) @ DUP IF 0 DO DUP I (KID@) @ GONE LOOP ELSE DROP THEN DROP ; diff --git a/v4/include/v4/manage.h b/v4/include/v4/manage.h index c030cdaf..e5520f6f 100644 --- a/v4/include/v4/manage.h +++ b/v4/include/v4/manage.h @@ -1,44 +1,43 @@ -/* manage.h -- what Hera asks of whoever holds the fabric. - * docs/v4.0.0/MESH.md section 9. +/* manage.h -- what a node asks of whoever holds the fabric about nodes and + * capsules. docs/v4.0.0/MESH.md sections 9 and 9a. * - * Hera decides which nodes exist and are awake (MESH.md ruling 7). She is - * a node like any other and cannot make one: she asks, by a write to the - * port where her requests go (ENGINE.md 3.3), and the write blocks her until - * she is answered. Each request is a word on her, made with KERNEL-WORD - * (capsules/v4/sys/compile.f18); its arguments are what it finds on her data - * stack and its results are what is left there. + * Births follow v3 (MESH.md 9): any node may birth, on its own ports, and is + * its child's parent; only Hera may put a node to sleep, wake it or remove + * it. The kernel keeps a registry of the live nodes -- each one's number, + * place and parent -- as v3's kernel keeps its registry of VMs, and checks + * every request against it. A request that is refused is error 12 on the + * node that asked, and changes nothing. * - * These are the requests, in order. The n-th is request `first + n`, where - * `first` is whatever number the host gives the first of them: + * Each request is a word on the node made with KERNEL-WORD (capsules/v4/ + * centre.4th, hera.4th), whose number is fixed, below the block requests' + * (v4/blocks.h): * - * NODE-ME ( -- place ) the place of the node that asks - * NODE-BORN ( -- place | -1 ) a node is born, empty, listening at - * its ports; -1: there is no room - * NODE-WIRE ( a pa b pb -- flag ) port pa of the node at a is wired - * to port pb of the node at b - * NODE-UNWIRE ( a pa -- ) what is on that port is cut - * NODE-SLEEP ( place -- ) it executes nothing until woken - * NODE-WAKE ( place -- ) - * NODE-KILL ( place -- ) it is removed, whatever it was doing - * NODE-PARITY ( place number -- ) the host records the node's parity: - * its number and its dictionary hash - * CAPSULE-OPEN ( baddr u -- flag ) the capsule of that name is found - * and checked, and is read from its - * start; 0: there is none, or it is - * not what it says it is - * CAPSULE-CELL ( -- w flag ) the next cell of a capsule of F18 - * code; 0 0 when there are no more - * CAPSULE-LINE ( baddr -- u | -1 ) the next line of a capsule of - * FORTH blocks, put at baddr: at - * most 64 characters; -1 at its end + * -6 NODE-ME ( -- place ) any node + * -7 NODE-BORN ( number -- place | -1 ) any node in the registry: a node + * is born, empty, and entered with + * the asker as its parent; a number + * that is live, or below 1, is + * refused; -1: no room + * -8 NODE-WIRE ( a pa b pb -- flag ) a and b each the asker or a node + * it birthed + * -9 NODE-UNWIRE ( a pa -- ) a the asker or a node it birthed + * -10 NODE-SLEEP ( number -- ) Hera + * -11 NODE-WAKE ( number -- ) Hera + * -12 NODE-KILL ( number -- ) Hera; not herself; a number not + * live does nothing. The node's + * children are not removed, as v3. + * -13 NODE-PARITY ( place number -- ) for a node the asker birthed, + * under the number it was born as + * -14 CAPSULE-OPEN ( baddr u -- flag ) any node in the registry: the + * capsule is found, checked and + * read from its start by that node + * -15 CAPSULE-CELL ( -- w flag ) the next cell of the asker's + * -16 CAPSULE-LINE ( baddr -- u | -1 ) the next line of the asker's + * -17 NODE-CHILD ( number i -- child | 0 ) any node: the i-th live child + * -18 NODE-PLACE ( number -- place | -1 ) any node * - * A place is where a node is in the fabric (fabric.h); it is the host's - * name for the node. A node's number, which messages are addressed by, is - * the nodes' own affair: Hera gives a newborn its number by telling it. - * - * Only the node the host has wired to this is answered, so only Hera's - * requests are honoured. The unit rule (MESH.md ruling 5) is not here: it - * is what Hera does with these, in her capsule. + * A place is where a node is in the fabric (fabric.h): the host's name for + * it. A number is the nodes' own name for it, which messages go by. * * Nothing here uses the C library: the bare-metal kernel links this file. */ @@ -47,35 +46,61 @@ #include "v4/fabric.h" -#define V4_MANAGE_WORDS 11 -extern const char *const v4_manage_words[V4_MANAGE_WORDS]; -#define V4_MANAGE_LINE 64 /* the most characters in a line of a block */ +#define V4_REQ_NODE_ME (-6) +#define V4_REQ_NODE_BORN (-7) +#define V4_REQ_NODE_WIRE (-8) +#define V4_REQ_NODE_UNWIRE (-9) +#define V4_REQ_NODE_SLEEP (-10) +#define V4_REQ_NODE_WAKE (-11) +#define V4_REQ_NODE_KILL (-12) +#define V4_REQ_NODE_PARITY (-13) +#define V4_REQ_CAPSULE_OPEN (-14) +#define V4_REQ_CAPSULE_CELL (-15) +#define V4_REQ_CAPSULE_LINE (-16) +#define V4_REQ_NODE_CHILD (-17) +#define V4_REQ_NODE_PLACE (-18) + +#define V4_MANAGE_WORDS 13 /* -6 down to -18 */ +extern const char *const v4_manage_words[V4_MANAGE_WORDS]; /* [k] is request -6 - k */ +#define V4_MANAGE_LINE 64 /* the most characters in a line of a block */ + +/* A live node: number 0 marks a free entry. */ +typedef struct { + v4_cell number; + v4_cell parent; /* the number of the node that birthed it; 0: none */ + unsigned place; +} v4_manage_entry; typedef struct { - v4_fabric *f; - - /* A node is born: the host finds it room, adds it to the fabric and - * gives it what a node's hardware gives it. Its place, or -1. */ + v4_fabric *f; + v4_manage_entry *reg; /* the registry: room the owner gives */ + unsigned reg_cap; + unsigned hera; /* Hera's place: the one node that may sleep, wake and kill */ + /* A node is born: the host finds it room, adds it to the fabric and gives + * it what a node's hardware gives it. Its place, or -1. */ int (*born)(void *self); /* The node that was at `place` has been removed: its room is free. */ void (*gone)(void *self, unsigned place, v4_fabric_node *was); - /* The capsule called `name`, `len` characters: found, its hash and - * signature checked, and to be read from its start. 1, or 0. */ - int (*open)(void *self, const char *name, unsigned len); - /* The next cell of the open capsule: 1 with it in *w, 0 at its end. */ - int (*cell)(void *self, v4_cell *w); - /* The next line of the open capsule that is part of a block, into - * `text`, at most V4_MANAGE_LINE characters: its length, or -1. */ - int (*line)(void *self, char *text); - /* The node at `place`, which Hera calls `number`: record its parity. */ + /* For the node at `asker`: the capsule called `name`, found, its hash + * and signature checked, and to be read from its start. 1, or 0. */ + int (*open)(void *self, unsigned asker, const char *name, unsigned len); + /* The next cell of the capsule `asker` has open: 1 with it in *w, 0 at its end. */ + int (*cell)(void *self, unsigned asker, v4_cell *w); + /* The next line of it that is part of a block, at most V4_MANAGE_LINE characters: its length, or -1. */ + int (*line)(void *self, unsigned asker, char *text); + /* The node at `place`, numbered `number`: record its parity. */ void (*parity)(void *self, unsigned place, v4_cell number, const v4_node *n); void *self; } v4_manage; -/* The node at `place` has written request `n` (0 is NODE-ME) to the port - * its requests go to. Its arguments are taken from its data stack and its - * results left there. Returns 1; 0 if `n` is not one of the requests, and - * then nothing has been done. */ -int v4_manage_serve(const v4_manage *m, unsigned place, unsigned n); +/* Enter a node made by the owner -- Hera -- in the registry. 1; 0 if the + * number is below 1, already live, or there is no room. */ +int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell parent); + +/* The node at `place` has written `request` to the port its requests go to. + * 0 if it is not one of these, and nothing is done; otherwise 1: it was + * served, its arguments taken from the node's data stack and its results + * left there, or it was refused and error 12 raised on the node. */ +int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request); #endif /* V4_MANAGE_H */ diff --git a/v4/src/manage.c b/v4/src/manage.c index 7d56b67a..23c5a528 100644 --- a/v4/src/manage.c +++ b/v4/src/manage.c @@ -1,4 +1,4 @@ -/* manage.c -- what Hera asks of whoever holds the fabric. See manage.h. +/* manage.c -- what a node asks of whoever holds the fabric. See manage.h. * * Nothing here uses the C library: the bare-metal kernel links this file. */ @@ -6,11 +6,11 @@ const char *const v4_manage_words[V4_MANAGE_WORDS] = { "NODE-ME", "NODE-BORN", "NODE-WIRE", "NODE-UNWIRE", "NODE-SLEEP", "NODE-WAKE", "NODE-KILL", "NODE-PARITY", - "CAPSULE-OPEN", "CAPSULE-CELL", "CAPSULE-LINE" + "CAPSULE-OPEN", "CAPSULE-CELL", "CAPSULE-LINE", "NODE-CHILD", "NODE-PLACE" }; /* A character of the node's memory: four to a cell, the first lowest, as - * the nucleus keeps text (capsules/v4/sys/core.f18). */ + * the system keeps text (capsules/v4/sys/core.f18). */ static int in_memory(v4_cell baddr) { return baddr >= 0 && (baddr >> 2) < (v4_cell)V4_NODE_WORDS; } static char char_at(const v4_node *n, v4_cell baddr) { @@ -26,82 +26,149 @@ static void char_put(v4_node *n, v4_cell baddr, char c) static unsigned place_of(v4_cell x) { return x < 0 ? ~0u : (unsigned)x; } -int v4_manage_serve(const v4_manage *m, unsigned place, unsigned request) +static v4_manage_entry *by_number(const v4_manage *m, v4_cell number) { - v4_fabric_node *asker = v4_fabric_node_at(m->f, place); + unsigned k; + if (number < 1) return 0; + for (k = 0; k < m->reg_cap; k++) if (m->reg[k].number == number) return &m->reg[k]; + return 0; +} +static v4_manage_entry *by_place(const v4_manage *m, unsigned place) +{ + unsigned k; + for (k = 0; k < m->reg_cap; k++) if (m->reg[k].number != 0 && m->reg[k].place == place) return &m->reg[k]; + return 0; +} +/* the place is the asker's own, or that of a node it birthed */ +static int mine(const v4_manage *m, const v4_manage_entry *asker, unsigned place) +{ + const v4_manage_entry *e = by_place(m, place); + return asker && e && (e == asker || e->parent == asker->number); +} +/* error 12 on the node that asked: a store to NODE-ERROR raises it (D-18) */ +static int refuse(v4_node *n) { v4_node_store(n, n->error_reg, 12); return 1; } + +int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell parent) +{ + unsigned k; + if (number < 1 || by_number(m, number)) return 0; + for (k = 0; k < m->reg_cap; k++) { + if (m->reg[k].number == 0) { + m->reg[k].number = number; m->reg[k].parent = parent; m->reg[k].place = place; + return 1; + } + } + return 0; +} + +int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request) +{ + v4_fabric_node *at = v4_fabric_node_at(m->f, place); + v4_manage_entry *me, *e; v4_node *n; v4_cell a, b, c, d; - if (!asker || request >= V4_MANAGE_WORDS) return 0; - n = &asker->n; + if (!at || request > V4_REQ_NODE_ME || request < V4_REQ_NODE_PLACE) return 0; + n = &at->n; + me = by_place(m, place); switch (request) { - case 0: /* NODE-ME ( -- place ) */ + case V4_REQ_NODE_ME: /* ( -- place ) */ v4_dstack_push(&n->ds, (v4_cell)place); - break; - case 1: /* NODE-BORN ( -- place | -1 ) */ - v4_dstack_push(&n->ds, (v4_cell)(m->born ? m->born(m->self) : -1)); - break; - case 2: /* NODE-WIRE ( a pa b pb -- flag ) */ - d = v4_dstack_pop(&n->ds); c = v4_dstack_pop(&n->ds); b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); - v4_dstack_push(&n->ds, v4_fabric_wire(m->f, place_of(a), place_of(b), place_of(c), place_of(d)) ? -1 : 0); - break; - case 3: /* NODE-UNWIRE ( a pa -- ) */ - b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); - (void)v4_fabric_unwire(m->f, place_of(a), place_of(b)); - break; - case 4: /* NODE-SLEEP ( place -- ) */ + return 1; + case V4_REQ_NODE_BORN: { /* ( number -- place | -1 ) */ + int p; a = v4_dstack_pop(&n->ds); - v4_fabric_sleep(m->f, place_of(a)); - break; - case 5: /* NODE-WAKE ( place -- ) */ - a = v4_dstack_pop(&n->ds); - v4_fabric_wake(m->f, place_of(a)); - break; - case 6: { /* NODE-KILL ( place -- ) */ - v4_fabric_node *was; - a = v4_dstack_pop(&n->ds); - if (place_of(a) == place) break; /* not itself: there would be no one to answer */ - was = v4_fabric_remove(m->f, place_of(a)); - if (was && m->gone) m->gone(m->self, place_of(a), was); - break; + if (!me || a < 1 || by_number(m, a)) return refuse(n); + p = m->born ? m->born(m->self) : -1; + if (p >= 0 && !v4_manage_enter(m, a, (unsigned)p, me->number)) { /* no room in the registry: the node goes again */ + v4_fabric_node *was = v4_fabric_remove(m->f, (unsigned)p); + if (was && m->gone) m->gone(m->self, (unsigned)p, was); + p = -1; + } + v4_dstack_push(&n->ds, (v4_cell)p); + return 1; } - case 7: { /* NODE-PARITY ( place number -- ) */ + case V4_REQ_NODE_WIRE: /* ( a pa b pb -- flag ) */ + d = v4_dstack_pop(&n->ds); c = v4_dstack_pop(&n->ds); b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!mine(m, me, place_of(a)) || !mine(m, me, place_of(c))) return refuse(n); + v4_dstack_push(&n->ds, v4_fabric_wire(m->f, place_of(a), place_of(b), place_of(c), place_of(d)) ? -1 : 0); + return 1; + case V4_REQ_NODE_UNWIRE: /* ( a pa -- ) */ + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!mine(m, me, place_of(a))) return refuse(n); + (void)v4_fabric_unwire(m->f, place_of(a), place_of(b)); + return 1; + case V4_REQ_NODE_SLEEP: /* ( number -- ) */ + case V4_REQ_NODE_WAKE: + case V4_REQ_NODE_KILL: + a = v4_dstack_pop(&n->ds); + if (place != m->hera) return refuse(n); + e = by_number(m, a); + if (!e || e->place == place) return 1; /* not live, or Hera herself: nothing */ + if (request == V4_REQ_NODE_SLEEP) v4_fabric_sleep(m->f, e->place); + else if (request == V4_REQ_NODE_WAKE) v4_fabric_wake(m->f, e->place); + else { + unsigned gone_place = e->place; + v4_fabric_node *was = v4_fabric_remove(m->f, gone_place); + e->number = 0; + if (was && m->gone) m->gone(m->self, gone_place, was); + } + return 1; + case V4_REQ_NODE_PARITY: { /* ( place number -- ) */ v4_fabric_node *it; b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); - it = v4_fabric_node_at(m->f, place_of(a)); - if (it && m->parity) m->parity(m->self, place_of(a), b, &it->n); - break; + e = by_place(m, place_of(a)); + if (!me || !e || e == me || e->parent != me->number || e->number != b) return refuse(n); + it = v4_fabric_node_at(m->f, e->place); + if (it && m->parity) m->parity(m->self, e->place, b, &it->n); + return 1; } - case 8: { /* CAPSULE-OPEN ( baddr u -- flag ) */ + case V4_REQ_CAPSULE_OPEN: { /* ( baddr u -- flag ) */ char name[64]; unsigned i, len; b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!me) return refuse(n); len = b < 0 ? 0u : (unsigned)b; if (len == 0 || len > sizeof name || !in_memory(a) || !in_memory(a + (v4_cell)len - 1) || !m->open) { v4_dstack_push(&n->ds, 0); - break; + return 1; } for (i = 0; i < len; i++) name[i] = char_at(n, a + (v4_cell)i); - v4_dstack_push(&n->ds, m->open(m->self, name, len) ? -1 : 0); - break; + v4_dstack_push(&n->ds, m->open(m->self, place, name, len) ? -1 : 0); + return 1; } - case 9: /* CAPSULE-CELL ( -- w flag ) */ + case V4_REQ_CAPSULE_CELL: /* ( -- w flag ) */ + if (!me) return refuse(n); a = 0; - if (m->cell && m->cell(m->self, &a)) { v4_dstack_push(&n->ds, a); v4_dstack_push(&n->ds, -1); } + if (m->cell && m->cell(m->self, place, &a)) { v4_dstack_push(&n->ds, a); v4_dstack_push(&n->ds, -1); } else { v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, 0); } - break; - case 10: { /* CAPSULE-LINE ( baddr -- u | -1 ) */ + return 1; + case V4_REQ_CAPSULE_LINE: { /* ( baddr -- u | -1 ) */ char text[V4_MANAGE_LINE]; int len, i; a = v4_dstack_pop(&n->ds); - len = m->line ? m->line(m->self, text) : -1; + if (!me) return refuse(n); + len = m->line ? m->line(m->self, place, text) : -1; if (len > V4_MANAGE_LINE) len = V4_MANAGE_LINE; if (len > 0 && (!in_memory(a) || !in_memory(a + len - 1))) len = -1; for (i = 0; i < len; i++) char_put(n, a + i, text[i]); v4_dstack_push(&n->ds, (v4_cell)len); - break; + return 1; } + case V4_REQ_NODE_CHILD: { /* ( number i -- child | 0 ) */ + unsigned k; + v4_cell found = 0; + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + for (k = 0; k < m->reg_cap && !found; k++) + if (m->reg[k].number != 0 && m->reg[k].parent == a && b-- == 0) found = m->reg[k].number; + v4_dstack_push(&n->ds, found); + return 1; + } + default: /* V4_REQ_NODE_PLACE ( number -- place | -1 ) */ + a = v4_dstack_pop(&n->ds); + e = by_number(m, a); + v4_dstack_push(&n->ds, e ? (v4_cell)e->place : -1); + return 1; } - return 1; } diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c index 48ff5f16..42530a58 100644 --- a/v4/tests/test_host_unit.c +++ b/v4/tests/test_host_unit.c @@ -162,23 +162,23 @@ static void host_gone(void *self, unsigned place, v4_fabric_node *was) (void)self; (void)place; pool_used[was - pool] = 0; } -static int host_open(void *self, const char *name, unsigned len) +static int host_open(void *self, unsigned asker, const char *name, unsigned len) { - (void)self; + (void)self; (void)asker; if (len == 17 && memcmp(name, "v4:nucleus-64.f18", 17) == 0) { hera_reads.is_f18 = 1; hera_reads.cells_at = 0; return 1; } if (len == 14 && memcmp(name, "v4:forth79.4th", 14) == 0) return read_file(&hera_reads, "forth79.4th"); return 0; } -static int host_cell(void *self, v4_cell *w) +static int host_cell(void *self, unsigned asker, v4_cell *w) { - (void)self; + (void)self; (void)asker; if (!hera_reads.is_f18 || hera_reads.cells_at >= f18_count) return 0; *w = f18[hera_reads.cells_at++]; return 1; } -static int host_line(void *self, char *text) +static int host_line(void *self, unsigned asker, char *text) { - (void)self; + (void)self; (void)asker; return hera_reads.is_f18 ? -1 : next_line(&hera_reads, text); } static void host_parity(void *self, unsigned place, v4_cell number, const v4_node *n) @@ -187,7 +187,8 @@ static void host_parity(void *self, unsigned place, v4_cell number, const v4_nod if (parity_count < 8) { parity_number[parity_count] = number; parity_hash[parity_count] = v4_image_dict_hash(n, &im); } parity_count++; } -static const v4_manage manage = { &f, host_born, host_gone, host_open, host_cell, host_line, host_parity, 0 }; +static v4_manage manage; +static v4_manage_entry registry[PLACES]; /* the kernel's registry of live nodes (MESH.md 9a.4) */ /* ---- the kernel: a device on Hera's port 0 ---- */ #define FIRST_REQUEST 1 /* the number of NODE-ME on her */ @@ -203,7 +204,7 @@ static int kernel_take(void *self, v4_cell value) /* a request */ { (void)self; if (v4_blocks_serve(&node_blocks[0], &pool[0].n, value)) return 1; /* a block, as for any node; Hera is the first in the pool */ - if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++; + if (!v4_manage_serve(&manage, hera_place, value)) refused++; return 1; } static const v4_device kernel = { kernel_take, kernel_give, 0 }; @@ -378,6 +379,9 @@ int main(void) printf(" not run at this width: the capsules are the 64-bit products'\n"); return 0; } + manage.f = &f; manage.reg = registry; manage.reg_cap = PLACES; + manage.born = host_born; manage.gone = host_gone; manage.open = host_open; + manage.cell = host_cell; manage.line = host_line; manage.parity = host_parity; CHECK(storage_made(), "the kernel's blocks are there before any node is"); CHECK(rom_build_system(&nucleus, 0), "the host's machine assembles the system with its ROM"); if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; } @@ -396,6 +400,7 @@ int main(void) place = host_born(0); CHECK(place == 0, "a node is born"); hera_place = (unsigned)place; + manage.hera = hera_place; for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { } CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports"); CHECK(v4_fabric_wire_kernel(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1"); @@ -404,10 +409,11 @@ int main(void) "it takes in the nucleus through its port, a word at a time, and sleeps until a message comes: %u of %u cells", given_cells, f18_count); CHECK(told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE") && ended_from == 10, "it is told it is 10, and where the console is"); + CHECK(v4_manage_enter(&manage, 10, hera_place, 0), "the kernel enters her in its registry, as v3's enters Hera"); { char text[600]; size_t at = 0; - for (i = 0; i < V4_MANAGE_WORDS; i++) at += (size_t)snprintf(text + at, sizeof text - at, "%u KERNEL-WORD %s ", FIRST_REQUEST + i, v4_manage_words[i]); + for (i = 0; i < V4_MANAGE_WORDS; i++) at += (size_t)snprintf(text + at, sizeof text - at, "%d KERNEL-WORD %s ", (int)(V4_REQ_NODE_ME - (v4_cell)i), v4_manage_words[i]); CHECK(told(10, text), "and given the words that ask for nodes and capsules: \"%s\"", printed); } CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "it asks where it is, and is answered"); diff --git a/v4/tests/test_manage.c b/v4/tests/test_manage.c new file mode 100644 index 00000000..a3d0a989 --- /dev/null +++ b/v4/tests/test_manage.c @@ -0,0 +1,159 @@ +/* test_manage.c -- the kernel's side of births (docs/v4.0.0/MESH.md 9a.4): + * the registry of live nodes, and who may ask what. At the level of C: a + * request's arguments are put on the asking node's stack and the request is + * served, as the kernel does when the node writes its number to port 0. */ +#include "v4/fabric.h" +#include "v4/manage.h" +#include +#include + +static int failures = 0, checks = 0; +#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0) + +#define PB ((v4_cell)(V4_NODE_WORDS - 24u)) /* the ports */ +#define ERR ((v4_cell)(V4_NODE_WORDS - 40u)) /* NODE-ERROR */ +#define NODES 8 +static v4_fabric_node pool[NODES]; +static int used[NODES]; +static v4_place places[NODES]; +static v4_fabric f; +static v4_manage_entry reg[NODES]; +static unsigned gone_count, parity_count; +static v4_cell parity_number; +static v4_manage m; + +static int born(void *self) +{ + unsigned k; + int place; + (void)self; + for (k = 0; k < NODES && used[k]; k++) { } + if (k == NODES) return -1; + place = v4_fabric_add(&f, &pool[k], PB); + if (place < 0) return -1; + used[k] = 1; + v4_node_error_attach(&pool[k].n, ERR); + return place; +} +static void gone(void *self, unsigned place, v4_fabric_node *was) { (void)self; (void)place; used[was - pool] = 0; gone_count++; } +static int open_(void *self, unsigned asker, const char *name, unsigned len) { (void)self; (void)asker; return len == 5 && memcmp(name, "v4:ok", 5) == 0; } +static int cell_(void *self, unsigned asker, v4_cell *w) { (void)self; *w = (v4_cell)asker; return 1; } +static int line_(void *self, unsigned asker, char *text) { (void)self; (void)asker; text[0] = 'O'; text[1] = 'K'; return 2; } +static void parity_(void *self, unsigned place, v4_cell number, const v4_node *n) { (void)self; (void)place; (void)n; parity_count++; parity_number = number; } + +static v4_node *N(unsigned place) { return &v4_fabric_node_at(&f, place)->n; } +static void push(unsigned place, v4_cell x) { v4_dstack_push(&N(place)->ds, x); } +static v4_cell pop(unsigned place) { return v4_dstack_pop(&N(place)->ds); } +static unsigned depth(unsigned place) { return N(place)->ds.depth; } +/* serve a request; 1 if it raised error 12 on the asker */ +static int refused(unsigned place, v4_cell request) +{ + unsigned before = N(place)->faults; + CHECK(v4_manage_serve(&m, place, request) == 1, "request %ld is served or refused", (long)request); + return N(place)->faults == before + 1 && N(place)->fault_kind == V4_FAULT_RAISED && N(place)->fault_addr == 12; +} +static v4_cell place_of(unsigned asker, v4_cell number) { push(asker, number); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_PLACE); return pop(asker); } +static v4_cell child(unsigned asker, v4_cell number, v4_cell i) { push(asker, number); push(asker, i); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_CHILD); return pop(asker); } +/* asker births `number`: its place, or -1 */ +static v4_cell birth(unsigned asker, v4_cell number) { push(asker, number); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_BORN); return depth(asker) ? pop(asker) : -2; } +static void put_chars(unsigned place, v4_cell baddr, const char *s) +{ + unsigned i; + for (i = 0; s[i]; i++) { + v4_cell a = baddr + (v4_cell)i; + unsigned shift = 8u * (unsigned)(a & 3); + v4_ucell c = (v4_ucell)N(place)->mem[a >> 2]; + c = (c & ~((v4_ucell)0xFFu << shift)) | ((v4_ucell)(unsigned char)s[i] << shift); + N(place)->mem[a >> 2] = (v4_cell)c; + } +} + +int main(void) +{ + unsigned h, a, b, x; + v4_cell r; + + printf("v4 manage tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); + v4_fabric_init(&f, places, NODES); + memset(&m, 0, sizeof m); + m.f = &f; m.reg = reg; m.reg_cap = NODES; m.born = born; m.gone = gone; + m.open = open_; m.cell = cell_; m.line = line_; m.parity = parity_; + h = (unsigned)born(0); + m.hera = h; + + CHECK(v4_manage_serve(&m, h, -1) == 0 && v4_manage_serve(&m, h, 1) == 0 && v4_manage_serve(&m, h, -19) == 0, "a block request, a host's own word and a number past the last are not this file's"); + CHECK(v4_manage_enter(&m, 10, h, 0) == 1, "Hera is entered by whoever made her, as v3's kernel enters Hera at boot"); + CHECK(v4_manage_enter(&m, 10, h, 0) == 0 && v4_manage_enter(&m, 0, h, 0) == 0, "a number that is live, or 0, is not entered again"); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_ME) == 1 && pop(h) == (v4_cell)h, "NODE-ME is her place"); + + /* births */ + r = birth(h, 11); + CHECK(r >= 0 && place_of(h, 11) == r && child(h, 10, 0) == 11 && child(h, 10, 1) == 0, "Hera births 11: the registry has it, with her as its parent"); + a = (unsigned)r; + push(h, 11); + CHECK(refused(h, V4_REQ_NODE_BORN) && depth(h) == 0 && used[2] == 0, "a birth of a number that is live is refused, and nothing is born"); + push(h, 0); + CHECK(refused(h, V4_REQ_NODE_BORN) && depth(h) == 0, "and so is a birth of 0"); + r = birth(a, 12); + CHECK(r >= 0 && child(a, 11, 0) == 12 && child(h, 10, 0) == 11 && child(h, 10, 1) == 0, "node 11 births 12, as v3: its parent is 11, not Hera"); + b = (unsigned)r; + x = (unsigned)v4_fabric_add(&f, &pool[7], PB); used[7] = 1; v4_node_error_attach(&pool[7].n, ERR); + push(x, 13); + CHECK(refused(x, V4_REQ_NODE_BORN) && depth(x) == 0, "a node the kernel has no record of may not birth"); + + /* wiring: its own ports, or those of a node it birthed */ + push(a, (v4_cell)a); push(a, 3); push(a, (v4_cell)b); push(a, 2); + CHECK(v4_manage_serve(&m, a, V4_REQ_NODE_WIRE) == 1 && pop(a) == -1, "node 11 wires itself to its child"); + push(b, (v4_cell)a); push(b, 4); push(b, (v4_cell)h); push(b, 5); + CHECK(refused(b, V4_REQ_NODE_WIRE) && depth(b) == 0, "node 12 may not wire its parent to Hera"); + push(a, (v4_cell)h); push(a, 5); push(a, (v4_cell)a); push(a, 5); + CHECK(refused(a, V4_REQ_NODE_WIRE) && depth(a) == 0, "node 11 may not wire Hera"); + push(a, (v4_cell)h); push(a, 2); + CHECK(refused(a, V4_REQ_NODE_UNWIRE) && depth(a) == 0, "nor cut a wire of hers"); + push(a, (v4_cell)a); push(a, 3); + CHECK(v4_manage_serve(&m, a, V4_REQ_NODE_UNWIRE) == 1 && depth(a) == 0, "and cuts its own"); + + /* parity: of a node it birthed */ + push(a, (v4_cell)a); push(a, 11); + CHECK(refused(a, V4_REQ_NODE_PARITY) && parity_count == 0, "node 11 may not report its own parity"); + push(h, (v4_cell)a); push(h, 12); + CHECK(refused(h, V4_REQ_NODE_PARITY) && parity_count == 0, "nor Hera that of her child under another number"); + push(h, (v4_cell)a); push(h, 11); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_PARITY) == 1 && parity_count == 1 && parity_number == 11 && depth(h) == 0, "Hera reports her child's"); + + /* capsules: the asker's own */ + put_chars(a, 400, "v4:ok"); + push(a, 400); push(a, 5); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_OPEN) == 1 && pop(a) == -1, "node 11 opens a capsule"); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_CELL) == 1 && pop(a) == -1 && pop(a) == (v4_cell)a, "and reads its cells: the reader is the asker's"); + push(a, 400); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_LINE) == 1 && pop(a) == 2 && (N(a)->mem[100] & 0xFFFF) == ('O' | 'K' << 8), "and its lines"); + + /* sleep, wake and kill: Hera's alone, by number */ + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_SLEEP) && !places[b].asleep && depth(a) == 0, "node 11 may not put 12 to sleep"); + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_SLEEP) == 1 && places[b].asleep, "Hera may, by its number"); + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_WAKE) && places[b].asleep, "node 11 may not wake it"); + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_WAKE) == 1 && !places[b].asleep, "Hera may"); + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_KILL) && v4_fabric_node_at(&f, b) != NULL, "node 11 may not kill its own child: KILL is Hera's, as v3"); + { + unsigned faults = N(h)->faults; + push(h, 10); (void)v4_manage_serve(&m, h, V4_REQ_NODE_KILL); + push(h, 99); (void)v4_manage_serve(&m, h, V4_REQ_NODE_KILL); + CHECK(N(h)->faults == faults && gone_count == 0 && depth(h) == 0, "Hera's kill of herself, or of a number not live, does nothing"); + } + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_KILL) == 1 && gone_count == 1 && place_of(h, 12) == -1 && child(h, 11, 0) == 0, "Hera kills 12, which she did not birth: the registry forgets it"); + r = birth(a, 13); + CHECK(r >= 0, "node 11 births 13"); + push(h, 11); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_KILL) == 1 && place_of(h, 13) == r && child(h, 11, 0) == 13, "killing 11 does not kill its child, as in v3"); + CHECK(place_of(h, 12) == -1 && place_of(h, 10) == (v4_cell)h, "NODE-PLACE of a number not live is -1"); + + printf(" %d checks, %d failures\n", checks, failures); + return failures != 0; +}