The final review of step 6e, by a fresh reviewer told to break it, and what was ruled: fix them all, test first. Each has a test that failed before the mend. - Two texts from one node could be begun in the wrong order when room for their answers came between the node's look at the first and at the second. Texts whose answers go by the same port are begun in the order they came: (D-MASK), quit.v4. 102 of 700 timings were wrong before. - A word written with PORT! to a node that speaks whole messages, or to a console's port, blocked its writer for ever and cut off every node beyond it. It is error 18 now, whether that node is asleep, running or stuck: fabric.c, node.c. - A node asleep for room on one wire is woken when another of its wires is cut. - A line there was no room for on the lone node's wire was reported as a fault and ended the hosted program. It is Message refused, and the prompt comes back: V4_BOOT_LINE_REFUSED. hosted-check has the case; no bare-metal boot has typed it. - Ruled: text behind held output for the same port is refused though it would fit. MESH.md 7d.4 said otherwise and is corrected. - The removal test also cuts and re-makes a wire, and has an exchange in which output is held at a full wire: 26,814 disturbances in all. - MESH.md and v4/README.md no longer say that a stuck node holds up no other node, that only printing waits, or that no node is left waiting on one that is gone; 7d.6 lists the limits and what the products do that they did not before. make -C v4 test and sanitize at both widths, hosted-check, lint. Three bare-metal boots: logs/20261013-194827 (amd64), -195131 (aarch64), -195518 (riscv64). POST 538 of 538, word_count=317, dict_hash=0xe5a57f3eb72ff94a on all six; the three sessions are the same character for character. The boots were built with the working tree's uncommitted change to kernel/Makefile that adds the ISA to the kernel's version line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
270 lines
8.2 KiB
C
270 lines
8.2 KiB
C
/* node.c -- the v4 node. See node.h. */
|
|
#include "v4/node.h"
|
|
#include <stddef.h>
|
|
|
|
void v4_node_reset(v4_node *n)
|
|
{
|
|
n->p = 0;
|
|
n->a = 0;
|
|
n->b = 0;
|
|
for (unsigned i = 0; i < V4_NODE_WORDS; i++) n->mem[i] = 0;
|
|
v4_guard_fill(n->mem_guard_head, V4_MEM_BOUND, V4_GUARD_PATTERN_HEAD);
|
|
v4_guard_fill(n->mem_guard_tail, V4_MEM_BOUND, V4_GUARD_PATTERN_TAIL);
|
|
v4_dstack_reset(&n->ds);
|
|
v4_rstack_reset(&n->rs);
|
|
v4_node_console_attach(n, -1);
|
|
v4_node_console_input_attach(n, -1, -1);
|
|
v4_node_fault_attach(n, -1);
|
|
v4_node_stack_regs_attach(n, -1, -1);
|
|
v4_node_error_attach(n, -1);
|
|
v4_node_port_attach(n, -1);
|
|
}
|
|
|
|
void v4_node_error_attach(v4_node *n, v4_cell addr)
|
|
{
|
|
n->error_reg = addr;
|
|
}
|
|
|
|
int v4_node_addr_ok(v4_cell addr)
|
|
{
|
|
/* One unsigned compare covers both ends: a negative address is a very
|
|
* large unsigned one. */
|
|
return (v4_ucell)addr < (v4_ucell)V4_NODE_WORDS;
|
|
}
|
|
|
|
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r)
|
|
{
|
|
n->dstack_reg = d;
|
|
n->rstack_reg = r;
|
|
}
|
|
|
|
void v4_node_fault_attach(v4_node *n, v4_cell table)
|
|
{
|
|
n->fault_vector = (v4_node_addr_ok(table) && v4_node_addr_ok(table + (v4_cell)(V4_FAULT_KINDS - 1u))) ? table : (v4_cell)-1;
|
|
n->fault_addr = 0;
|
|
n->fault_kind = 0;
|
|
n->faults = 0;
|
|
n->stopped = 0;
|
|
}
|
|
|
|
void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr)
|
|
{
|
|
n->fault_kind = kind;
|
|
n->fault_addr = addr;
|
|
n->faults++;
|
|
v4_rstack_clear(&n->rs);
|
|
if (kind != V4_FAULT_RAISED) v4_dstack_clear(&n->ds);
|
|
if (n->fault_vector >= 0) n->p = n->fault_vector + (v4_cell)kind;
|
|
else n->stopped = 1;
|
|
}
|
|
|
|
int v4_node_guards_intact(const v4_node *n)
|
|
{
|
|
return v4_guard_intact(n->mem_guard_head, V4_MEM_BOUND, V4_GUARD_PATTERN_HEAD)
|
|
&& v4_guard_intact(n->mem_guard_tail, V4_MEM_BOUND, V4_GUARD_PATTERN_TAIL)
|
|
&& v4_dstack_guards_intact(&n->ds)
|
|
&& v4_rstack_guards_intact(&n->rs);
|
|
}
|
|
|
|
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
|
|
{
|
|
if (!v4_node_addr_ok(addr)) return 0;
|
|
return n->mem[(unsigned)addr];
|
|
}
|
|
|
|
void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
|
|
{
|
|
/* CONSOLE-TX: capture the character, leave memory alone. console_tx is
|
|
* -1 when no console is attached, which no valid address equals. */
|
|
if (addr == n->console_tx) {
|
|
if (n->console_len < V4_CONSOLE_CAP)
|
|
n->console[n->console_len++] = (unsigned char)((v4_ucell)value & 0xFFu);
|
|
else
|
|
n->console_dropped++;
|
|
return;
|
|
}
|
|
/* A port: the node keeps what it wrote and is blocked until its
|
|
* neighbour has taken it. */
|
|
{
|
|
int port = v4_node_port_index(n, addr);
|
|
if (port == (int)V4_PORT_WIRE) { n->wire = (value >= 0 && value < (v4_cell)V4_PORTS) ? (unsigned)value : V4_PORTS; return; }
|
|
if (port == (int)V4_PORT_A) { n->wire_a = value; return; }
|
|
if (port == (int)V4_PORT_B) { n->wire_b = value; return; }
|
|
if (port == (int)V4_PORT_DO) { n->do_op = value; n->doing = 1; n->spoke = 1; return; }
|
|
if (port >= 0 && port < (int)V4_PORTS) {
|
|
n->request = value;
|
|
n->ask_port = (unsigned)port;
|
|
n->asking = 1;
|
|
return;
|
|
}
|
|
if (port >= 0) { v4_node_fault(n, V4_FAULT_ADDRESS, addr); return; } /* "any", "which port", How and Have are not written to */
|
|
}
|
|
/* The stack registers: a store empties the stack. -1 when not attached. */
|
|
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; }
|
|
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
|
|
if (!v4_node_addr_ok(addr)) return;
|
|
n->mem[(unsigned)addr] = value;
|
|
/* NODE-ERROR: a non-zero code raises an error. -1 when not attached. */
|
|
if (addr == n->error_reg && value != 0) v4_node_fault(n, V4_FAULT_RAISED, value);
|
|
}
|
|
|
|
v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
|
|
{
|
|
/* console_rx and console_status are -1 when no input is attached, which
|
|
* no valid address equals. */
|
|
if (addr == n->console_status)
|
|
return n->input_pos < n->input_len ? (v4_cell)-1 : (v4_cell)0;
|
|
if (addr == n->console_rx) {
|
|
v4_cell c;
|
|
if (n->input_pos >= n->input_len) return (v4_cell)-1;
|
|
c = (v4_cell)n->input[n->input_pos++];
|
|
if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0;
|
|
return c;
|
|
}
|
|
{
|
|
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) return (v4_cell)n->writers;
|
|
if (port == (int)V4_PORTS + 3) return (v4_cell)n->readers;
|
|
if (port == (int)V4_PORT_WIRE) return (v4_cell)n->wire;
|
|
if (port == (int)V4_PORT_A) return n->wire_a;
|
|
if (port == (int)V4_PORT_B) return n->wire_b;
|
|
if (port == (int)V4_PORT_DO) return n->do_op;
|
|
if (port == (int)V4_PORT_HOW) return n->how;
|
|
if (port == (int)V4_PORT_HAVE) { n->have_fetched = 1; return (v4_cell)n->have; }
|
|
if (port >= 0) {
|
|
/* the word its neighbour wrote; v4_node_read_ready has said it is there */
|
|
if (!n->given) return 0;
|
|
n->given = 0;
|
|
if (port == (int)V4_PORT_ANY) n->last_from = n->given_port;
|
|
return n->given_value;
|
|
}
|
|
}
|
|
if (addr == n->dstack_reg) return (v4_cell)n->ds.depth;
|
|
if (addr == n->rstack_reg) return (v4_cell)n->rs.depth;
|
|
return v4_node_load(n, addr);
|
|
}
|
|
|
|
void v4_node_console_input_attach(v4_node *n, v4_cell rx, v4_cell status)
|
|
{
|
|
n->console_rx = rx;
|
|
n->console_status = status;
|
|
n->input_len = 0;
|
|
n->input_pos = 0;
|
|
}
|
|
|
|
unsigned v4_node_console_feed(v4_node *n, const void *chars, unsigned len)
|
|
{
|
|
const unsigned char *c = (const unsigned char *)chars;
|
|
unsigned i, pending = n->input_len - n->input_pos, took = 0;
|
|
|
|
/* Slide what is pending to the front so the whole buffer is usable. */
|
|
if (n->input_pos > 0) {
|
|
for (i = 0; i < pending; i++) n->input[i] = n->input[n->input_pos + i];
|
|
n->input_pos = 0;
|
|
n->input_len = pending;
|
|
}
|
|
while (took < len && n->input_len < V4_CONSOLE_CAP) n->input[n->input_len++] = c[took++];
|
|
return took;
|
|
}
|
|
|
|
void v4_node_console_attach(v4_node *n, v4_cell addr)
|
|
{
|
|
n->console_tx = addr;
|
|
n->console_len = 0;
|
|
n->console_dropped = 0;
|
|
}
|
|
|
|
void v4_node_port_attach(v4_node *n, v4_cell base)
|
|
{
|
|
n->port = base;
|
|
n->asking = 0;
|
|
n->request = 0;
|
|
n->ask_port = 0;
|
|
n->reading = 0;
|
|
n->read_port = 0;
|
|
n->given = 0;
|
|
n->given_value = 0;
|
|
n->given_port = 0;
|
|
n->last_from = 0;
|
|
n->writers = 0;
|
|
n->readers = 0;
|
|
n->wire = 0;
|
|
n->wire_a = 0;
|
|
n->wire_b = 0;
|
|
n->doing = 0;
|
|
n->spoke = 0;
|
|
n->do_op = 0;
|
|
n->how = 0;
|
|
n->have = 0;
|
|
n->have_fetched = 0;
|
|
}
|
|
|
|
void v4_node_port_status(v4_node *n, unsigned writers, unsigned readers)
|
|
{
|
|
n->writers = writers;
|
|
n->readers = readers;
|
|
}
|
|
|
|
int v4_node_port_index(const v4_node *n, v4_cell addr)
|
|
{
|
|
if (n->port < 0 || addr < n->port || addr > n->port + (v4_cell)V4_PORT_HAVE) return -1;
|
|
return (int)(addr - n->port);
|
|
}
|
|
|
|
int v4_node_read_ready(v4_node *n, v4_cell addr)
|
|
{
|
|
int port = v4_node_port_index(n, addr);
|
|
if (port < 0 || port > (int)V4_PORT_ANY) return 1;
|
|
if (n->given && (port == (int)V4_PORT_ANY || (unsigned)port == n->given_port)) return 1;
|
|
n->reading = 1;
|
|
n->read_port = (unsigned)port;
|
|
return 0;
|
|
}
|
|
|
|
void v4_node_port_give(v4_node *n, unsigned port, v4_cell value)
|
|
{
|
|
n->given = 1;
|
|
n->given_value = value;
|
|
n->given_port = port;
|
|
n->reading = 0;
|
|
}
|
|
|
|
void v4_node_born(v4_node *n, v4_cell base)
|
|
{
|
|
v4_node_reset(n);
|
|
v4_node_port_attach(n, base);
|
|
n->p = base + (v4_cell)V4_PORT_ANY;
|
|
}
|
|
|
|
void v4_node_port_served(v4_node *n)
|
|
{
|
|
n->asking = 0;
|
|
}
|
|
|
|
int v4_node_doing(const v4_node *n)
|
|
{
|
|
return n->doing;
|
|
}
|
|
|
|
void v4_node_done(v4_node *n, v4_cell how)
|
|
{
|
|
n->how = how;
|
|
n->doing = 0;
|
|
}
|
|
|
|
void v4_node_have(v4_node *n, unsigned mask)
|
|
{
|
|
n->have = mask;
|
|
}
|
|
|
|
int v4_node_port_gone(v4_node *n, v4_cell code)
|
|
{
|
|
if (code == 0 || n->error_reg < 0 || n->fault_vector < 0 || n->stopped) return 0;
|
|
if (n->asking) n->asking = 0;
|
|
else if (n->reading && !n->given && n->read_port != V4_PORT_ANY) n->reading = 0;
|
|
else return 0;
|
|
v4_node_store(n, n->error_reg, code); /* raises it: P is the node's handler now */
|
|
return 1;
|
|
}
|