fix(v4.0.0): storage -- what the review of step 6 found

A block request with fewer than two values on the stack is refused; it
had acted on whatever the stack ring held and stopped the node.

Bare metal: when the kernel's chain takes the place of POST's block RAM
the node's two buffers are emptied, so it no longer holds POST's copy of
a block; and the chain's fast RAM is cleared, so a node cannot read what
was in the kernel's heap.

blocks.c is built with each test under that test's own warnings and
sanitizers; it had been left out of both.  The hosted link cleans its
object directory first: it had linked the withdrawn store_v3.o left there
from the day before.

node.h and DECOMPOSITION.md D-19 no longer describe the message device or
the four registers as current.  MESH.md 8.5 records two findings for
ruling: a node's own copy of a block, and a block read over a node's code.

From a clean build: make -C v4 test, sanitize and hosted-check pass;
amd64, aarch64 and riscv64 boot, POST 538 of 538, same hashes, blocks 1
and 2047 clean at the prompt: logs/20261007-085017, -085254, -085636.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 08:58:52 -04:00
co-authored by Claude Opus 5.5
parent 6d90375da7
commit bcfd6556a8
15 changed files with 543 additions and 33 deletions
+12 -18
View File
@@ -63,24 +63,18 @@ uses_blocks = $(or $(findstring /test_blocks,$(1)),$(findstring /test_host_,$(1)
blocks_inc = $(if $(call uses_blocks,$(1)),$(V3_INC))
$(BINDIR)/v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)/v3o
@rm -rf $(BINDIR)/v3o && mkdir -p $(BINDIR)/v3o
cd $(BINDIR)/v3o && $(CC) $(V3_CFLAGS) -c $(V3_BLOCK_SRCS)
$(LD) -r $(BINDIR)/v3o/*.o -o $@
$(BINDIR)/san-v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)/san-v3o
@rm -rf $(BINDIR)/san-v3o && mkdir -p $(BINDIR)/san-v3o
cd $(BINDIR)/san-v3o && $(CC) $(V3_CFLAGS) -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=all -c $(V3_BLOCK_SRCS)
$(LD) -r $(BINDIR)/san-v3o/*.o -o $@
# blocks.c holds a node, so it is built for each node a test may have: by
# width, and for the host node's size or the mesh node's.
define BLOCKS_RULE
$(BINDIR)/blocks-$(1)-$(2).o: $(BLOCKS_SRC) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(if $(filter host,$(2)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)) -c $(BLOCKS_SRC) -o $$@
endef
$(foreach w,32 64,$(foreach k,host mesh,$(eval $(call BLOCKS_RULE,$(w),$(k)))))
blocks_kind = $(if $(findstring /test_host_,$(1)),host,mesh)
blocks_objs = $(if $(call uses_blocks,$(2)),$(BINDIR)/blocks-$(1)-$(call blocks_kind,$(2)).o $(BINDIR)/v3blocks.o)
san_blocks_objs = $(if $(call uses_blocks,$(2)),$(BINDIR)/blocks-$(1)-$(call blocks_kind,$(2)).o $(BINDIR)/san-v3blocks.o)
# blocks.c is compiled with each test that uses it, under that test's own
# flags: its warnings, its sanitizers, its cell width and node size.
blocks_src = $(if $(call uses_blocks,$(1)),$(BLOCKS_SRC))
blocks_objs = $(if $(call uses_blocks,$(1)),$(BINDIR)/v3blocks.o)
san_blocks_objs = $(if $(call uses_blocks,$(1)),$(BINDIR)/san-v3blocks.o)
# The capsule sources: definitions as text (include/v4/text.h), which tests
# assemble onto a node. The directory is passed to the tests so that they
@@ -160,7 +154,7 @@ $(CAPSULE_DIR_C): $(BINDIR)/mkcapsule $(CAPSULE_FILES) $(BINDIR)/v4_image_64.c
define HOSTED_RULE
$(BINDIR)/starforth4-$(1): $(HERE)/tools/hosted.c $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) $(BLOCKS_SRC) $(V3_BLOCK_SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
$$(CC_$(1)) $$(CFLAGS) -D_POSIX_C_SOURCE=200809L -I$(HERE)/include $(V3_INC) -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(HERE)/tools/hosted.c -o $(BINDIR)/hosted-$(1).o
@mkdir -p $(BINDIR)/v3o-$(1)
@rm -rf $(BINDIR)/v3o-$(1) && mkdir -p $(BINDIR)/v3o-$(1)
cd $(BINDIR)/v3o-$(1) && $$(CC_$(1)) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(V3_BLOCK_SRCS) $(BLOCKS_SRC)
$$(CC_$(1)) $$(SYSTEM_CFLAGS) -static $(BINDIR)/hosted-$(1).o $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) $(BINDIR)/v3o-$(1)/*.o -o $$@
@@ -226,16 +220,16 @@ endif
#
# Both binaries depend on this Makefile, so a flag change rebuilds them.
define TEST_RULE
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_src,$(2)) $(call blocks_objs,$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CFLAGS) -I$(HERE)/include $(call blocks_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) $(call blocks_objs,$(1),$(2)) -o $$@
$(2) $$(SRCS) $(call blocks_src,$(2)) $(call blocks_objs,$(2)) -o $$@
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call san_blocks_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_src,$(2)) $(call san_blocks_objs,$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CSTD) $$(WARN) -O1 -g -fno-omit-frame-pointer \
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include $(call blocks_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) $(call san_blocks_objs,$(1),$(2)) -o $$@
$(2) $$(SRCS) $(call blocks_src,$(2)) $(call san_blocks_objs,$(2)) -o $$@
endef
# One run target per (width, test) and one prerequisite line per width, so
+3 -1
View File
@@ -12,7 +12,9 @@
* and their numbers are below zero: the requests a host names for its own
* words (KERNEL-WORD) count up from 1. The status is 0, or
* V4_BLOCK_REFUSED -- the cells are not all in the node's memory, or
* storage will not have it -- or V4_BLOCK_RANGE, there is no such block.
* storage will not have it, or the stack did not hold both arguments, in
* which case the status is all that is left on it -- or V4_BLOCK_RANGE,
* there is no such block.
*
* A block is 1024 bytes and 256 cells, four bytes to a cell, the first
* lowest, whatever the cell width: a read leaves the rest of each cell
+3 -3
View File
@@ -331,9 +331,9 @@ void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr);
* detaches both (-1); the addresses are the caller's choice (D-4). */
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r);
/* A BLOCK (DECOMPOSITION.md D-19, 5.11). Mass storage is a device on a
* port that speaks messages (docs/v4.0.0/MESH.md section 8; storage.h); the
* node has nothing of it but these. A block is 1024 bytes, and a byte
/* A BLOCK (DECOMPOSITION.md D-19, 5.11). Mass storage is the kernel's: a
* node asks it for a block by a request on port 0 (docs/v4.0.0/MESH.md
* section 8; blocks.h), and has nothing of it but these. A block is 1024 bytes, and a byte
* address is four times a word address plus 0 .. 3 (D-1), so a block is 256
* cells, four bytes to a cell, the first byte lowest, whatever the cell
* width. */
+5
View File
@@ -15,6 +15,11 @@ int v4_blocks_serve(v4_node *n, v4_cell request)
int status;
if (request != V4_REQ_BLOCK_READ && request != V4_REQ_BLOCK_WRITE) return 0;
if (n->ds.depth < 2u) { /* not both arguments: nothing is done with what is there */
v4_dstack_reset(&n->ds);
v4_dstack_push(&n->ds, (v4_cell)V4_BLOCK_REFUSED);
return 1;
}
addr = v4_dstack_pop(&n->ds);
num = v4_dstack_pop(&n->ds);
block = (uint32_t)num;
+11
View File
@@ -83,6 +83,17 @@ int main(void)
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);