feat(v4.0.0): the block words are the kernel's -- v3's way in full

Ruled 2026-10-07.  BLOCK, BUFFER, UPDATE, SAVE-BUFFERS and EMPTY-BUFFERS
are each one kernel request on port 0, by block number only.  The node
has a window of four slots in its memory, as a v3 VM has; the kernel
copies blocks into it, keeps the record of which block is in which slot,
and decides when a block is written (v4/system/blocks.c).  The node keeps
no record and gives the kernel no address, so a request cannot overwrite
the node's code.  When a block is written stays FORTH-79's: UPDATE marks
it.  When the chain of devices changes the slots are let go, as in v3.

The window is 512 cells more than the two buffers were; the dictionary
space ends that much lower, at 13824.

MESH.md 8.5 had said, from the review, that a v3 VM's BLOCK is the
kernel's buffer.  It is not, and the section now says what was reported,
what v3 does, and what was ruled.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, the same hashes on all six:
logs/20261007-092835, -093112, -093456.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 09:37:30 -04:00
co-authored by Claude Opus 5.5
parent 7f4d946d2e
commit 9bfd5071d2
21 changed files with 991 additions and 330 deletions
+8 -9
View File
@@ -59,7 +59,7 @@
#define HEND ((HBUF_W + 16) * 4)
#define XVARS (HBUF_W - 4) /* (X): words.v4, 4 cells */
#define QBASE (XVARS - 40) /* the Q words' and DUMP's scratch cells, 39 in all */
#define BVARS (QBASE - 16) /* blocks.v4: (B) 6 cells, then SCR BLK (SRC) (SRC-HOOK) */
#define BVARS (QBASE - 16) /* blocks.v4: 6 cells not used now, then SCR BLK (SRC) (SRC-HOOK) */
#define SCR (BVARS + 6)
#define BLK (BVARS + 7)
#define SRC (BVARS + 8)
@@ -99,8 +99,7 @@
#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */
#define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
#define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */
#define BUF0_W (BVARS - 2 * 256) /* the two block buffers, 256 cells each */
#define BUF1_W (BUF0_W + 256)
#define BUF0_W (BVARS - (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS)) /* the block window: the kernel's slots, 256 cells each (v4/blocks.h) */
/* the ports' ten addresses and the two that follow them end below BUF0_W - 20 */
typedef char host_map_ports_fit[(V4_PORTS <= 8u) ? 1 : -1];
/* Nothing printed and no message waiting: a node's variables for them, as at switch-on. */
@@ -116,7 +115,7 @@ typedef char host_map_ports_fit[(V4_PORTS <= 8u) ? 1 : -1];
#define DICT_W ((v4_cell)8192)
#endif
#ifndef DICT_END_W
#define DICT_END_W ((v4_cell)14336)
#define DICT_END_W ((v4_cell)13824) /* 512 cells lower than it was: the block window is four slots now */
#endif
/* the messages waiting are above the dictionary */
typedef char host_map_queue_fits[(MQ_W >= DICT_END_W) ? 1 : -1];
@@ -169,7 +168,6 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
v4_text_constant(tx, "(S)", SVARS);
v4_text_constant(tx, "(W)", WVARS);
v4_text_constant(tx, "(X)", XVARS);
v4_text_constant(tx, "(B)", BVARS);
v4_text_constant(tx, "(BOOT)", BOOT_CELLS);
v4_text_constant(tx, "(LOG-LEVEL)", LOG_LEVEL);
v4_text_constant(tx, "(ACL-HOOK)", ACL_HOOK);
@@ -209,10 +207,11 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
v4_text_constant(tx, "(OUT)", OUT_W);
v4_text_constant(tx, "(WORD-DEFINED)", WORD_DEFINED);
v4_text_constant(tx, "(WORD-FORGOTTEN)", WORD_FORGOTTEN);
v4_text_constant(tx, "BLOCK-READ#", V4_REQ_BLOCK_READ); /* the kernel's two block requests (v4/blocks.h) */
v4_text_constant(tx, "BLOCK-WRITE#", V4_REQ_BLOCK_WRITE);
v4_text_constant(tx, "BUF0", BUF0_W);
v4_text_constant(tx, "BUF1", BUF1_W);
v4_text_constant(tx, "BLOCK#", V4_REQ_BLOCK); /* the kernel's block requests (v4/blocks.h) */
v4_text_constant(tx, "BUFFER#", V4_REQ_BUFFER);
v4_text_constant(tx, "UPDATE#", V4_REQ_UPDATE);
v4_text_constant(tx, "SAVE-BUFFERS#", V4_REQ_SAVE_BUFFERS);
v4_text_constant(tx, "EMPTY-BUFFERS#", V4_REQ_EMPTY_BUFFERS);
v4_text_constant(tx, "(Q/)", QBASE); /* 5 cells */
v4_text_constant(tx, "(QE)", QBASE + 5); /* 8 */
v4_text_constant(tx, "(QR)", QBASE + 13); /* 5 */
-112
View File
@@ -1,112 +0,0 @@
/* test_blocks.c -- the kernel serves a node's block requests.
* docs/v4.0.0/MESH.md sections 8.2 and 8.3.
*
* A node, stopped, with a number and an address on its data stack; the
* blocks are v3's block subsystem (v3/src/block_subsystem.c): its fast RAM
* and a device of 64 blocks.
*/
#include "v4/blocks.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "block_subsystem.h"
extern void sf_time_init(void);
extern void log_set_level(int level);
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 DEVICE_BLOCKS 64u
#define BUF 100
static unsigned char ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE], dev[DEVICE_BLOCKS * BLK_FORTH_SIZE];
static v4_node n;
static v4_cell ask(v4_cell request, v4_cell block, v4_cell addr)
{
v4_dstack_push(&n.ds, 7777);
v4_dstack_push(&n.ds, block);
v4_dstack_push(&n.ds, addr);
if (!v4_blocks_serve(&n, request)) return -100;
{
v4_cell status = v4_dstack_pop(&n.ds);
return v4_dstack_pop(&n.ds) == 7777 ? status : -200; /* what was under its arguments is still there */
}
}
int main(void)
{
unsigned i;
v4_cell top = (v4_cell)V4_NODE_WORDS;
printf("v4 block request tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
sf_time_init();
log_set_level(-1);
CHECK(blk_subsys_init(ram, sizeof ram) == BLK_OK && blk_subsys_add_raw_device(dev, DEVICE_BLOCKS) == BLK_OK, "the kernel's chain: fast RAM and a device of 64 blocks");
v4_node_reset(&n);
/* ---- a block is written and read ---- */
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[BUF + i] = (v4_cell)((v4_ucell)0x41424344u + i);
#if V4_CELL_BITS > 32
n.mem[BUF + 5] = (v4_cell)0x7766554433221100LL; /* only its low 32 bits are the block's */
#endif
CHECK(ask(V4_REQ_BLOCK_WRITE, 2050, BUF) == V4_BLOCK_OK, "a block is written");
CHECK(dev[2 * 1024] == 0x44 && dev[2 * 1024 + 1] == 0x43 && dev[2 * 1024 + 2] == 0x42 && dev[2 * 1024 + 3] == 0x41 && dev[2 * 1024 + 4] == 0x45,
"block 2050 is the device's third, four bytes to a cell, the first lowest");
CHECK(dev[1 * 1024 + 1023] == 0 && dev[3 * 1024] == 0, "and no other block was touched");
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[400 + i] = -1;
CHECK(ask(V4_REQ_BLOCK_READ, 2050, 400) == V4_BLOCK_OK, "it is read");
for (i = 0; i < V4_BLOCK_CELLS && (i == 5 || n.mem[400 + i] == (v4_cell)((v4_ucell)0x41424344u + i)); i++) { }
CHECK(i == V4_BLOCK_CELLS, "as it was written: cell %u differs", i);
#if V4_CELL_BITS > 32
CHECK(n.mem[400 + 5] == (v4_cell)0x33221100, "the rest of each cell is zero");
#else
CHECK(n.mem[400 + 5] == (v4_cell)((v4_ucell)0x41424344u + 5u), "cell 5 as the others");
#endif
CHECK(n.mem[399] == 0 && n.mem[400 + V4_BLOCK_CELLS] == 0, "256 cells and no more");
n.mem[BUF] = 0x5A;
CHECK(ask(V4_REQ_BLOCK_WRITE, 20, BUF) == V4_BLOCK_OK && ram[((size_t)20 + BLK_FORTH_SYS_RESERVED) * 1024] == 0x5A, "a block below 2048 is the chain's fast RAM");
/* ---- numbers that are no block ---- */
CHECK(ask(V4_REQ_BLOCK_READ, 0, 400) == V4_BLOCK_RANGE, "0 is no block");
CHECK(ask(V4_REQ_BLOCK_READ, -1, 400) == V4_BLOCK_RANGE && ask(V4_REQ_BLOCK_WRITE, -5, BUF) == V4_BLOCK_RANGE, "nor is a number below it");
CHECK(ask(V4_REQ_BLOCK_READ, 2111, 400) == V4_BLOCK_OK && ask(V4_REQ_BLOCK_READ, 2112, 400) == V4_BLOCK_RANGE, "the device's last block is, and the one after it is not");
CHECK(ask(V4_REQ_BLOCK_WRITE, 2112, BUF) == V4_BLOCK_RANGE, "nor can it be written");
#if V4_CELL_BITS > 32
CHECK(ask(V4_REQ_BLOCK_READ, (v4_cell)0x100000014LL, 400) == V4_BLOCK_RANGE, "a number 32 bits do not hold is no block, whatever its low bits are");
#endif
/* ---- cells that are not in the node's memory ---- */
n.mem[top - 1] = 123;
CHECK(ask(V4_REQ_BLOCK_READ, 2050, top - 255) == V4_BLOCK_REFUSED && n.mem[top - 1] == 123, "256 cells that run past the end of memory are refused, and nothing is read");
CHECK(ask(V4_REQ_BLOCK_READ, 2050, top - 256) == V4_BLOCK_OK, "the last 256 cells are not");
CHECK(ask(V4_REQ_BLOCK_READ, 2050, -1) == V4_BLOCK_REFUSED && ask(V4_REQ_BLOCK_WRITE, 2050, top) == V4_BLOCK_REFUSED, "an address below 0, or at the end, is refused");
CHECK(dev[2 * 1024] == 0x44, "and nothing was written");
/* ---- a request with too little on the stack ---- */
{
unsigned char before = dev[2 * 1024];
v4_cell low = n.mem[0];
v4_dstack_reset(&n.ds);
CHECK(v4_blocks_serve(&n, V4_REQ_BLOCK_READ) && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED, "a request with nothing on the stack is refused, and the status is all that is left");
v4_dstack_push(&n.ds, 2050);
CHECK(v4_blocks_serve(&n, V4_REQ_BLOCK_WRITE) && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED, "and so is one with a single value");
CHECK(dev[2 * 1024] == before && n.mem[0] == low, "nothing was read or written");
}
/* ---- what v3 wrote ---- */
{
uint8_t *b = blk_get_buffer(2070, 1);
CHECK(b != 0, "v3 gives a buffer for block 2070");
if (b) { memset(b, 'V', BLK_FORTH_SIZE); CHECK(blk_update(2070) == BLK_OK && blk_flush(2070) == BLK_OK, "and v3 writes it"); }
CHECK(ask(V4_REQ_BLOCK_READ, 2070, 400) == V4_BLOCK_OK && n.mem[400] == (v4_cell)0x56565656 && n.mem[400 + 255] == (v4_cell)0x56565656, "what v3's own calls wrote is what the node reads");
}
/* ---- what is not a block request ---- */
v4_dstack_push(&n.ds, 11); v4_dstack_push(&n.ds, 22);
CHECK(!v4_blocks_serve(&n, 5) && !v4_blocks_serve(&n, 0) && !v4_blocks_serve(&n, -3) && v4_dstack_pop(&n.ds) == 22 && v4_dstack_pop(&n.ds) == 11,
"any other request is not served here, and the stack is as it was");
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}
+176
View File
@@ -0,0 +1,176 @@
/* test_blocks.c -- the kernel serves a node's block requests.
* docs/v4.0.0/MESH.md sections 8.2 and 8.3.
*
* A host node, stopped, with what a request takes on its data stack; the blocks
* are v3's block subsystem (v3/src/block_subsystem.c): its fast RAM and a
* device of 64 blocks. The window is four slots of 256 cells in the node's
* memory, and the kernel keeps the record of what is in them.
*/
#include "v4/blocks.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "block_subsystem.h"
extern void sf_time_init(void);
extern void log_set_level(int level);
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 DEVICE_BLOCKS 64u
#define WINDOW 200
static unsigned char ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE], dev[DEVICE_BLOCKS * BLK_FORTH_SIZE];
static v4_node n;
static v4_blocks w;
static v4_cell got_addr;
/* a request that takes a block's number: its status, and the address in got_addr */
static v4_cell ask(v4_cell request, v4_cell block)
{
v4_cell status;
v4_dstack_reset(&n.ds);
v4_dstack_push(&n.ds, 7777);
v4_dstack_push(&n.ds, block);
if (!v4_blocks_serve(&w, &n, request)) return -100;
status = v4_dstack_pop(&n.ds);
got_addr = v4_dstack_pop(&n.ds);
return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? status : -200; /* what was under its argument is still there */
}
/* a request that takes nothing: its status */
static v4_cell tell(v4_cell request)
{
v4_dstack_reset(&n.ds);
v4_dstack_push(&n.ds, 7777);
if (!v4_blocks_serve(&w, &n, request)) return -100;
if (request == V4_REQ_EMPTY_BUFFERS) return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? 0 : -200;
{
v4_cell status = v4_dstack_pop(&n.ds);
return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? status : -200;
}
}
static int in_window(v4_cell a) { return a >= WINDOW && a < WINDOW + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS) && (a - WINDOW) % (v4_cell)V4_BLOCK_CELLS == 0; }
static unsigned char *chain_block(unsigned num) { return num < 2048u ? ram + ((size_t)num + BLK_FORTH_SYS_RESERVED) * 1024u : dev + (size_t)(num - 2048u) * 1024u; }
int main(void)
{
unsigned i;
v4_cell a, b;
printf("v4 block request tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
sf_time_init();
log_set_level(-1);
CHECK(blk_subsys_init(ram, sizeof ram) == BLK_OK && blk_subsys_add_raw_device(dev, DEVICE_BLOCKS) == BLK_OK, "the kernel's chain: fast RAM and a device of 64 blocks");
v4_node_reset(&n);
v4_blocks_init(&w, WINDOW);
/* ---- BLOCK: the block is copied into a slot of the window ---- */
memset(chain_block(2050), 'A', 1024);
chain_block(2050)[1] = 'B';
n.mem[WINDOW - 1] = 111; n.mem[WINDOW + V4_BLOCK_SLOTS * V4_BLOCK_CELLS] = 222;
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && in_window(got_addr), "BLOCK gives the address of a slot of the window");
a = got_addr;
CHECK(n.mem[a] == (v4_cell)0x41414241 && n.mem[a + 255] == (v4_cell)0x41414141, "holding the block, four bytes to a cell, the first lowest, the rest of each cell zero");
chain_block(2050)[0] = 'Z';
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && got_addr == a && n.mem[a] == (v4_cell)0x41414241, "a block already in a slot is given as it is there, and not read again");
CHECK(ask(V4_REQ_BLOCK, 20) == V4_BLOCK_OK && in_window(got_addr) && got_addr != a, "another block is put in another slot");
CHECK(n.mem[WINDOW - 1] == 111 && n.mem[WINDOW + V4_BLOCK_SLOTS * V4_BLOCK_CELLS] == 222, "nothing outside the window is written");
/* ---- UPDATE marks; nothing is written until SAVE-BUFFERS ---- */
CHECK(ask(V4_REQ_BLOCK, 2051) == V4_BLOCK_OK, "block 2051 is asked for");
b = got_addr;
n.mem[b] = (v4_cell)0x64636261;
#if V4_CELL_BITS > 32
n.mem[b + 1] = (v4_cell)0x7766554433221100LL; /* only its low 32 bits are the block's */
#endif
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && chain_block(2051)[0] == 0, "UPDATE marks it, and it is not written yet");
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && memcmp(chain_block(2051), "abcd", 4) == 0, "SAVE-BUFFERS writes it");
#if V4_CELL_BITS > 32
CHECK(chain_block(2051)[4] == 0x00 && chain_block(2051)[7] == 0x33 && chain_block(2051)[8] == 0, "the low 32 bits of each cell, and no more");
#endif
CHECK(chain_block(2050)[0] == 'Z' && chain_block(2050)[1] == 'B', "a block that was not UPDATEd is not written");
n.mem[b] = (v4_cell)0x31313131;
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2051)[0] == 'a', "nor is one changed again and not UPDATEd again");
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "UPDATE is of the block most recently asked for");
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK
&& chain_block(2051)[0] == '1' && chain_block(2050)[0] == 'A', "two UPDATEd blocks are both written, each from its own slot");
/* ---- EMPTY-BUFFERS forgets, and writes nothing ---- */
n.mem[a] = (v4_cell)0x58585858;
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_EMPTY_BUFFERS) == 0 && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK
&& chain_block(2050)[0] == 'A', "EMPTY-BUFFERS forgets an UPDATEd block: it is not written");
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK, "UPDATE with no block asked for does nothing");
chain_block(2050)[0] = 'Q';
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && (n.mem[got_addr] & 0xFF) == 'Q', "and the block is read again when it is next asked for");
/* ---- BUFFER: a slot for the block, not read ---- */
(void)tell(V4_REQ_EMPTY_BUFFERS);
CHECK(ask(V4_REQ_BUFFER, 2060) == V4_BLOCK_OK && in_window(got_addr) && n.mem[got_addr] == 0 && n.mem[got_addr + 255] == 0, "BUFFER gives a slot of zeros, as v3's does");
n.mem[got_addr] = (v4_cell)0x62626262;
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2060)[0] == 'b' && chain_block(2060)[4] == 0, "and what is put in it is written");
CHECK(ask(V4_REQ_BLOCK, 2060) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x62626262, "BLOCK then finds it in its slot");
/* ---- more blocks than slots ---- */
(void)tell(V4_REQ_EMPTY_BUFFERS);
for (i = 0; i < V4_BLOCK_SLOTS; i++) {
CHECK(ask(V4_REQ_BLOCK, 2100 + (v4_cell)i) == V4_BLOCK_OK, "block %u is asked for", 2100 + i);
n.mem[got_addr] = (v4_cell)(0x30303030 + i);
}
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "the last of them is UPDATEd");
CHECK(ask(V4_REQ_BLOCK, 2110) == V4_BLOCK_OK && chain_block(2100 + V4_BLOCK_SLOTS - 1)[0] == 0, "one more takes a slot whose block was not UPDATEd, and nothing is written");
for (i = 0; i < V4_BLOCK_SLOTS; i++) { (void)ask(V4_REQ_BLOCK, 2100 + (v4_cell)i); n.mem[got_addr] = (v4_cell)(0x30303030 + i); (void)tell(V4_REQ_UPDATE); }
for (i = 0; i < V4_BLOCK_SLOTS && chain_block(2100 + i)[0] == 0; i++) { }
CHECK(i == V4_BLOCK_SLOTS, "with every slot UPDATEd nothing is written until a slot is wanted");
{
unsigned written = 0;
CHECK(ask(V4_REQ_BLOCK, 2111) == V4_BLOCK_OK, "one more block is asked for");
for (i = 0; i < V4_BLOCK_SLOTS; i++) if (chain_block(2100 + i)[0] != 0) written++;
CHECK(written >= 1, "and the block whose slot it took was written first: %u written", written);
}
(void)tell(V4_REQ_EMPTY_BUFFERS);
/* ---- numbers that are no block ---- */
CHECK(ask(V4_REQ_BLOCK, 0) == V4_BLOCK_RANGE && got_addr == 0, "0 is no block");
CHECK(ask(V4_REQ_BLOCK, -1) == V4_BLOCK_RANGE && ask(V4_REQ_BUFFER, -5) == V4_BLOCK_RANGE, "nor is a number below it");
CHECK(ask(V4_REQ_BLOCK, 2111) == V4_BLOCK_OK && ask(V4_REQ_BLOCK, 2112) == V4_BLOCK_RANGE && ask(V4_REQ_BUFFER, 2112) == V4_BLOCK_RANGE, "the device's last block is, and the one after it is not");
#if V4_CELL_BITS > 32
CHECK(ask(V4_REQ_BLOCK, (v4_cell)0x100000014LL) == V4_BLOCK_RANGE, "a number 32 bits do not hold is no block, whatever its low bits are");
#endif
/* ---- a request with too little on the stack ---- */
v4_dstack_reset(&n.ds);
CHECK(v4_blocks_serve(&w, &n, V4_REQ_BLOCK) && n.ds.depth == 2 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED && v4_dstack_pop(&n.ds) == 0,
"BLOCK with nothing on the stack is refused: no address, and the status");
/* ---- a window that is not in the node's memory ---- */
{
v4_blocks bad;
v4_cell top = (v4_cell)V4_NODE_WORDS, status;
v4_blocks_init(&bad, top - (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS) + 1);
n.mem[top - 1] = 123;
v4_dstack_reset(&n.ds); v4_dstack_push(&n.ds, 2050);
CHECK(v4_blocks_serve(&bad, &n, V4_REQ_BLOCK), "a request with a window that runs past the end of memory is served");
status = v4_dstack_pop(&n.ds);
CHECK(status == V4_BLOCK_REFUSED && v4_dstack_pop(&n.ds) == 0 && n.mem[top - 1] == 123, "by being refused, with nothing written");
}
/* ---- the chain changes: what is in the slots is let go, as v3 does ---- */
(void)tell(V4_REQ_EMPTY_BUFFERS);
CHECK(ask(V4_REQ_BLOCK, 2070) == V4_BLOCK_OK && (n.mem[got_addr] = (v4_cell)0x59595959) != 0 && tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "a block is in a slot, UPDATEd");
{
static unsigned char more[4 * 1024];
CHECK(blk_subsys_add_raw_device(more, 4) == BLK_OK, "a device joins the chain");
memset(chain_block(2070), 'N', 1024);
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2070)[0] == 'N', "what was in the slots is not written to the chain as it now is");
CHECK(ask(V4_REQ_BLOCK, 2070) == V4_BLOCK_OK && (n.mem[got_addr] & 0xFF) == 'N', "and the block is read again");
}
/* ---- what is not a block request ---- */
v4_dstack_reset(&n.ds);
v4_dstack_push(&n.ds, 11); v4_dstack_push(&n.ds, 22);
CHECK(!v4_blocks_serve(&w, &n, 5) && !v4_blocks_serve(&w, &n, 0) && !v4_blocks_serve(&w, &n, -6) && v4_dstack_pop(&n.ds) == 22 && v4_dstack_pop(&n.ds) == 11,
"any other request is not served here, and the stack is as it was");
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}
+23 -10
View File
@@ -78,7 +78,7 @@ static char out[V4_CONSOLE_CAP + 1];
#define REFUSING_FIRST "2112" /* its first block */
static unsigned char ram[RAM_BYTES], common_dev[DISK_BLOCKS * V4_BLOCK_BYTES], refusing_dev[REFUSING_BLOCKS * V4_BLOCK_BYTES];
#define disk (ram + (size_t)BLK_FORTH_SYS_RESERVED * BLK_FORTH_SIZE)
static unsigned long block_requests; /* how many the node has made */
static v4_blocks kblocks; /* what the kernel keeps of the node's block window */
extern void sf_time_init(void);
extern void log_set_level(int level);
@@ -122,7 +122,7 @@ static v4_cell last_request;
static void kernel_serve(void)
{
last_request = n.request;
if (v4_blocks_serve(&n, n.request)) { block_requests++; return; } /* a block, as for any node (blocks.h) */
if (v4_blocks_serve(&kblocks, &n, n.request)) return; /* a block, as for any node (blocks.h) */
if (n.request == 1) {
v4_cell xt = v4_dstack_pop(&n.ds);
if (known_n < 1024) known[known_n++] = xt;
@@ -266,6 +266,7 @@ static void boot_with(unsigned depth)
n.mem[SRC] = TIB;
for (v4_cell k = BVARS; k < BVARS + 14; k++) if (k != SRC) n.mem[k] = 0; /* empty buffers, SCR and BLK 0, no hook */
memset(ram, 0, sizeof ram); memset(common_dev, 0, sizeof common_dev);
v4_blocks_init(&kblocks, BUF0_W);
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST);
v4_node_fault_attach(&n, w_fault);
v4_node_error_attach(&n, NODE_ERROR);
@@ -1175,9 +1176,10 @@ int main(void)
boot();
CHECK(is(say(REFUSING_FIRST " BLOCK DROP UPDATE SAVE-BUFFERS 65 EMIT\n"), "Storage refused\n ERROR\nok> "), "a write storage will not take says so, and the prompt returns");
{
unsigned long before = block_requests;
CHECK(is(say(REFUSING_FIRST " BLOCK DROP\n"), " ok\nok> ") && block_requests == before + 1, "the buffer was emptied: the next BLOCK asks the kernel again");
CHECK(is(say(REFUSING_FIRST " BLOCK DROP\n"), " ok\nok> ") && block_requests == before + 1, "and the one after finds it in the buffer");
unsigned k, held = 0;
for (k = 0; k < V4_BLOCK_SLOTS; k++) if (kblocks.block[k] == 2112u) held++;
CHECK(held == 0, "the block storage would not take is let go: no slot holds it");
CHECK(is(say(REFUSING_FIRST " BLOCK C@ .\n"), "0 ok\nok> "), "and the next BLOCK reads it from storage again");
}
/* ---- blocks: text loaded from storage ---- */
@@ -1225,11 +1227,22 @@ int main(void)
"an UPDATEd block is not written yet");
CHECK(is(say("FLUSH\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'H' && disk[3 * V4_BLOCK_BYTES + 1] == ' ' && disk[3 * V4_BLOCK_BYTES + 1023] == 'I'
&& disk[2 * V4_BLOCK_BYTES + 1023] == 0 && disk[4 * V4_BLOCK_BYTES] == 0, "FLUSH writes it, all 1024 bytes and no others");
CHECK(is(say("74 3 BLOCK C! UPDATE 4 BLOCK DROP\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'H', "with two buffers a second block does not disturb it");
CHECK(is(say("5 BLOCK DROP\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'J', "a third takes its buffer, and it is written first");
CHECK(is(say("EMPTY-BUFFERS 74 3 BLOCK C! UPDATE 4 BLOCK DROP 5 BLOCK DROP 6 BLOCK DROP\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'H',
"with four slots three more blocks do not disturb it");
CHECK(is(say("7 BLOCK DROP\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'H', "a fifth takes the slot of a block that was not UPDATEd, and nothing is written");
CHECK(is(say("77 4 BLOCK C! UPDATE 77 5 BLOCK C! UPDATE 77 6 BLOCK C! UPDATE\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'H'
&& disk[4 * V4_BLOCK_BYTES] != 'M' && disk[5 * V4_BLOCK_BYTES] != 'M' && disk[6 * V4_BLOCK_BYTES] != 'M', "four UPDATEd blocks fill the slots, and none is written yet");
{
unsigned written;
CHECK(is(say("8 BLOCK DROP\n"), " ok\nok> "), "one more block is asked for");
written = (disk[3 * V4_BLOCK_BYTES] == 'J') + (disk[4 * V4_BLOCK_BYTES] == 'M') + (disk[5 * V4_BLOCK_BYTES] == 'M') + (disk[6 * V4_BLOCK_BYTES] == 'M');
CHECK(written == 1, "it takes the slot of one of them, and that one is written first: %u written", written);
}
CHECK(is(say("FLUSH\n"), " ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'J' && disk[4 * V4_BLOCK_BYTES] == 'M' && disk[5 * V4_BLOCK_BYTES] == 'M' && disk[6 * V4_BLOCK_BYTES] == 'M',
"FLUSH writes the rest");
CHECK(is(say("3 BLOCK C@ . 75 3 BLOCK C! UPDATE EMPTY-BUFFERS FLUSH 3 BLOCK C@ .\n"), "74 74 ok\nok> ") && disk[3 * V4_BLOCK_BYTES] == 'J',
"EMPTY-BUFFERS forgets a change without writing it");
CHECK(is(say("76 3 BLOCK C! 5 BLOCK DROP 6 BLOCK DROP 3 BLOCK C@ .\n"), "74 ok\nok> "), "a change without UPDATE is lost when the buffer is taken");
CHECK(is(say("76 3 BLOCK C! 5 BLOCK DROP 6 BLOCK DROP 7 BLOCK DROP 8 BLOCK DROP 3 BLOCK C@ .\n"), "74 ok\nok> "), "a change without UPDATE is lost when the slot is taken");
CHECK(is(say("3 BLOCK 4 BLOCK 1024 CMOVE UPDATE SAVE-BUFFERS\n"), " ok\nok> ") && memcmp(disk + 3 * V4_BLOCK_BYTES, disk + 4 * V4_BLOCK_BYTES, V4_BLOCK_BYTES) == 0
&& disk[4 * V4_BLOCK_BYTES] == 'J', "two blocks in memory at once: one copied to the other");
put_block(40, "this is on the device");
@@ -1238,7 +1251,7 @@ int main(void)
{
v4_cell k, wide = 0;
(void)say("3 BLOCK DROP\n");
for (k = 0; k < 2 * 256; k++) if ((v4_ucell)n.mem[BUF0_W + k] > 0xFFFFFFFFu) wide = 1;
for (k = 0; k < (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS); k++) if ((v4_ucell)n.mem[BUF0_W + k] > 0xFFFFFFFFu) wide = 1;
CHECK(!wide, "a block's bytes are four to a cell at every cell width");
}
{
@@ -1396,7 +1409,7 @@ int main(void)
&& is(say("4 LOG-LEVEL!\n"), " ok\nok> "), "a vocabulary, a base, a block, a screen, a log level");
CHECK(is(say("1 2 COLD 65 EMIT\n"), "FORTH-79 Cold Start\nSystem initialized.\n ok\nok> "), "COLD says so");
CHECK(n.mem[DP] == dp && n.mem[LATEST] == capsule_latest && n.mem[CONTEXT] == LATEST && n.mem[CURRENT] == LATEST && n.mem[VOC_LINK] == 0
&& n.mem[BASE] == 10 && n.mem[SCR] == 0 && n.mem[BVARS] == 0 && n.mem[FENCE] == dp / 4 && n.mem[LOG_LEVEL] == 2, "and everything is as the loader left it");
&& n.mem[BASE] == 10 && n.mem[SCR] == 0 && kblocks.block[0] == 0 && kblocks.block[1] == 0 && kblocks.block[2] == 0 && kblocks.block[3] == 0 && n.mem[FENCE] == dp / 4 && n.mem[LOG_LEVEL] == 2, "and everything is as the loader left it");
CHECK(is(say("KEEPME\n"), "UNKNOWN WORD: 'KEEPME'\n ERROR\nok> ") && is(say("VV\n"), "UNKNOWN WORD: 'VV'\n ERROR\nok> ")
&& is(say(".S : NEW 16 . ; NEW\n"), "<0> \n16 ok\nok> "), "what was defined is gone, and the system works");
}
+6 -3
View File
@@ -113,9 +113,11 @@ 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)
{
if (!v4_blocks_serve((v4_node *)self, value)) refused++;
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];
@@ -135,8 +137,9 @@ static int host_born(void *self)
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].n;
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k];
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
}
return place;
@@ -187,7 +190,7 @@ static int kernel_give(void *self, v4_cell *value) /* the nucleus, to a nod
static int kernel_take(void *self, v4_cell value) /* a request */
{
(void)self;
if (v4_blocks_serve(&v4_fabric_node_at(&f, hera_place)->n, value)) return 1; /* a block, as for any node */
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;
}