feat(v4.0.0): the wait replaces looking and the console release; step 6d written up

The engine's console release (v4_fabric_interrupt_error), a device's
pending, and the count of words since a look are removed: nothing uses
them.  MESH.md 4.1, 7b and 7c say what was built, where it differs from
what was approved and why, and what is still a limit; step 6d is marked
built, with the defects found on the way.

make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots.  POST 538 of 538, word_count=317,
dict_hash=0x6a39c0bb9d183418 on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 01:24:09 -04:00
co-authored by Claude Opus 5.5
parent ec817a8264
commit 2b587206ee
13 changed files with 638 additions and 181 deletions
+8 -2
View File
@@ -141,8 +141,14 @@ sixteenth to pass it on, sends a NACK to whoever waits on it; `(REFUSED)`
counts them. `AWAIT` ends in an error if the answer is not going to come:
"Message refused", "Node gone", or "Interrupted" when a line is typed at
the console. `n KILL` on Hera removes node n and tells every other node it
is gone; a node believes that only from its centre. A stuck node can hold
up its neighbours until it is killed: `MESH.md` 7b.7 says how.
is gone; a node believes that only from its centre.
**The wait** (`MESH.md` 7c, step 6d). A node does not write the first word
of a message: it offers it, and sleeps until it is taken or a word comes
for it. What a node has to pass on, the answers it has to give and the
refusals it owes all wait with it, offered, and go when they can. So a
node that is stuck holds up no other: only text that must itself send to
the stuck node waits, and on Hera a typed line ends that.
**Blocks** (`MESH.md` step 6). The block words are the kernel's, as they
are for a v3 VM: `BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS` and
-15
View File
@@ -26,7 +26,6 @@ typedef struct {
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
void *self;
int (*pending)(void *self); /* 1 if it has a word to give that has not been asked for; may be 0: it never says */
} v4_device;
/* One end of a wire. */
@@ -56,7 +55,6 @@ typedef struct {
v4_place *place;
unsigned capacity;
v4_cell gone_error; /* see v4_fabric_gone_error; 0: none */
v4_cell interrupt_error; /* see v4_fabric_interrupt_error; 0: none */
} v4_fabric;
/* An empty fabric with room for `capacity` nodes, in `places`. */
@@ -73,19 +71,6 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity);
* is how a fabric begins, nothing is raised. */
void v4_fabric_gone_error(v4_fabric *f, v4_cell code);
/* A NODE BLOCKED WRITING TO A NODE THAT DOES NOT READ. Of two neighbours
* one may write to the other without looking (docs/v4.0.0/MESH.md 7a), and
* is then blocked until the other reads; if the other never does, it is
* blocked for ever, and if it is the node the console is on, nothing can be
* typed to it again. So: when a node that can take an error is blocked
* writing to another node, on the first word of a message (node.h,
* v4_node_words_since_look: nothing of the message has gone), and a device
* on one of its own ports says it has something pending for it, the error
* `code` is raised on the node: it gives up that message and goes on, to
* read what the device has. Only a device with a `pending` function can
* do this. With `code` 0, which is how a fabric begins, it never happens. */
void v4_fabric_interrupt_error(v4_fabric *f, v4_cell code);
/* More room: `places`, of `capacity` no less than before, takes over; every
* node keeps its number and its wiring. */
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
-5
View File
@@ -65,11 +65,6 @@ typedef struct {
* port". */
#define V4_ERROR_NO_ONE 18
/* The nucleus's error for a node let go of a message it was blocked on the
* first word of, because its console has a line for it (fabric.h,
* v4_fabric_interrupt_error): "Interrupted". */
#define V4_ERROR_INTERRUPTED 21
/* The description of the nucleus built from v4/capsule (the generated file
* defines it). */
extern const v4_image v4_capsule_image;
-8
View File
@@ -113,7 +113,6 @@ typedef struct {
unsigned last_from; /* the port the last read from "any" took its word from */
unsigned writers; /* bit k: the neighbour on port k is blocked writing to this node */
unsigned readers; /* bit k: the neighbour on port k is blocked reading from this node */
unsigned since_look; /* words taken from this node since it last fetched either of those */
unsigned offers; /* bit k: a word is offered on port k (the wait: docs/v4.0.0/MESH.md 7c) */
v4_cell offer[V4_PORTS]; /* the words offered */
int waiting; /* non-zero: the read it is blocked in is the wait */
@@ -225,13 +224,6 @@ void v4_node_port_attach(v4_node *n, v4_cell base);
/* The neighbour has taken what the node wrote. */
void v4_node_port_served(v4_node *n);
/* How many words the node has had taken since it last looked at its
* neighbours (a fetch of either of the two status addresses). A node that
* looks before it begins a message (MESH.md 7a) and is blocked writing with
* this at 0 is blocked on the first word of the message: nothing of it has
* gone. */
unsigned v4_node_words_since_look(const v4_node *n);
/* THERE IS NO ONE ON THE PORT the node is blocked at -- writing to it, or
* reading from that one port -- and there will not be. A node that can
* take an error (it has an error register and a fault table: node.h,
-23
View File
@@ -18,7 +18,6 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity)
f->place = places;
f->capacity = capacity;
f->gone_error = 0;
f->interrupt_error = 0;
for (i = 0; i < capacity; i++) clear_place(&places[i]);
}
@@ -27,10 +26,6 @@ void v4_fabric_gone_error(v4_fabric *f, v4_cell code)
f->gone_error = code;
}
void v4_fabric_interrupt_error(v4_fabric *f, v4_cell code)
{
f->interrupt_error = code;
}
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity)
{
@@ -239,24 +234,6 @@ unsigned v4_fabric_step(v4_fabric *f)
}
}
/* a node blocked on the first word of a message to a node that is not
* reading, while a device of its own has something for it */
if (f->interrupt_error != 0)
for (i = 0; i < f->capacity; i++) {
v4_node *n;
unsigned k;
if (!awake(f, i)) continue;
n = &f->place[i].node->n;
if (!n->asking || f->place[i].wire[n->ask_port].kind != V4_WIRE_NODE || v4_node_words_since_look(n) != 0) continue;
for (k = 0; k < V4_PORTS; k++) {
const v4_wire *w = &f->place[i].wire[k];
if (w->kind == V4_WIRE_DEVICE && w->device->pending && w->device->pending(w->device->self)) {
done += (unsigned)v4_node_port_gone(n, f->interrupt_error);
break;
}
}
}
/* a node blocked at one port that nothing is wired to */
if (f->gone_error != 0)
for (i = 0; i < f->capacity; i++) {
+2 -8
View File
@@ -126,8 +126,8 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
{
int port = v4_node_port_index(n, addr);
if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from;
if (port == (int)V4_PORTS + 2) { n->since_look = 0; return (v4_cell)n->writers; }
if (port == (int)V4_PORTS + 3) { n->since_look = 0; return (v4_cell)n->readers; }
if (port == (int)V4_PORTS + 2) return (v4_cell)n->writers;
if (port == (int)V4_PORTS + 3) return (v4_cell)n->readers;
if (port == (int)V4_PORT_WAIT) {
/* the wait: the word that came, or 0 for an offer taken */
if (!n->given) return 0;
@@ -193,7 +193,6 @@ void v4_node_port_attach(v4_node *n, v4_cell base)
n->last_from = 0;
n->writers = 0;
n->readers = 0;
n->since_look = 0;
n->offers = 0;
n->waiting = 0;
n->offer_asked = 0;
@@ -273,17 +272,12 @@ void v4_node_born(v4_node *n, v4_cell base)
void v4_node_port_served(v4_node *n)
{
n->asking = 0;
n->since_look++;
if (n->offer_asked) { /* what was served was an offer: the wait is over */
n->offer_asked = 0;
v4_node_port_give(n, V4_PORTS + n->ask_port, 0);
}
}
unsigned v4_node_words_since_look(const v4_node *n)
{
return n->since_look;
}
int v4_node_port_gone(v4_node *n, v4_cell code)
{
-60
View File
@@ -70,10 +70,6 @@ static void count_for_ever(void)
v4_asm_branch(&as, V4_OP_JUMP, again);
}
/* a device that says whether it has something pending */
static int *dev_has;
static int device_has(void *self) { (void)self; return *dev_has; }
/* Let time pass until nothing more can happen, or `max` steps. */
static unsigned settle(unsigned max)
{
@@ -392,62 +388,6 @@ int main(void)
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
/* ---- a node blocked on the first word of a message, while a device of its own has something for it ---- */
{
static int has; /* what the device says it has pending */
static stream none;
static v4_device dev;
v4_fabric_init(&f, places, 4);
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
a = loaded(0); /* looks about, then writes two words to its port 0 */
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
b = loaded(1); /* never reads */
LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a writer that looks first, wired to a node that never reads");
dev.take = stream_take; dev.give = 0; dev.self = &none; dev.pending = device_has; has = 1; dev_has = &has;
CHECK(v4_fabric_wire_device(&f, a, 5, &dev), "and to a device, which has something for it");
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.request == 1 && v4_node_words_since_look(&ND(a)->n) == 0, "the writer is blocked on its first word, and waits: the fabric has no error for it");
v4_fabric_interrupt_error(&f, 21);
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "nor is a bare node let go when it has: it cannot take an error");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
has = 0;
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a node that can take one waits all the same while the device has nothing for it");
has = 1;
(void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 21 && ND(a)->n.mem[OUT + 20] == 21,
"when it has, the writer is let go and the error raised on it");
/* once the first word has gone, it is not let go: the message must be finished */
v4_fabric_init(&f, places, 4);
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
a = loaded(0);
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
b = loaded(1); /* reads one word, and no more */
LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire_device(&f, a, 5, &dev), "the same, to a node that reads one word and no more");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
v4_fabric_interrupt_error(&f, 21);
for (i = 0; i < 60; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits");
}
/* ---- the wait (MESH.md 7c): a node offers a word and sleeps ---- */
{
static stream none;
+1 -2
View File
@@ -71,8 +71,7 @@ static int console_take(void *self, v4_cell value)
}
return 1;
}
static int console_pending(void *self) { (void)self; return going_at < going.count; }
static const v4_device console = { console_take, console_give, 0, console_pending };
static const v4_device console = { console_take, console_give, 0 };
static int is_empty(const char *s) { return s[0] == 0; }
+3 -5
View File
@@ -140,7 +140,7 @@ static int host_born(void *self)
v4_node_fault_attach(&pool[k].n, w_fault);
v4_blocks_init(&node_blocks[k], BUF0_W);
if (k != 0) { /* Hera's kernel is wired where she is made */
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k];
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
}
return place;
@@ -194,7 +194,7 @@ static int kernel_take(void *self, v4_cell value) /* a request */
if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;
return 1;
}
static const v4_device kernel = { kernel_take, kernel_give, 0, 0 };
static const v4_device kernel = { kernel_take, kernel_give, 0 };
/* ---- the console: a device on Hera's port 1 ---- */
#define CONSOLE_ID 1
@@ -237,8 +237,7 @@ static int console_take(void *self, v4_cell value)
}
return 1;
}
static int console_pending(void *self) { (void)self; return going_at < going.count; }
static const v4_device console = { console_take, console_give, 0, console_pending };
static const v4_device console = { console_take, console_give, 0 };
/* Is there a node that will execute at the next step? A step in which a
* node only faulted counts nothing done (exec.h), and it goes on from its
@@ -384,7 +383,6 @@ int main(void)
/* ---- Hera is born empty, and takes in the nucleus ---- */
v4_fabric_init(&f, places, PLACES);
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
v4_fabric_interrupt_error(&f, V4_ERROR_INTERRUPTED);
place = host_born(0);
CHECK(place == 0, "a node is born");
hera_place = (unsigned)place;