EMIT is a device service: a character sent to the console node. The mesh and its ports are development step 2 and the memory map is open (D-4), so the single-node model now stands in for the console with one memory-mapped register, so that printing words can be run and their output compared with v3's. v4_node_console_attach(n, addr): after it, a store to word address `addr` appends the low 8 bits of the value to a buffer on the node (V4_CONSOLE_CAP characters, 4096 by default) and does not write memory; a load from `addr` reads the memory word as before. Characters past the capacity are counted in console_dropped and discarded. The hook is in v4_node_store, which all four store opcodes use. It is off by default: v4_node_reset detaches the console (address -1) and empties the capture, so the ISA's behaviour and every existing test are unchanged unless a console is attached. The address is the caller's choice. New test v4/tests/test_console.c, 22 checks per width: direct stores, !b, ! and !+ through the executor printing "Hi!", memory at and around the register untouched, the low byte only, overflow, detach, a console at word 0, and reset. At 32- and 64-bit cells, optimised and ASan+UBSan (`make test`, `make sanitize`); all other v4 tests still pass. Four mutations of the hook each fail. DECOMPOSITION.md section 7 gains the CONSOLE-TX row. No printing word is defined here; EMIT and the words on it are still to do. Test results on the amd64 host only. This is a development check, not acceptance (JUSTIFICATION.md section 16). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
53 lines
1.6 KiB
C
53 lines
1.6 KiB
C
/* node.c -- the v4 node. See node.h. */
|
|
#include "v4/node.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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* Precondition checked by the caller; see node.h. The unsigned compare is
|
|
* deliberate: `addr` is signed, and a negative address must not wrap into
|
|
* a large positive one and read the top of memory instead. */
|
|
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;
|
|
}
|
|
n->mem[(unsigned)addr] = value;
|
|
}
|
|
|
|
void v4_node_console_attach(v4_node *n, v4_cell addr)
|
|
{
|
|
n->console_tx = addr;
|
|
n->console_len = 0;
|
|
n->console_dropped = 0;
|
|
}
|