Files
LithosAnanake/v4/tests/test_host_unit.c
T
rajamesandClaude Opus 5.5 6a7d0aa27f fix(v4.0.0): step 6e review round 1 -- a take that does not take does nothing; output and answers wait at a middle node; no queue on the kernel's port; a printing wait is not interrupted
- The result of every take is looked at: a wire cut between the look and
  the take leaves nothing acted on (the text before run again, a refusal
  counted, a wait ended by an old answer, a way forgotten).
- A node with no room to pass on what another printed, or its word of how
  text ended, leaves it on the wire it came by and moves it when there is
  room; what is behind it for the same port waits too.  Sleep for room (9)
  is woken by room on any of the node's wires (engine; MESH.md 7d.3).
- The lone node's host keeps no queue for the kernel's port: a message to
  it is error 18.  The step limit is counted while the node waits for room.
- A text waiting for room to print is not interrupted by console text;
  AWAIT is, as before.  (BREAK?) is gone.
- A GONE is believed only by the centre's port; unawaited answers let go
  are counted; nothing past a message's last character goes on a wire;
  AWAIT sets (AWAIT-FROM) only once the stack has been tried.
- test_host_depth.c: ten paths, each run works or ends in one named stack
  error; it fails with (ROOM) or (ROOM-SERVE) taken out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:01:11 -04:00

609 lines
33 KiB
C

/* test_host_unit.c -- five nodes come up from nothing.
* docs/v4.0.0/MESH.md section 9, step 5; acceptance 1 and 2.
*
* [11]---[12]
* | \ / | the four outer nodes are each on a
* console --1 [10] | port of Hera, 10, and joined in a
* kernel --0 | / \ | square; the corners across from each
* [13]---[14] other are not wired
*
* Hera is born empty and is given the nucleus through the port her requests
* go to, as the boot gives it (v4/system/boot.c). She is told who she is,
* given the words that ask for nodes (v4/include/v4/manage.h), FORTH-79,
* POST, and her own capsule, capsules/v4/hera.4th. Then she is told
* `10 UNIT`, and everything after that is hers: she asks for four nodes,
* wires them, sends each the nucleus cell by cell through the port that
* joins them and then FORTH-79 and POST a line at a time, and joins them.
*
* What stands in here for the host: the capsules are read from the files
* the build bakes in, and the nucleus is assembled as mkimage assembles it.
*
* STORAGE (MESH.md section 8, acceptance 3). Every node asks its kernel
* for its blocks, on its port 0, and the kernel's block subsystem
* (v3/src/block_subsystem.c) has them: one chain, fast RAM and a device of
* 64 blocks. For Hera the kernel on that port also answers her requests
* for nodes and capsules; for every other node it serves blocks and
* nothing else.
*/
#include "v4/capsule.h"
#include "v4/fabric.h"
#include "v4/image.h"
#include "v4/manage.h"
#include "v4/blocks.h"
#include "v4/message.h"
#include "v4/post.h"
#include "v4/text.h"
#include "block_subsystem.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "host_map.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
#define PLACES 10u
static v4_text tx;
static v4_node nucleus; /* the nucleus, assembled: what the capsule of F18 code is written from */
static v4_cell w_idle, w_fault;
static v4_fabric_node pool[PLACES];
static int pool_used[PLACES];
/* the kernel's blocks: fast RAM and a device of 64 blocks */
#define DISK_BLOCKS 64u
static unsigned char common_ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE], common_dev[DISK_BLOCKS * V4_BLOCK_BYTES];
extern void sf_time_init(void);
extern void log_set_level(int level);
static int storage_made(void)
{
sf_time_init();
log_set_level(-1);
return blk_subsys_init(common_ram, sizeof common_ram) == BLK_OK && blk_subsys_add_raw_device(common_dev, DISK_BLOCKS) == BLK_OK;
}
static v4_place places[PLACES];
static v4_fabric f;
#define QUEUES 64u /* two for every wire there is at once: five kernels, the console, four to Hera, four round the square */
static v4_wire_queue queues[QUEUES];
static v4_image im; /* only what the dictionary hash needs of it */
/* ---- the capsules ---- */
static v4_cell f18[2u * V4_NODE_WORDS + 64u]; /* the nucleus as a capsule of F18 code */
static unsigned f18_count;
static void f18_put(void *self, v4_cell w) { (void)self; if (f18_count < sizeof f18 / sizeof f18[0]) f18[f18_count++] = w; }
typedef struct { char *text; long len, at; int in_block; unsigned cells_at; int is_f18; } reader;
static int read_file(reader *r, const char *name)
{
char path[600];
FILE *in;
snprintf(path, sizeof path, "%s/../../capsules/v4/%s", V4_CAPSULE_DIR, name);
in = fopen(path, "rb");
if (!in) return 0;
free(r->text);
fseek(in, 0, SEEK_END); r->len = ftell(in); fseek(in, 0, SEEK_SET);
r->text = (char *)malloc((size_t)r->len + 1);
if (!r->text || fread(r->text, 1, (size_t)r->len, in) != (size_t)r->len) { fclose(in); return 0; }
fclose(in);
r->at = 0; r->in_block = 0; r->is_f18 = 0;
return 1;
}
/* the next line of a block that has anything on it: its length, or -1 */
static int next_line(reader *r, char *text)
{
while (r->at < r->len) {
const char *p = r->text + r->at;
long n = 0;
while (r->at + n < r->len && p[n] != '\n') n++;
r->at += n + 1;
if (n >= 7 && memcmp(p, "Block ", 6) == 0 && p[6] >= '0' && p[6] <= '9') { r->in_block = 1; continue; }
if (n && p[n - 1] == '\r') n--;
if (!r->in_block || n == 0) continue;
if (n > V4_MANAGE_LINE) return -1;
memcpy(text, p, (size_t)n);
return (int)n;
}
return -1;
}
/* ---- what Hera's requests are answered with ---- */
static reader hera_reads; /* the capsule Hera has open */
static unsigned born_count, parity_count;
static v4_cell parity_number[8];
static uint64_t parity_hash[8];
/* THE KERNEL, for a node that is not Hera: on its port 0 it serves the
* node's blocks, and no other request (MESH.md 8.3). */
static unsigned refused;
static v4_blocks node_blocks[PLACES]; /* what the kernel keeps of each node's block window, by where the node is in the pool */
static int node_kernel_take(void *self, v4_cell value)
{
v4_fabric_node *x = (v4_fabric_node *)self;
if (!v4_blocks_serve(&node_blocks[x - pool], &x->n, value)) refused++;
return 1;
}
static v4_device node_kernel[PLACES];
static int host_born(void *self)
{
unsigned k;
int place;
(void)self;
for (k = 0; k < PLACES && pool_used[k]; k++) { }
if (k == PLACES) return -1;
place = v4_fabric_add(&f, &pool[k], PORT);
if (place < 0) return -1;
pool_used[k] = 1;
born_count++;
/* what a node's hardware gives it; there is nothing in its memory */
v4_node_stack_regs_attach(&pool[k].n, DSTACK_REG, RSTACK_REG);
v4_node_error_attach(&pool[k].n, NODE_ERROR);
v4_node_fault_attach(&pool[k].n, w_fault);
v4_blocks_init(&node_blocks[k], BUF0_W);
if (k != 0) { /* Hera's kernel is wired where she is made */
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
}
return place;
}
static void host_gone(void *self, unsigned place, v4_fabric_node *was)
{
(void)self; (void)place;
pool_used[was - pool] = 0;
}
static int host_open(void *self, const char *name, unsigned len)
{
(void)self;
if (len == 17 && memcmp(name, "v4:nucleus-64.f18", 17) == 0) { hera_reads.is_f18 = 1; hera_reads.cells_at = 0; return 1; }
if (len == 14 && memcmp(name, "v4:forth79.4th", 14) == 0) return read_file(&hera_reads, "forth79.4th");
return 0;
}
static int host_cell(void *self, v4_cell *w)
{
(void)self;
if (!hera_reads.is_f18 || hera_reads.cells_at >= f18_count) return 0;
*w = f18[hera_reads.cells_at++];
return 1;
}
static int host_line(void *self, char *text)
{
(void)self;
return hera_reads.is_f18 ? -1 : next_line(&hera_reads, text);
}
static void host_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
{
(void)self; (void)place;
if (parity_count < 8) { parity_number[parity_count] = number; parity_hash[parity_count] = v4_image_dict_hash(n, &im); }
parity_count++;
}
static const v4_manage manage = { &f, host_born, host_gone, host_open, host_cell, host_line, host_parity, 0 };
/* ---- the kernel: a device on Hera's port 0 ---- */
#define FIRST_REQUEST 1 /* the number of NODE-ME on her */
static unsigned hera_place, given_cells;
static int kernel_give(void *self, v4_cell *value) /* the nucleus, to a node that has nothing in it */
{
(void)self;
if (given_cells >= f18_count) return 0;
*value = f18[given_cells++];
return 1;
}
static int kernel_take(void *self, v4_cell value) /* a request */
{
(void)self;
if (v4_blocks_serve(&node_blocks[0], &pool[0].n, value)) return 1; /* a block, as for any node; Hera is the first in the pool */
if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;
return 1;
}
static const v4_device kernel = { kernel_take, kernel_give, 0, 0 };
/* ---- the console: a device on Hera's port 1 ----
* It speaks whole messages: it puts them on the wire to Hera and takes them
* off the wire from her, through the fabric (MESH.md 7d.2). */
#define CONSOLE_ID 1
static char printed[8192]; /* what has come back since the console last sent */
static unsigned printed_len;
static v4_cell printed_from;
static int ended;
static v4_cell ended_from, ended_how;
static unsigned post_passed[32]; /* by node number: POST tallies with no failures seen from it */
static unsigned post_other; /* tallies with failures */
static const v4_device console = { 0, 0, 0, 0 };
static unsigned console_takes(void)
{
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
unsigned took = 0;
while (v4_fabric_device_take(&f, hera_place, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
took++;
if (hd[2] == V4_MSG_OUTPUT) {
unsigned i, start = printed_len;
for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
printed[printed_len] = 0;
printed_from = hd[1];
if (strstr(printed + start, "PARITY:V4_POST")) {
if (strstr(printed + start, " fail=0\n") && printed_from >= 0 && printed_from < 32) post_passed[printed_from]++;
else post_other++;
}
} else if (hd[2] == V4_MSG_DONE) {
ended++;
ended_from = hd[1];
ended_how = text[0];
}
}
return took;
}
/* Is there a node, other than those known to be going round for ever, that
* will execute at the next step? A node asleep at a wire will not; a step
* in which a node only faulted counts nothing done (exec.h), and it goes on
* from its fault handler at the next. */
static unsigned spinning; /* bit n: the node numbered n is known to be stuck in a loop, and is not waited for */
static int any_running(void)
{
unsigned k;
for (k = 0; k < PLACES; k++) {
const v4_fabric_node *x = v4_fabric_node_at(&f, k);
if (!x || places[k].asleep || x->n.stopped || x->n.asking || x->n.doing || (x->n.reading && !x->n.given)) continue;
if (x->n.mem[ME] >= 0 && x->n.mem[ME] < 32 && (spinning & 1u << x->n.mem[ME])) continue;
return 1;
}
return 0;
}
/* What every node is doing, for a test that does not end. */
static unsigned long step_limit = 4000000000ul;
static void stuck(void)
{
unsigned k;
for (k = 0; k < PLACES; k++) {
const v4_fabric_node *x = v4_fabric_node_at(&f, k);
if (x) printf(" stuck: node %ld p=%ld asking=%d port=%u reading=%d doing=%d op=%ld wire=%u stopped=%d await=%ld lost=%ld\n", (long)x->n.mem[ME], (long)x->n.p,
x->n.asking, x->n.ask_port, x->n.reading, x->n.doing, (long)x->n.do_op, x->n.wire, x->n.stopped, (long)x->n.mem[AWAIT_FROM], (long)x->n.mem[LOST]);
}
}
/* Send text to a node and let everything that follows from it happen: until
* nothing can; or, with nodes known to be stuck, until no other node has
* executed and the console has been sent nothing for a while. */
static const char *tell(v4_cell node, const char *text)
{
static v4_message m;
unsigned long steps = 0, quiet = 0;
printed_len = 0; printed[0] = 0; printed_from = -1;
ended = 0; ended_from = -1; ended_how = -1;
if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)";
if (v4_fabric_device_put(&f, hera_place, 1, m.word, m.word + V4_MSG_HEADER) != V4_WIRE_DONE) return "(not put)";
while (steps < step_limit) {
unsigned did = v4_fabric_step(&f), took = console_takes();
int run = any_running();
steps++;
if (did == 0 && took == 0 && !run) return printed;
quiet = (spinning && !run && took == 0) ? quiet + 1 : 0;
if (quiet >= 400000ul) return printed;
}
stuck();
return "(still running)";
}
static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
/* POST's host, for the node numbered 10 (v4/include/v4/post.h): a line is
* told to it from the console, and what it prints is handed to the runner. */
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
{
static char line[V4_MSG_MAX_CHARS + 1];
const char *r;
(void)self;
*len = 0;
if (text_len > V4_MSG_MAX_CHARS) return -1;
memcpy(line, text, text_len); line[text_len] = 0;
r = tell(10, line);
if (ended != 1 || ended_from != 10) return -1;
*len = printed_len;
memcpy(out, r, printed_len < cap ? printed_len : cap);
return (int)ended_how;
}
static char post_said[2048];
static unsigned post_said_len;
static void post_say(void *self, const char *text, unsigned len)
{
(void)self;
if (post_said_len + len < sizeof post_said) { memcpy(post_said + post_said_len, text, len); post_said_len += len; post_said[post_said_len] = 0; }
}
/* Every line of a capsule of FORTH blocks, told to a node from the console:
* how many lines did not complete. */
static unsigned load(v4_cell node, const char *file)
{
static reader r;
char line[V4_MANAGE_LINE + 1];
unsigned bad = 0;
int n;
if (!read_file(&r, file)) return 1;
while ((n = next_line(&r, line)) >= 0) {
line[n] = 0;
(void)tell(node, line);
if (ended != 1 || ended_how != V4_TEXT_COMPLETED) { if (!bad) { const v4_node *q = &pool[0].n; printf(" %s: \"%s\" -> \"%s\" ended=%d how=%ld; stopped=%d asking=%d port=%u doing=%d op=%ld p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->doing, (long)q->do_op, (long)q->p, q->fault_kind, (long)q->fault_addr); } bad++; }
}
return bad;
}
static v4_node *node_numbered(v4_cell number)
{
unsigned k;
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return &v4_fabric_node_at(&f, k)->n;
return 0;
}
static v4_uheat_t clock_of(v4_cell number)
{
unsigned k;
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return v4_fabric_node_at(&f, k)->es.anticlock;
return 0;
}
/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */
static int waiting(v4_cell number)
{
const v4_node *n = node_numbered(number);
return n && n->doing && n->do_op == 8;
}
/* how many cells of messages wait on the wire going from a port of the node */
static unsigned going_from(v4_cell number, unsigned port)
{
unsigned k;
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number && places[k].wire[port].tx) return places[k].wire[port].tx->used;
return 0;
}
int main(void)
{
unsigned i;
int place;
printf("v4 unit tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
if (V4_CELL_BITS != 64) {
/* The capsules the unit is built from are the 64-bit products': the
* expected results in POST's table are v3's, on 64-bit cells, and the
* nucleus capsule is nucleus-64. POST has never been run on 32-bit
* cells (docs/v4.0.0/MESH.md, step 5). */
printf(" not run at this width: the capsules are the 64-bit products'\n");
return 0;
}
CHECK(storage_made(), "the kernel's blocks are there before any node is");
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles");
}
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_idle = v4_text_word(&tx, "(IDLE)");
w_fault = v4_text_word(&tx, "(FAULTS)");
nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */
nucleus.mem[LATEST] = v4_text_latest(&tx);
nucleus.mem[CFP] = CFS_W;
nucleus.mem[BASE] = 10;
nucleus.mem[FENCE] = DICT_W;
nucleus.mem[BOOT_CELLS] = nucleus.mem[DP];
nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST];
nucleus.mem[LOG_LEVEL] = 2;
nucleus.mem[CONTEXT] = LATEST;
nucleus.mem[CURRENT] = LATEST;
nucleus.mem[SRC] = TIB;
nucleus.mem[LINE_STATUS] = 1;
HOST_MESSAGES_EMPTY(&nucleus);
(void)v4_capsule_write(&nucleus, w_idle, f18_put, 0);
CHECK(f18_count > 1000 && f18_count < sizeof f18 / sizeof f18[0], "the nucleus is a capsule of F18 code: %u cells", f18_count);
memset(&im, 0, sizeof im);
im.dp = DP; im.latest = LATEST;
/* ---- Hera is born empty, and takes in the nucleus ---- */
v4_fabric_init(&f, places, PLACES);
v4_fabric_queues(&f, queues, QUEUES);
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
place = host_born(0);
CHECK(place == 0, "a node is born");
hera_place = (unsigned)place;
for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { }
CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports");
CHECK(v4_fabric_wire_device(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
CHECK(given_cells == f18_count && pool[0].n.doing && pool[0].n.do_op == 8 && !pool[0].n.stopped,
"it takes in the nucleus through its port, a word at a time, and sleeps until a message comes: %u of %u cells", given_cells, f18_count);
CHECK(told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE") && ended_from == 10, "it is told it is 10, and where the console is");
{
char text[600];
size_t at = 0;
for (i = 0; i < V4_MANAGE_WORDS; i++) at += (size_t)snprintf(text + at, sizeof text - at, "%u KERNEL-WORD %s ", FIRST_REQUEST + i, v4_manage_words[i]);
CHECK(told(10, text), "and given the words that ask for nodes and capsules: \"%s\"", printed);
}
CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "it asks where it is, and is answered");
CHECK(load(10, "forth79.4th") == 0, "it takes in FORTH-79");
CHECK(told(10, "(SEAL)"), "the nucleus and FORTH-79 are the system POST is run on");
{
static v4_cell before[V4_NODE_WORDS];
v4_post_host host;
v4_post_tally tally;
v4_node *hera = node_numbered(10);
v4_cell here = hera->mem[DP] / 4, k, changed = 0, first = -1;
uint64_t hash = v4_image_dict_hash(hera, &im);
memcpy(before, hera->mem, sizeof before);
host.self = 0; host.n = hera; host.line = post_line; host.say = post_say;
CHECK(v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally) && tally.tests == 538 && tally.pass == 538 && tally.fail == 0
&& strstr(post_said, "PARITY:V4_POST tests=538 pass=538 fail=0\n") != NULL && strstr(post_said, "POST FAIL") == NULL,
"and passes POST, which the kernel runs on her: %u of %u, %u failed; \"%s\"", tally.pass, tally.tests, tally.fail, post_said);
CHECK(strcmp(tell(10, "RS1"), "42 42 \n") == 0, "while POST runs, what its cases define is on her: \"%s\"", printed);
CHECK(strstr(tell(10, "COLD"), "Cold Start") != NULL, "the kernel puts her back to the system as it was sealed");
CHECK(hera->mem[DP] == before[DP] && hera->mem[LATEST] == before[LATEST], "HERE and LATEST are where they were before POST");
for (k = 0; k < here; k++) if (hera->mem[k] != before[k]) { if (first < 0) first = k; changed++; }
/* What differs is the TTL in the entry of each word POST had interpreted: a count of uses left before
* the word's access is checked again, which every use counts down (compile.v4, (ACL?)), as in v3. */
printf(" after POST and COLD: %ld of the %ld cells below HERE differ from before POST (the first at %ld); dict_hash %s\n",
(long)changed, (long)here, (long)first, v4_image_dict_hash(hera, &im) == hash ? "the same" : "differs");
}
CHECK(strstr(tell(10, "T{"), "UNKNOWN WORD") != NULL, "nothing of POST's harness is on her: \"%s\"", printed);
CHECK(strstr(tell(10, "RS1"), "UNKNOWN WORD") != NULL && strstr(tell(10, "ZERO"), "UNKNOWN WORD") != NULL, "nor anything POST's cases defined: POST leaves nothing, as in v3: \"%s\"", printed);
CHECK(load(10, "hera.4th") == 0, "and its own capsule, which makes it Hera");
CHECK(told(10, "(SEAL)"), "that is the system it comes back to");
/* ---- she births the unit ---- */
born_count = 0;
{
const char *r = tell(10, "10 UNIT");
CHECK(ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "Hera births four nodes and joins them: \"%s\"", r);
}
CHECK(born_count == 4 && refused == 0, "four nodes were asked for: %u", born_count);
CHECK(post_passed[10] == 0 && post_passed[11] == 0 && post_passed[12] == 0 && post_passed[13] == 0 && post_passed[14] == 0 && post_other == 0,
"a node she births is not POSTed, and no node prints a POST tally: POST is the kernel's, once, on Hera: %u %u %u %u %u, %u failed",
post_passed[10], post_passed[11], post_passed[12], post_passed[13], post_passed[14], post_other);
CHECK(parity_count == 4 && parity_number[0] == 11 && parity_number[1] == 12 && parity_number[2] == 13 && parity_number[3] == 14, "the parity of each was recorded: %u", parity_count);
CHECK(parity_hash[0] == parity_hash[1] && parity_hash[1] == parity_hash[2] && parity_hash[2] == parity_hash[3],
"and their dictionary hashes are the same: %016llx %016llx %016llx %016llx",
(unsigned long long)parity_hash[0], (unsigned long long)parity_hash[1], (unsigned long long)parity_hash[2], (unsigned long long)parity_hash[3]);
printf(" the four outer nodes: dict_hash=0x%016llx\n", (unsigned long long)parity_hash[0]);
CHECK(parity_hash[0] != v4_image_dict_hash(node_numbered(10), &im), "Hera's is not: she has her own capsule");
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports");
{
v4_uheat_t c[5];
for (i = 0; i < 5; i++) c[i] = clock_of(10 + (v4_cell)i);
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
for (i = 0; i < 5 && clock_of(10 + (v4_cell)i) == c[i]; i++) { }
CHECK(i == 5, "and execute nothing while they wait");
}
/* ---- they talk ---- */
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10, "a line typed at the console is done by Hera");
for (i = 11; i <= 14; i++) {
char text[32], want[16];
snprintf(text, sizeof text, "%u 100 * .", i);
snprintf(want, sizeof want, "%u ", i * 100);
CHECK(strcmp(tell((v4_cell)i, text), want) == 0 && printed_from == (v4_cell)i && ended_from == (v4_cell)i, "text for node %u is done there and what it prints comes back: \"%s\"", i, printed);
}
CHECK(strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0, "an outer node has FORTH-79: U* is a word of the capsule");
CHECK(strcmp(tell(13, ": SQ DUP * ; 7 SQ ."), "49 ") == 0 && strstr(tell(12, "7 SQ ."), "UNKNOWN WORD") != NULL, "each has its own dictionary");
CHECK(strstr(tell(13, "COLD"), "Cold Start") != NULL && strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0 && strstr(tell(13, "7 SQ ."), "UNKNOWN WORD") != NULL,
"COLD on it comes back to the nucleus and FORTH-79");
{
v4_uheat_t hera = clock_of(10);
CHECK(strcmp(tell(11, ": T S\" 65 EMIT\" 12 SEND ; T"), "A") == 0 && printed_from == 12, "an outer node sends text to the one beside it: \"%s\"", printed);
hera = clock_of(10);
CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed);
CHECK(clock_of(10) > hera, "that went by a node in between");
}
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "2 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0,
"nothing was let go but the two answers that came back to node 11, which did not wait for them: \"%s\"", printed);
/* ---- they share the store (acceptance 3) ---- */
step_limit = 30000000ul;
CHECK(told(12, "2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS") && common_dev[52 * V4_BLOCK_BYTES] == 'A' && common_dev[52 * V4_BLOCK_BYTES + 1] == ' ',
"node 12 writes a block, by asking the kernel: \"%s\"", printed);
for (i = 10; i <= 14; i++) {
if (i == 12) continue;
CHECK(strcmp(tell((v4_cell)i, "2100 BLOCK C@ ."), "65 ") == 0, "node %u reads what node 12 wrote: \"%s\"", i, printed);
}
CHECK(strstr(tell(14, "9000 BLOCK"), "Block out of range") != NULL && strcmp(tell(14, "14 100 * ."), "1400 ") == 0, "a block there is not says so, and the node goes on: \"%s\"", printed);
{
char text[64];
unsigned before = refused, born = born_count;
snprintf(text, sizeof text, "%u KERNEL-WORD ASK-BORN ASK-BORN", FIRST_REQUEST + 1);
(void)tell(12, text);
CHECK(refused == before + 1 && born_count == born, "an outer node's request for a node is not honoured: only its blocks are");
}
/* two nodes use the store at once */
CHECK(told(10, ": W 10 0 DO 97 I 2060 + BLOCK C! UPDATE SAVE-BUFFERS LOOP ;") && told(12, ": W 10 0 DO 98 I 2070 + BLOCK C! UPDATE SAVE-BUFFERS LOOP ;"), "each of two nodes has ten blocks to write");
CHECK(told(10, ": GO S\" W\" 12 SEND W ; GO"), "Hera sets node 12 writing and writes her own meanwhile: \"%s\"", printed);
for (i = 0; i < 10 && common_dev[(12 + i) * V4_BLOCK_BYTES] == 'a' && common_dev[(22 + i) * V4_BLOCK_BYTES] == 'b'; i++) { }
CHECK(i == 10, "all twenty blocks reached the store: the first that did not is %u", i);
CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go but node 12's answer to Hera, who did not wait for it: \"%s\"", printed);
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports again");
/* ---- Hera kills a node by number, and tells the others it is gone (MESH.md 7b, 7d) ----
* A node that is stuck holds up no one: what is sent to it waits on its wire until the wire is full, and
* is refused from then on; Hera can be typed to whatever any other node is doing, and her KILL of one
* stuck node is not held by another (acceptance 3 and 4). */
step_limit = 60000000ul;
spinning = 1u << 13 | 1u << 14;
(void)tell(14, ": SPIN BEGIN 0 UNTIL ; SPIN");
(void)tell(13, ": SPIN BEGIN 0 UNTIL ; SPIN");
CHECK(!waiting(14) && !waiting(13) && ended == 0, "nodes 13 and 14 are each stuck in a loop that never ends");
CHECK(tell(12, ": TO14 S\" 65 EMIT\" 14 SEND ; TO14")[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED && waiting(12) && going_from(12, 3) == 9,
"node 12, beside 14, sends it text: the text waits on the wire, 14 never taking it, and node 12's line ends: \"%s\", %u cells", printed, going_from(12, 3));
{
v4_cell refused = node_numbered(12)->mem[REFUSED];
CHECK(strstr(tell(12, ": FILL14 200 0 DO S\" 65 EMIT\" 14 SEND LOOP ; FILL14"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR
&& node_numbered(12)->mem[REFUSED] == refused + 1 && going_from(12, 3) > V4_WIRE_CELLS - 9u,
"it sends more: when the wire is full the next is refused at once, and told: \"%s\", %u cells on the wire", printed, going_from(12, 3));
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && waiting(12) && strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "node 12 is not held, and goes on doing what it is sent; so does node 11");
}
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "Hera is typed to, with two nodes stuck");
{
unsigned born = born_count;
const char *r = tell(10, "14 KILL 65 EMIT");
CHECK(strcmp(r, "A") == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED && node_numbered(14) == NULL && born_count == born,
"Hera kills node 14, by its number, with node 13 still stuck: the KILL ends, and the rest of her line is done: \"%s\"", r);
CHECK(going_from(10, 4) == 8 && node_numbered(13) != NULL && !waiting(13), "her word to node 13 that 14 is gone waits on the wire to it: it is stuck, and she did not wait for it: %u cells", going_from(10, 4));
}
spinning = 1u << 13;
CHECK(waiting(12) && waiting(11) && waiting(10), "every node that is not stuck is asleep again");
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and everything else keeps running: \"%s\"", printed);
CHECK(strstr(tell(12, "5 3 PORT!"), "No one on that port") != NULL && waiting(12), "a write to the port node 14 was on is an error at once, not a wait: \"%s\"", printed);
{
v4_cell refused = node_numbered(12)->mem[REFUSED], r11 = node_numbered(11)->mem[REFUSED];
CHECK(tell(12, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(12)->mem[REFUSED] == refused + 1 && waiting(12),
"node 12 was told 14 is gone and forgot the way: what it sends it now goes toward Hera, who has no way either, and is refused: \"%s\"", printed);
CHECK(tell(11, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(11)->mem[REFUSED] == r11 + 1 && waiting(11), "and so was node 11");
CHECK(strstr(tell(10, ": H14 S\" 1\" 14 SEND ; H14"), "Argument out of range") != NULL, "Hera herself has no way to it");
}
/* a node waits on one that is stuck and is not its neighbour; Hera kills the stuck one */
(void)tell(12, ": W13 13 AWAIT . ; W13");
CHECK(ended == 0 && node_numbered(12)->mem[AWAIT_FROM] == 13 && waiting(12), "node 12, which is not wired to node 13, waits for its answer, asleep");
(void)tell(10, "13 KILL");
CHECK(node_numbered(13) == NULL && strstr(printed, "Node gone") != NULL && node_numbered(12)->mem[AWAIT_FROM] == 0 && waiting(12),
"Hera kills node 13: node 12's wait ends, with an error that says the node is gone: \"%s\"", printed);
spinning = 0;
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "and the others go on");
/* Hera's own wait, on a node that is stuck: a line from the console ends it */
spinning = 1u << 12;
(void)tell(12, ": SPIN BEGIN 0 UNTIL ; SPIN");
(void)tell(10, "12 AWAIT .");
CHECK(ended == 0 && !waiting(12) && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "node 12 is stuck in a loop; Hera waits for its answer, which will not come");
{
const char *r = tell(10, "12 KILL 66 EMIT");
CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "B") != NULL && strstr(r, "Interrupted\n") < strstr(r, "B") && ended == 2,
"a line typed at the console ends her wait, with an error that says so, and is then done: \"%s\"", r);
CHECK(node_numbered(12) == NULL && node_numbered(10)->mem[AWAIT_FROM] == 0, "the line killed the stuck node");
}
spinning = 0;
/* Hera sends to a node that is stuck: her line ends, and when the wire is full she is refused; she is never held */
spinning = 1u << 11;
(void)tell(11, ": SPIN BEGIN 0 UNTIL ; SPIN");
CHECK(strcmp(tell(10, ": T11 S\" 1 DROP\" 11 SEND 67 EMIT ; T11"), "C") == 0 && ended == 1 && !waiting(11) && waiting(10),
"node 11 is stuck in a loop; Hera sends it text, which waits on the wire, and the rest of her line is done: \"%s\"", printed);
CHECK(strstr(tell(10, ": F11 200 0 DO S\" 1 DROP\" 11 SEND LOOP 67 EMIT ; F11"), "Message refused") != NULL && strstr(printed, "C") == NULL && ended_how == V4_TEXT_ERROR && waiting(10),
"she sends more than the wire holds: the one there is no room for is refused at once, and her line ends there: \"%s\"", printed);
{
const char *r = tell(10, "11 KILL 68 EMIT");
CHECK(strcmp(r, "D") == 0 && node_numbered(11) == NULL && waiting(10), "she is typed to, and kills the stuck node: \"%s\"", r);
}
spinning = 0;
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and she goes on");
step_limit = 30000000ul;
/* what is not a node she has had born */
{
unsigned born = born_count;
CHECK(strstr(tell(10, "99 KILL"), "KILL: no such node") != NULL && ended_how == V4_TEXT_ERROR, "KILL of a node there never was is an error that says so: \"%s\"", printed);
CHECK(strstr(tell(10, "10 KILL"), "KILL: no such node") != NULL && ended_how == V4_TEXT_ERROR && node_numbered(10) != NULL, "so is KILL of Hera herself");
CHECK(strstr(tell(10, "14 KILL"), "KILL: no such node") != NULL && ended_how == V4_TEXT_ERROR, "and of a node she has killed already");
CHECK(born_count == born && strcmp(tell(10, "1 2 + ."), "3 ") == 0,
"nothing was removed, and Hera goes on");
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}