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:
co-authored by
Claude Opus 5.5
parent
ec817a8264
commit
2b587206ee
+8
-2
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user