A block is a kernel request, as ENGINE.md 3.3 has it: the node puts the
block's number and the address of 256 cells on its stack and writes the
request to port 0, and the kernel leaves the status there. The requests
are -1, read, and -2, write, the same for every node. v4/system/blocks.c
serves them from the kernel's block subsystem, which is v3's. The four
storage registers are gone from the engine.
The device that spoke block messages (4a505a15) is withdrawn with its
test and its message types: Captain Bob ruled on 2026-10-07 that it, a
node's own drive, and nodes with no storage had left the OS as designed
(docs/v4.0.0/MESH.md 8.5).
Hera no longer sends POST to the nodes she births: POST is the kernel's,
once. Every node has its kernel on port 0; it serves a node's blocks and,
for Hera alone, her requests for nodes and capsules.
Bare metal: the node boots and is POSTed against POST's own block RAM,
and the kernel's chain -- fast RAM, the ramdrive, the virtio disk -- is
set up after POST and before the prompt, as on the v3 path. The disk is
read and not written: nothing in v4 yet gives the owner's word that it
may be formatted. A hosted program has the chain's fast RAM, as hosted
v3 has with no disk. Error 17 is Storage refused.
make -C v4 test and sanitize pass at both widths; hosted-check passes on
three ISAs; amd64, aarch64 and riscv64 boot, POST 538 of 538, with the
typed session: logs/20261007-081603, -081839, -082226. The hashes are
the same on all six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
225 lines
6.7 KiB
C
225 lines
6.7 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 >= 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" and "which port" 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 >= 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;
|
|
}
|
|
|
|
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_PORTS + 3) 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;
|
|
}
|