Ruled 2026-10-08. The engine, the nucleus and their tests are as they
were at dfabfa46, before step 6d; the code of the wait and its tests are
in history at 075385ab..2cf37aab. The limit of MESH.md 7b.7 stands: a
node that is stuck can hold up its neighbours until Hera kills it.
MESH.md 7c is kept as the record of the design as approved, and 7c.6 says
what was built, what the two reviews found, the cause the findings share
-- a node's message machinery runs on the stacks its text is using, and a
fault abandons whatever was in progress -- and what a second attempt
must settle before anything is built.
Kept from it, in hosted-check: a line that leaves 29 values on the stack,
and WORDS with 26 values on it. Both hung the products at some commit of
the wait.
make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots typing both. POST 538 of 538, word_count=317,
dict_hash=0xc0769523a47b7dc3 on all six: the hash of step 6c's code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
243 lines
8.2 KiB
C
243 lines
8.2 KiB
C
/* fabric.c -- nodes, wiring, and time passing for all of them. See fabric.h.
|
|
*
|
|
* Nothing here uses the C library: the bare-metal kernel links this file.
|
|
*/
|
|
#include "v4/fabric.h"
|
|
|
|
static void clear_place(v4_place *p)
|
|
{
|
|
unsigned k;
|
|
p->node = 0;
|
|
p->asleep = 0;
|
|
for (k = 0; k < V4_PORTS; k++) { p->wire[k].kind = V4_WIRE_NONE; p->wire[k].node = 0; p->wire[k].port = 0; p->wire[k].device = 0; }
|
|
}
|
|
|
|
void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity)
|
|
{
|
|
unsigned i;
|
|
f->place = places;
|
|
f->capacity = capacity;
|
|
f->gone_error = 0;
|
|
f->interrupt_error = 0;
|
|
for (i = 0; i < capacity; i++) clear_place(&places[i]);
|
|
}
|
|
|
|
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)
|
|
{
|
|
unsigned i;
|
|
if (capacity < f->capacity) return;
|
|
for (i = 0; i < f->capacity; i++) places[i] = f->place[i];
|
|
for (; i < capacity; i++) clear_place(&places[i]);
|
|
f->place = places;
|
|
f->capacity = capacity;
|
|
}
|
|
|
|
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < f->capacity && f->place[i].node; i++) { }
|
|
if (i == f->capacity || !storage) return -1;
|
|
clear_place(&f->place[i]);
|
|
f->place[i].node = storage;
|
|
v4_node_born(&storage->n, port_base);
|
|
v4_exec_reset(&storage->es);
|
|
v4_heat_reset(&storage->h);
|
|
return (int)i;
|
|
}
|
|
|
|
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id)
|
|
{
|
|
return id < f->capacity ? f->place[id].node : 0;
|
|
}
|
|
|
|
static int there(const v4_fabric *f, unsigned id, unsigned port)
|
|
{
|
|
return id < f->capacity && f->place[id].node && port < V4_PORTS;
|
|
}
|
|
|
|
/* cut what is on a port, at both ends */
|
|
static void cut(v4_fabric *f, unsigned a, unsigned pa)
|
|
{
|
|
v4_wire *w = &f->place[a].wire[pa];
|
|
if (w->kind == V4_WIRE_NODE && w->node < f->capacity && w->port < V4_PORTS) {
|
|
v4_wire *other = &f->place[w->node].wire[w->port];
|
|
if (other->kind == V4_WIRE_NODE && other->node == a && other->port == pa) { other->kind = V4_WIRE_NONE; other->device = 0; }
|
|
}
|
|
w->kind = V4_WIRE_NONE;
|
|
w->device = 0;
|
|
}
|
|
|
|
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb)
|
|
{
|
|
if (!there(f, a, pa) || !there(f, b, pb) || (a == b && pa == pb)) return 0;
|
|
cut(f, a, pa);
|
|
cut(f, b, pb);
|
|
f->place[a].wire[pa].kind = V4_WIRE_NODE; f->place[a].wire[pa].node = b; f->place[a].wire[pa].port = pb;
|
|
f->place[b].wire[pb].kind = V4_WIRE_NODE; f->place[b].wire[pb].node = a; f->place[b].wire[pb].port = pa;
|
|
return 1;
|
|
}
|
|
|
|
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device)
|
|
{
|
|
if (!there(f, a, pa) || !device) return 0;
|
|
cut(f, a, pa);
|
|
f->place[a].wire[pa].kind = V4_WIRE_DEVICE;
|
|
f->place[a].wire[pa].device = device;
|
|
return 1;
|
|
}
|
|
|
|
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa)
|
|
{
|
|
if (!there(f, a, pa)) return 0;
|
|
cut(f, a, pa);
|
|
return 1;
|
|
}
|
|
|
|
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id)
|
|
{
|
|
v4_fabric_node *storage;
|
|
unsigned k;
|
|
if (id >= f->capacity || !f->place[id].node) return 0;
|
|
for (k = 0; k < V4_PORTS; k++) cut(f, id, k);
|
|
storage = f->place[id].node;
|
|
clear_place(&f->place[id]);
|
|
return storage;
|
|
}
|
|
|
|
void v4_fabric_sleep(v4_fabric *f, unsigned id)
|
|
{
|
|
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 1;
|
|
}
|
|
|
|
void v4_fabric_wake(v4_fabric *f, unsigned id)
|
|
{
|
|
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 0;
|
|
}
|
|
|
|
static int awake(const v4_fabric *f, unsigned id)
|
|
{
|
|
return id < f->capacity && f->place[id].node && !f->place[id].asleep;
|
|
}
|
|
|
|
/* What node `a` wrote to one of its ports, handed to whatever is there. */
|
|
static unsigned hand_over_write(v4_fabric *f, unsigned a)
|
|
{
|
|
v4_node *n = &f->place[a].node->n;
|
|
const v4_wire *w = &f->place[a].wire[n->ask_port];
|
|
|
|
if (w->kind == V4_WIRE_DEVICE) {
|
|
if (w->device->take && w->device->take(w->device->self, n->request)) { v4_node_port_served(n); return 1; }
|
|
} else if (w->kind == V4_WIRE_NODE && awake(f, w->node)) {
|
|
v4_node *r = &f->place[w->node].node->n;
|
|
if (r->reading && !r->given && (r->read_port == w->port || r->read_port == V4_PORT_ANY)) {
|
|
v4_node_port_give(r, w->port, n->request);
|
|
v4_node_port_served(n);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Node `a` is reading: a device on that port, or on any of them, is asked. */
|
|
static unsigned hand_over_read(v4_fabric *f, unsigned a)
|
|
{
|
|
v4_node *n = &f->place[a].node->n;
|
|
unsigned first = n->read_port == V4_PORT_ANY ? 0 : n->read_port;
|
|
unsigned last = n->read_port == V4_PORT_ANY ? V4_PORTS - 1 : n->read_port;
|
|
unsigned k;
|
|
v4_cell value;
|
|
|
|
for (k = first; k <= last; k++) {
|
|
const v4_wire *w = &f->place[a].wire[k];
|
|
if (w->kind == V4_WIRE_DEVICE && w->device->give && w->device->give(w->device->self, &value)) {
|
|
v4_node_port_give(n, k, value);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* What node `a` can see of its neighbours before it executes: who is
|
|
* blocked writing to it, and who would take a word from it. A device is
|
|
* always ready to be written to; what it has to give, the node finds when
|
|
* it reads. */
|
|
static void look_about(v4_fabric *f, unsigned a)
|
|
{
|
|
unsigned k, writers = 0, readers = 0;
|
|
|
|
for (k = 0; k < V4_PORTS; k++) {
|
|
const v4_wire *w = &f->place[a].wire[k];
|
|
if (w->kind != V4_WIRE_NONE) readers |= 1u << (V4_PORTS + k); /* there is something on this port */
|
|
if (w->kind == V4_WIRE_DEVICE) {
|
|
if (w->device->take) readers |= 1u << k;
|
|
} else if (w->kind == V4_WIRE_NODE && awake(f, w->node)) {
|
|
const v4_node *o = &f->place[w->node].node->n;
|
|
if (o->asking && o->ask_port == w->port) writers |= 1u << k;
|
|
if (o->reading && !o->given && (o->read_port == w->port || o->read_port == V4_PORT_ANY)) readers |= 1u << k;
|
|
}
|
|
}
|
|
v4_node_port_status(&f->place[a].node->n, writers, readers);
|
|
}
|
|
|
|
unsigned v4_fabric_step(v4_fabric *f)
|
|
{
|
|
unsigned i, done = 0;
|
|
|
|
/* a node that is blocked fetches nothing, and is looked about for when
|
|
* it is next to execute */
|
|
for (i = 0; i < f->capacity; i++)
|
|
if (awake(f, i)) {
|
|
const v4_node *n = &f->place[i].node->n;
|
|
if (!n->stopped && !n->asking && !(n->reading && !n->given)) look_about(f, i);
|
|
}
|
|
|
|
for (i = 0; i < f->capacity; i++)
|
|
if (awake(f, i)) done += v4_exec_step_word(&f->place[i].node->n, &f->place[i].node->es, &f->place[i].node->h);
|
|
|
|
for (i = 0; i < f->capacity; i++)
|
|
if (awake(f, i) && f->place[i].node->n.asking) done += hand_over_write(f, i);
|
|
for (i = 0; i < f->capacity; i++)
|
|
if (awake(f, i) && f->place[i].node->n.reading && !f->place[i].node->n.given) done += hand_over_read(f, i);
|
|
|
|
/* 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++) {
|
|
v4_node *n;
|
|
if (!awake(f, i)) continue;
|
|
n = &f->place[i].node->n;
|
|
if ((n->asking && f->place[i].wire[n->ask_port].kind == V4_WIRE_NONE)
|
|
|| (n->reading && !n->given && n->read_port != V4_PORT_ANY && f->place[i].wire[n->read_port].kind == V4_WIRE_NONE))
|
|
done += (unsigned)v4_node_port_gone(n, f->gone_error);
|
|
}
|
|
|
|
return done;
|
|
}
|