The review of step 6c found it did not keep its rule. Ruled: fix them all; 16 hops. - Paying a NACK no longer waits on a lower-numbered neighbour; a node that owes one does not sleep, and goes on taking in what it is sent. - AWAIT looks first through the messages already waiting. - A node waiting to write to a removed neighbour gets error 18: the word that says who is reading also says which ports have anything on them. - A console line lets Hera go when she is blocked on the first word of a message (v4_fabric_interrupt_error, a device's `pending`). - A refused answer is told to the node that waits, not the one that answered; one owed to the node itself goes to its own queue. - A message is passed on by at most 16 nodes, then refused. - A GONE is believed only when it says it is from the node's centre. - Tests: the check that tested nothing is replaced; no room for a waiting sender; the limit that remains is tested as a limit. - MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls short; README likewise. make -C v4 test, sanitize, hosted-check; three bare-metal boots. POST 538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six. 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;
|
|
}
|