Ruled 2026-10-08. The engine, the nucleus and their tests are as they
were at dfabfa46, before step 6d; the code of the wait and its tests are
in history at 075385ab..2cf37aab. The limit of MESH.md 7b.7 stands: a
node that is stuck can hold up its neighbours until Hera kills it.
MESH.md 7c is kept as the record of the design as approved, and 7c.6 says
what was built, what the two reviews found, the cause the findings share
-- a node's message machinery runs on the stacks its text is using, and a
fault abandons whatever was in progress -- and what a second attempt
must settle before anything is built.
Kept from it, in hosted-check: a line that leaves 29 values on the stack,
and WORDS with 26 values on it. Both hung the products at some commit of
the wait.
make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots typing both. POST 538 of 538, word_count=317,
dict_hash=0xc0769523a47b7dc3 on all six: the hash of step 6c's code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
578 lines
30 KiB
C
578 lines
30 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;
|
|
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 ---- */
|
|
#define CONSOLE_ID 1
|
|
static v4_message going, coming;
|
|
static unsigned going_at;
|
|
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 int console_give(void *self, v4_cell *value)
|
|
{
|
|
(void)self;
|
|
if (going_at >= going.count) return 0;
|
|
*value = going.word[going_at++];
|
|
return 1;
|
|
}
|
|
static int console_take(void *self, v4_cell value)
|
|
{
|
|
(void)self;
|
|
if (v4_message_word(&coming, value)) {
|
|
if (v4_message_type(&coming) == V4_MSG_OUTPUT) {
|
|
unsigned i, chars = v4_message_length(&coming), start = printed_len;
|
|
for (i = 0; i < chars && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = v4_message_char(&coming, i);
|
|
printed[printed_len] = 0;
|
|
printed_from = v4_message_from(&coming);
|
|
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 (v4_message_type(&coming) == V4_MSG_DONE) {
|
|
ended++;
|
|
ended_from = v4_message_from(&coming);
|
|
ended_how = coming.word[V4_MSG_HEADER];
|
|
}
|
|
coming.count = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
static int console_pending(void *self) { (void)self; return going_at < going.count; }
|
|
static const v4_device console = { console_take, console_give, 0, console_pending };
|
|
|
|
/* Is there a node that will execute at the next step? 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 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.reading && !x->n.given)) 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 rport=%u given=%d stopped=%d mq=%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.read_port, x->n.given, x->n.stopped, (long)x->n.mem[MQ_COUNT], (long)x->n.mem[LOST]);
|
|
}
|
|
}
|
|
/* Send text to a node and let everything that follows from it happen. */
|
|
static const char *tell(v4_cell node, const char *text)
|
|
{
|
|
unsigned long steps = 0;
|
|
printed_len = 0; printed[0] = 0; printed_from = -1;
|
|
ended = 0; ended_from = -1; ended_how = -1;
|
|
if (!v4_message_text(&going, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)";
|
|
going_at = 0;
|
|
while (steps < step_limit && (v4_fabric_step(&f) != 0 || any_running())) steps++;
|
|
if (steps >= step_limit) { stuck(); return "(still running)"; }
|
|
return printed;
|
|
}
|
|
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 reading=%d rport=%u p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->reading, q->read_port, (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;
|
|
}
|
|
static int waiting(v4_cell number)
|
|
{
|
|
const v4_node *n = node_numbered(number);
|
|
return n && n->reading && !n->given && n->read_port == V4_PORT_ANY;
|
|
}
|
|
|
|
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_gone_error(&f, V4_ERROR_NO_ONE);
|
|
v4_fabric_interrupt_error(&f, V4_ERROR_INTERRUPTED);
|
|
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");
|
|
going.count = 0; going_at = 0;
|
|
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
|
|
CHECK(given_cells == f18_count && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY && !pool[0].n.stopped,
|
|
"it takes in the nucleus through its port and waits for a message: %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) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go");
|
|
|
|
/* ---- 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) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go");
|
|
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) ---- */
|
|
|
|
/* a node is killed while its neighbour is writing to it */
|
|
step_limit = 3000000ul;
|
|
CHECK(strcmp(tell(14, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 14 is stuck in a loop that never ends");
|
|
CHECK(strcmp(tell(12, ": TO14 S\" 65 EMIT\" 14 SEND ; TO14"), "(still running)") == 0 && node_numbered(12)->asking,
|
|
"node 12, beside it, sends it text: 14 never reads, and 12 is blocked writing to it");
|
|
{
|
|
unsigned born = born_count;
|
|
const char *r = tell(10, "14 KILL 65 EMIT");
|
|
CHECK(strstr(printed, "A") != NULL && node_numbered(14) == NULL && born_count == born, "Hera kills node 14, by its number: \"%s\"", r);
|
|
}
|
|
CHECK(!node_numbered(12)->asking && !node_numbered(12)->stopped && waiting(12), "node 12 is no longer blocked: it is waiting at its ports");
|
|
CHECK(strstr(printed, "No one on that port") != NULL, "what it was doing ended in an error, which said so: \"%s\"", printed);
|
|
step_limit = 30000000ul;
|
|
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(13, "13 100 * ."), "1300 ") == 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];
|
|
CHECK(tell(12, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(12)->mem[REFUSED] == refused + 1 && waiting(12),
|
|
"every node was told 14 is gone and forgot the way: what node 12 sends it now goes toward Hera, who has no way either, and is refused: \"%s\"", printed);
|
|
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 */
|
|
step_limit = 3000000ul;
|
|
CHECK(strcmp(tell(13, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 13 is stuck in a loop");
|
|
CHECK(strcmp(tell(12, ": W13 13 AWAIT . ; W13"), "(still running)") == 0 && node_numbered(12)->mem[AWAIT_FROM] == 13 && waiting(12),
|
|
"node 12, which is not wired to it, waits for its answer");
|
|
(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);
|
|
step_limit = 30000000ul;
|
|
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 */
|
|
step_limit = 3000000ul;
|
|
CHECK(strcmp(tell(12, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 12 is stuck in a loop");
|
|
CHECK(strcmp(tell(10, "12 AWAIT ."), "(still running)") == 0 && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "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");
|
|
}
|
|
step_limit = 30000000ul;
|
|
|
|
/* Hera blocked writing to a node that is stuck: a line from the console lets her go (MESH.md 7b.7) */
|
|
step_limit = 3000000ul;
|
|
CHECK(strcmp(tell(11, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 11 is stuck in a loop");
|
|
CHECK(strcmp(tell(10, ": T11 S\" 1 DROP\" 11 SEND 67 EMIT ; T11"), "(still running)") == 0 && node_numbered(10)->asking,
|
|
"Hera sends it text: its number is the higher, so she writes without looking, and is blocked");
|
|
{
|
|
const char *r = tell(10, "11 KILL 68 EMIT");
|
|
CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "D") != NULL && strstr(r, "C") == NULL && strstr(r, "Interrupted\n") < strstr(r, "D"),
|
|
"a line typed at the console ends the line that was blocked, with an error that says so, and is then done: \"%s\"", r);
|
|
CHECK(node_numbered(11) == NULL && !node_numbered(10)->asking && waiting(10), "the line killed the stuck node, and Hera is back at her ports");
|
|
}
|
|
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;
|
|
}
|