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>
177 lines
10 KiB
C
177 lines
10 KiB
C
/* 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;
|
|
}
|