#189 -- Captain Bob, 2026-11-12: "Add HEAT-OP. If it's a huge performance hit, we'll revert it. If not ... it stands"; a full 33-cell array, as DECOMPOSITION.md section 7 draws it; the Stadium feed is Phase 1's. - node.h/node.c: v4_node_heat_regs_attach; a data fetch from base+k gives opcode k's count, base+32 the anti-clock (the low cell at 32 bits); a store changes nothing. Reset detaches them. - host_map.h: HEAT_REG at word 13824, in what was free between the dictionary space and SEND_TEXT; the compile-time check covers it. Nothing moved. map-*.f18: HEAT-OP 13824, ANTICLOCK 13856. - image.h/image.c/mkmap.c: the image carries the address; v4_image_born attaches the node's own counts. - system.f18: HEAT-OP@ ( op -- n ), op mod 32; ANTICLOCK@ ( -- n ). word_count 318 -> 320 (hosted 317 -> 319): dict_hash changes. - v4-dictionary.tsv: both words; DECOMPOSITION.md 7 records the build. Also in this batch: - Makefile: make init installs qemu-user -- hosted-check needs it (Captain Bob: "Add to make init now"). - .claude/memory/rules.md, rule 5: stop and ask when something comes up (Captain Bob: "you are supposed to stop and ask"). Tests first (logs/20261113-005047/heat-op): test_node did not compile; test_host_quit 3 of 1337 failed. Suite: 199 programs, 7448554 checks, 0 failures; hosted-check, the three alike. Timing: no measurable cost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
311 lines
9.7 KiB
C
311 lines
9.7 KiB
C
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
|
|
* Copyright (c) 2023-2026 Robert A. James
|
|
*/
|
|
/* node.c -- the v4 node. See node.h. */
|
|
#include "v4/node.h"
|
|
#include <stddef.h>
|
|
|
|
void v4_node_reset(v4_node *n)
|
|
{
|
|
n->rom = NULL; /* attached again by the owner, as every device is */
|
|
n->rom_words = 0;
|
|
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_heat_regs_attach(n, -1, 0, 0);
|
|
v4_node_error_attach(n, -1);
|
|
v4_node_port_attach(n, -1);
|
|
}
|
|
|
|
void v4_node_heat_regs_attach(v4_node *n, v4_cell base, const v4_uheat_t *op, const v4_uheat_t *anticlock)
|
|
{
|
|
n->heat_reg = (op && anticlock) ? base : -1;
|
|
n->heat_op = op;
|
|
n->heat_anticlock = anticlock;
|
|
}
|
|
|
|
/* Which heat register addr is, 0 .. 32, or -1: none (detached, or another address). */
|
|
static int heat_reg_index(const v4_node *n, v4_cell addr)
|
|
{
|
|
v4_ucell k;
|
|
if (n->heat_reg < 0) return -1;
|
|
k = (v4_ucell)addr - (v4_ucell)n->heat_reg;
|
|
return k < (v4_ucell)V4_HEAT_REGS ? (int)k : -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);
|
|
}
|
|
|
|
int v4_node_addr_readable(const v4_node *n, v4_cell addr)
|
|
{
|
|
return v4_node_addr_ok(addr)
|
|
|| ((v4_ucell)addr - (v4_ucell)V4_ROM_BASE) < (v4_ucell)n->rom_words;
|
|
}
|
|
|
|
void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words)
|
|
{
|
|
if (!rom || words > V4_ROM_WORDS) { n->rom = NULL; n->rom_words = 0; return; }
|
|
n->rom = rom;
|
|
n->rom_words = words;
|
|
}
|
|
|
|
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
|
|
{
|
|
if (v4_node_addr_ok(addr)) return n->mem[(unsigned)addr];
|
|
if (v4_node_addr_readable(n, addr)) return n->rom[(v4_ucell)addr - (v4_ucell)V4_ROM_BASE];
|
|
return 0;
|
|
}
|
|
|
|
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 (heat_reg_index(n, addr) >= 0) return; /* HEAT-OP and ANTICLOCK are read-only */
|
|
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;
|
|
{
|
|
int k = heat_reg_index(n, addr);
|
|
if (k >= 0) return (v4_cell)(v4_ucell)(k < 32 ? n->heat_op[k] : *n->heat_anticlock);
|
|
}
|
|
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;
|
|
}
|