From 9bfd5071d250cc4596cabd6830a9810a93cbd14a Mon Sep 17 00:00:00 2001 From: rajames Date: Wed, 7 Oct 2026 09:37:30 -0400 Subject: [PATCH] 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 --- capsules/BLOCK_MAP.md | 2 +- capsules/v4/nucleus-64.f18 | Bin 53216 -> 52520 bytes docs/v4.0.0/DECOMPOSITION.md | 2 +- docs/v4.0.0/MESH.md | 136 +++++++------ docs/v4.0.0/V3-PARITY.md | 7 +- .../amd64/qemu-amd64-20261007-092835.log | 148 ++++++++++++++ .../aarch64/qemu-aarch64-20261007-093112.log | 121 +++++++++++ .../riscv64/qemu-riscv64-20261007-093456.log | 191 ++++++++++++++++++ v4/Makefile | 6 +- v4/README.md | 13 +- v4/capsule/blocks.v4 | 96 +++------ v4/include/v4/blocks.h | 77 +++++-- v4/include/v4/image.h | 4 + v4/system/blocks.c | 159 ++++++++++++--- v4/system/boot.c | 6 +- v4/tests/host_map.h | 17 +- v4/tests/test_blocks.c | 112 ---------- v4/tests/test_host_blocks.c | 176 ++++++++++++++++ v4/tests/test_host_quit.c | 33 ++- v4/tests/test_host_unit.c | 9 +- v4/tools/mkimage.c | 6 +- 21 files changed, 991 insertions(+), 330 deletions(-) create mode 100644 logs/20261007-092835/amd64/qemu-amd64-20261007-092835.log create mode 100644 logs/20261007-093112/aarch64/qemu-aarch64-20261007-093112.log create mode 100644 logs/20261007-093456/riscv64/qemu-riscv64-20261007-093456.log delete mode 100644 v4/tests/test_blocks.c create mode 100644 v4/tests/test_host_blocks.c diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index dde6ec6b..0154e10a 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index b93a03fe04c8855df31876a230f59b840a53fc2e..b604f3dd9c6b1e30c6c15314482925cf76ddd9d1 100644 GIT binary patch delta 4225 zcmZ`-eNa@_6@O1wWH%0Gw==xAXkC_6a2aP-jkEgDh#$z8hzlwh(}V;wL9sKusu4q- zVmh6swT?UHpicNBMkk#l#*Tw)8$pz1iy9C&Ks&LuO|aBVtfMV8C01;t_v4(0+3CJN z?s@0jbAIRJo_pWjy>T?@{gx!B`T0Ph!k#Djz@EBRr)l#Vl6g;2=JL0ZN6uB8j^m>n zaNL%s8Zc}-+Jw3hWxvq5wUBEnN>u5-z^NHC+Bw}A;G?3P41F#fu1C9dNX^ul!d{hX z%AJ2-6M7qPzAp|xhWJg19FSqW14?4#z;)qZ89JDZj?Iyxb^O(g$p8v)en?Rkarry< zogyK`Ng6LO;3vrqtQup<9yX>=@lZ|9DT@9$)N+K1m#;`5UztFjpFq9}4Dx2UmCG*8Ru}~Xve!41?^r*6lcAi-(>QjO9q!(PU z^=)2^MJRt-?Hl9#%|FRHmu$m{d1~xkKJhdHDxKd3-1F`d;b1fBlcWA6i#Kt*jRtJ8 zd)zqdQTLwZ(u^-Z6=`iV;7z;Vz**|nmoZ86g7j5|^WVkf%$w5sWwy$8kKM9{Oh>l5 z5K0`icI^8)1KJ&YZ;UzoPSi7h`P)D97;Y^J=RR>HRsCal&|Rf6k~6*D##CE83soaL z=&7B`Nba1b6h_(?gr?0y`;`<0wq#|3=!y+!%x6$=T8y{zYbnpR6uF39DY6>7v4$@q z(o0CUgnSh|QdYx#tE`r|OiLdd5;ZJF?Ra@f0{Q9$^3nwIH7K7S@1QKsfDV(bC@(Xh zu7GdCeFcSV94x4(uFiPC(^8Zzm`X=TE+`>>ohRo{qPz$ZsVT=(_O^138FpE~Aa*&W zOs9&?E(`q|EHp$4?JSbs!fa|tikTL1ivf-zkJT!VmPWZB)@Zp!**=WI#KKcsF0jkJ zMrTwJ%ect$kjw$tb29Yv)ts`b^cGdKC~}I2*yt$n5N<8vpLyil$2E22-x)2x!ueVQ zDoR4kX1Ju*j&rXt+3G@ymuvCo6(f9N^(mF9Zdy}aj;gam&7xo8yKLv(ZyzD%+h5(~ zAiu}Op+%shneACe8DG|%vTO>YoOu$m#mYyLhhA6RNz-pPKDix{HZ78vYrgduM{o!t44VE`%M9k4fSjs1V0-m zHS;KPo2#kLBct++BlustBwT-+T)#_flBIn5el(EZkJIXV{tu0?pci&2r9==GMDo*Q17_kE0HXROrN3!F*1auNT4cSMiLa{lhq%ZxD!ZhL~e+Fm>JXN=Bfm-8&~m6A+3GDFyxC|6>b% z?K7O;LB(lA;X%7U(~X=?FgIlG4=os|J2!LwQ#hSLYPgSg;j*v{#8n`pLw;a(vCqW0 zYT8BS0eKp+KFH~)rmUz5fBcNhgfO~w*-g}(d-6B$=Ni;qrAqSioHt`WahBY`P&dF| zJ2dZe8lD|xPH{nd9cQ1_$3 z7?N>68ZToio#SYjj2P^*$c(hZ@}L%yOJTn-dyjMgoow zWZN~UVQe?DQ4NkWwh!4}4Tc%}2V@7CZC4=uFUVd;YNpCk?O=PnD{I9~6b~c4i`jjp z*~D!}l#>m#sMLS|vh?73mv!^(A06oC7CW&0#8Hlny!EiSUPpG?2z2ak!9Sh9=2YZOA}nX%ny)zkEHru5rd5f!k(}W!-x&v8_@Wy9*Jj8tkQJpAUcaiTo8hgiepekLykG?b*RKYeb|7n7VuCiNa0vPvfPR%*=R~Y{C7-8X l??g$z&fv2XkBR#9y9)J^lzMg_iycs31MepxHnx7^{~uV+rPcrd delta 4806 zcmZ`-eNa@_6@L#_WLF2fn-2RHQWj_igr-A?3=KRZZouV3W`?Qs9N z=bhg<_uO;OIrn4t568prz8!AX?p)88X!{Q9X!CWLHA8|bnD-^=IrcRA{Si`>qe?a# z?KY6w<>vDYjsM~=d|A6xa>SUH1yZcm*I2sk6M40`&hAK&L|>$cMia#F zRj^b8bh{^|f_X|~*s?}1i4jbFV2sDG5VS+_q6P4^3*f~I;OhW?K6HYTkO6p@>;Sw( zB|DvL6Sg?>X@A4H8>>4030syVZHJ{?@Q8$>#jkUOydCfYu!u!D7unpwG@dq!0G2_O zp7n}-%!c0qCqc%ebyu`Dx; zVz1#DRX|rE~CrV^Ic)RJU$qnysfvHc~7=ynM!wX(@$%2@eVl;zO=2>`-_-cyz%t`XAxD=<5LA+tqRWi&*z~w3tB)$o#NuS}D|Y9@KB&X`q`-lFEL0Ai!wT zi>#NV+jk!G(|Ih~$#E@^;%7K6{7bMu8ExS9XF&kBagDB6f8qqM0lWq<*RoYOm@%C) z!3^WX?3*>M}-8!hF~W zau>*T#{yG>Ode(xS8LOBO3(t)jzO9(TTiC0ezyi6C2SOHXi;$d6}FmvWBO1ZXGDGE zX&Qs{<4MOkg+Gg^kx*g8OBRPvY1I`++5DL9Y| z)YT$x2{eQiw%OWT$Oh0yE3YgJD~-%~a{H5KH@yM|DuISs`&aWBpbN7$a~|Wp$XUFB ziPjf@0+4MmehX4kb3QHRr@-w3n(^mRzQ>wgq{R?z&?d`rSydDniVJzpDuazm`(fN( zpc-5{``}QZ%ja>7zaU;i`{A=(UE0DI~i$$+U!rnijJ{cJ^kP zo&wgakPj#u3#?5c6O{cvun7uD?aMU%0N4~dwl6bg1+eKrwJ4FPz4T4mmlUG{oCP#b z5$9ZyLT38n+yy!-o;a8i16vYpT9o+Zwil{o^zn7RTI9?BZ`EJ%RllM%ceC-^l%+f> zxLNiD^NDk=U|{Z02!DihU#yak5OY84cktfFdz+6Tdwe;THdvbjXt6>De3_OLz`ivP zn+7s1zX$g8JX}HHv%sF2hmTYEJh12H;qieaqYv1N#5|Z|DOU2th1n{i)kvQPgGni$ zD{vpf4~#|#qP$3Kwd_UWfEXqngUj)aL!BQVOfuhv35J0Nh;1m%d>^=f=;$VjP6GF( zj{c6K{|4@B9sO)5Em5+uv`Eq{qKY__FyST}Nxif$<|$B%0E)+d9j&TMNtRfEZ9pS! ze|&hvUK=Hgap&nV1V`wk{RXS!NTo#76whapctz#Zn3? zg}N$f8F40T0=80zgDq?&QzKPb+d*2rfL5IiUr*vi&|vHDW|4A{gO}~{0rKu;tL!4( zm#fkbf`!)?u=GZVrB>p&QkwW%&^{D0>YnSsO*RjWVds?+TRRM1X6x@~@cY33k=Xpn z@+so*=Oml~6UTt!IXn$NXikxL{nnIz-Hn_4xpxWzN`LPLom5`(H-DPN2ev-e-6M%t zGu?h*M|4=kmKMZf<{ti+gn#Ye^{fR7`BM)60z!)VJ7F&{8?2!|{x}D{aA~(nZe4Aa ON6C(B9@;;-mi9kdo!`3v diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index bcc7dbae..3216d022 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -180,7 +180,7 @@ definition below depends on one, it says so. | **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. | | **D-17** | Stack sizes on the host node (2026-10-04, following D-16). | **32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9.** Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and `/` could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are `V4_DATA_RING` and `V4_RET_RING` (`stack.h`), set for the host-node tests in `v4/Makefile`. | | **D-18** | Every other error (ruled 2026-10-04: "guard all errors"). | **A word that finds an error raises it, and the line ends there.** The word takes its own arguments off the stack and stores an error code in `NODE-ERROR` (§7). On a node with a prompt that store is a trap, a sixth kind of fault beside D-14's and D-16's: nothing after it executes, the return stack is emptied, and the prompt prints the code's message, ends any definition that was open, prints ` ERROR` and waits for the next line. Unlike the other faults it leaves the data stack as the word left it. The codes: 1 `Negative count` (`CMOVE`, `TYPE`), 2 `Not a number` (`NUMBER`), 3 `Number too long` (`HOLD` into a full buffer), 4 `Not a character` (`HOLD`), 5 `Dictionary full` (`,` `C,` `ALLOT`, any defining word), 6 `Name missing` (a defining word with nothing after it), 7 `Control structure mismatch`, 8 `Control structures too deep`, 9 `Shift count out of range`, 10 `Protected word`, 11 `Division by zero` (`Q./`), 12 `Argument out of range`, 13 `Block out of range`, 14 `No block is being loaded`, 15 `Deferred word not set`, 16 `Not a word`; −1 means the word printed its own message (`UNKNOWN WORD: 'xxx'`, `xxx: compile-only`). Before this ruling these set the flag and the line ran on to its end. v3 stops at once too; its messages name the word (`LOOP: missing DO`) where v4's name the fault. With the trap not attached `NODE-ERROR` is plain memory and the word returns, which is how the words are tested below the prompt. D-11, D-12 and D-13 said "set `NODE-ERROR`"; on a node with a prompt that now means this. Executed on the golden model (2026-10-04): `tests/test_exec.c`, `tests/test_host_quit.c`. | -| **D-19** | Block storage on the golden model (2026-10-05). **Replaced 2026-10-07:** the four registers are gone; a node asks its kernel for a block by a request on port 0 (`MESH.md` section 8, `v4/include/v4/blocks.h`). What follows is the decision as it was. | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. | +| **D-19** | Block storage on the golden model (2026-10-05). **Replaced 2026-10-07:** the four registers are gone; the block words are kernel requests on port 0, by block number, and the kernel keeps the node's window of four slots (`MESH.md` section 8, `v4/include/v4/blocks.h`). What follows is the decision as it was. | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. | **Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index 80551ec7..5f547c69 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -315,34 +315,57 @@ in 8.6, so that it is not proposed again. ### 8.2 What a node does *(step 6)* -`BLOCK`, `BUFFER`, `UPDATE` and `SAVE-BUFFERS` keep their two buffers and -their FORTH-79 behaviour. The one word beneath them in -`v4/capsule/blocks.v4` that read or wrote a block by storing to four -registers makes a kernel request instead: it puts the block's number and -the word address of the buffer's 256 cells on the data stack and writes the -request's number to port 0, where its kernel is. When the write has been -served the status is on the stack in their place: 0 it worked, 2 it was -refused, 3 there is no such block. +**Ruled 2026-10-07: v3's way in full.** The block words are the kernel's. +`BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS` and `EMPTY-BUFFERS` in +`v4/capsule/blocks.v4` each write one request to port 0, where the node's +kernel is, and do nothing else: `BLOCK` and `BUFFER` with the block's +number on the stack, the others with nothing. The node gives the kernel no +address and keeps no record of what is in its buffers. -There are two requests, read a block and write a block. Their numbers are -the same for every node and are below zero; the requests a host names for -its own words (`KERNEL-WORD`) count up from 1. +The node has a **window** in its memory, four slots of 256 cells, as a v3 +VM has four slots of 1 KiB at the top of its memory. The kernel copies a +block into a slot and gives the node the slot's address. + +The five requests' numbers are the same for every node and are below zero, +-1 to -5; the requests a host names for its own words (`KERNEL-WORD`) +count up from 1. The status a request leaves: 0 it worked, 2 it was +refused (error 17, "Storage refused"), 3 there is no such block (error 13). The four storage registers and `v4_node_storage_attach` go from the engine. ### 8.3 What the kernel does *(step 6)* Every node has its kernel on port 0, whatever else is there for it: Hera's -requests for nodes (section 9) are honoured only from Hera, and the two -block requests from every node. +requests for nodes (section 9) are honoured only from Hera, and the block +requests from every node. `v4/system/blocks.c`, which the hosted and the bare-metal builds and the -tests share, serves them: it takes the number and the address from the -node's data stack, checks that the 256 cells are in the node's memory, and -reads or writes the block through v3's block subsystem -(`v3/src/block_subsystem.c`), four characters to a cell, the first lowest. -The engine (`v4/src`) knows nothing of it. +tests share, serves them, from v3's block subsystem +(`v3/src/block_subsystem.c`). For each node it keeps what v3 keeps for each +VM (`v3/src/word_source/block_words.c`): which block is in which slot of +the window, which have been UPDATEd, and the chain as it was when they +were filled. The engine (`v4/src`) knows nothing of it. +- It writes into the node's window and nowhere else in the node. That is + how a request cannot overwrite the node's code (ruled 2026-10-07: the + kernel refuses it): there is no address for a node to give. +- `BLOCK` gives the slot a block is in, and reads it from storage only if + it is in none. `BUFFER` gives a slot of zeros without reading. +- **When a block is written is FORTH-79's:** `UPDATE` marks it, and it is + written by `SAVE-BUFFERS` or when its slot is wanted. v3's `UPDATE` + copies the slot to the kernel at once; the standard is followed + (standing ruling). A slot whose block is not marked is taken before one + whose block is. +- A block storage will not take is let go of, and its slot is empty. +- When the chain of devices changes, everything in the slots is let go and + none of it is written, as v3 does. +- Four characters to a cell, the first lowest; a read leaves the rest of + each cell zero and a write takes the low 32 bits. +- **Each node has its own copy of a block it holds, as each v3 VM has.** + If node A has block *n* in a slot and node B writes it, A goes on reading + what it had, and A's next `UPDATE` and `SAVE-BUFFERS` writes all of the + block over B's. v3 is the same. Closing that would be a departure from + v3 and is not ruled. - v3's code does what it does: header, allocation map, block cards, relocation, three blocks and their cards to a 4 KiB device block. - There is one chain, as in v3: fast RAM at blocks 0 to 2047, then the @@ -376,37 +399,20 @@ block number not to exist, and on the real chain it does. step 6a. - **Cloud stores and real USB drives** wait for their drivers. -### 8.5 Open, for ruling +### 8.5 Ruled 2026-10-07, after the review of step 6 -Found by review of step 6 on 2026-10-07; nothing is built for either. +The review found that a block request could be made by hand with an +address in the node's own code, and said that a node's own copy of a +block was a departure from v3. The second was wrong: a v3 VM has a window +of four slots in its own memory and `BLOCK` copies the kernel's block into +one (`v3/include/vm.h`, `BLK_VM_SLOTS`; `block_words.c`, `blk_vm_load`). +It was reported to Captain Bob as a departure before it was checked +against v3, and he ruled on it as one; it was then checked and taken back. -- **A node keeps its own copy of a block.** `BLOCK` gives the address of - one of the node's two buffers, and the kernel is asked only when a block - is not in one. If node A has block *n* in a buffer and node B writes it, - A goes on reading what it had; and if A then does `UPDATE` and - `SAVE-BUFFERS`, all of B's block is overwritten. - **Corrected 2026-10-07.** This entry first said that in v3 a VM's `BLOCK` - gives the kernel's buffer, so that every VM sees one copy. That was - wrong, and was not checked against v3 before it was written. In v3 each - VM has a window of four 1 KiB slots at the top of its own memory - (`v3/include/vm.h`, `BLK_VM_SLOTS`; `v3/src/word_source/block_words.c`, - `blk_vm_load`): `BLOCK` copies the kernel's block into a slot, unless - the block is already in one, and gives the slot's address. So a v3 VM - keeps its own copy too, and two VMs can each hold a stale one, exactly - as here. Where v4 differs from v3: - - two buffers, where v3 has four slots; - - the node keeps the record of what is in them and does the asking, - where in v3 the block words are the kernel's and the kernel keeps the - record and does the copying, only ever into the window; - - `UPDATE` marks the buffer and it is written when the buffer is wanted - or at `SAVE-BUFFERS`, as FORTH-79 has it, where v3's `UPDATE` copies - the slot to the kernel at once; - - v3 lets go of every slot when the device chain changes; v4 does that - in one place only, after POST on bare metal. -- **A block read over the node's own code.** The kernel checks that the - 256 cells are in the node's memory and nothing more, so a request made - by hand with an address in the nucleus overwrites it and the node stops. - `!` can do the same; whether the kernel should refuse it is not ruled. +His rulings, on what v3 does: **the kernel refuses a request that would +overwrite the node's code**, and **v3's way in full** — the block words +are the kernel's, by number only, with the kernel keeping the record of +the window and doing the copying. Both are built: 8.2 and 8.3. ### 8.6 Withdrawn 2026-10-07 @@ -628,17 +634,23 @@ Each is tested, committed and pushed before the next. 6. **Storage (section 8): every node asks the kernel for its blocks.** **Done 2026-10-07.** It was first built another way and brought back; 8.6 says what was withdrawn and why. - - *The requests* (`v4/include/v4/blocks.h`, `v4/system/blocks.c`). -1 - reads a block and -2 writes one, `( n waddr -- status )`, for any - node, from the kernel's block subsystem. `v4/tests/test_blocks.c`: 22 - checks at 64 bits, 21 at 32 — a block written and read, the fast RAM, - numbers that are no block, cells that are not in the node's memory, - and that what v3's own calls wrote is what the node reads. - - *The node* (`v4/capsule/blocks.v4`). `(DEVICE)` makes the request on - port 0. The four storage registers and `v4_node_storage_attach` are - gone from the engine. Error 17 is "Storage refused". - `v4/tests/test_host_quit.c`: its block cases unchanged, and a disk - that will not be written says so and empties the buffer. + - *The requests* (`v4/include/v4/blocks.h`, `v4/system/blocks.c`). Five, + -1 to -5: `BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS`, + `EMPTY-BUFFERS`, for any node, by block number only. The kernel keeps + the node's window of four slots. `v4/tests/test_host_blocks.c`: 41 + checks at 64 bits, 39 at 32 — a block copied into a slot and found + there again, `UPDATE` and when a block is written, `EMPTY-BUFFERS`, + `BUFFER`, more blocks than slots, numbers that are no block, too + little on the stack, a window not in the node's memory, and the chain + changing under what the slots hold. + - *The node* (`v4/capsule/blocks.v4`). Each block word is one request + on port 0; the node's own buffers and its record of them are gone, and + so are the four storage registers and `v4_node_storage_attach`. Error + 17 is "Storage refused". The window takes 512 cells more than the two + buffers did, and the dictionary space ends 512 cells lower, at 13824. + `v4/tests/test_host_quit.c`: its block cases, those that counted on + two buffers rewritten for four slots, and a disk that will not be + written says so and is let go of. - *v3* (`v3/src/block_subsystem.c`). `blk_subsys_init` takes no `VM`. Nothing else of it is changed. Accepted on the v3 configuration, three ISAs, `PARITY:M7.1a` hash `0x08873e0f44b7cb2a` as on 2026-10-03: @@ -658,7 +670,9 @@ Each is tested, committed and pushed before the next. a block that is not there. `logs/20261007-081603` (amd64), `-081839` (aarch64), `-082226` (riscv64); and again after the review's changes below, with blocks 1 and 2047 read clean at the prompt: - `logs/20261007-085017`, `-085254`, `-085636`. The parity hashes are the same on the + `logs/20261007-085017`, `-085254`, `-085636`; and as it stands, with the + block words the kernel's: `logs/20261007-092835`, `-093112`, + `-093456`. The parity hashes are the same on the three hosted and the three bare-metal systems. - *Review, 2026-10-07.* One reading of the whole step by a fresh reviewer; no critical defect. Changed after it: a block request with @@ -669,7 +683,9 @@ Each is tested, committed and pushed before the next. `blocks.c` is built under each test's own warnings and sanitizers; the hosted link no longer takes whatever objects lie in its directory (it had linked the withdrawn `store_v3.o`). Two findings - are for ruling, section 8.5. + went to Captain Bob, and his rulings on them are section 8.5; they + made the block words the kernel's, which replaced the request first + built here, `( n waddr -- status )`. - **Not as intended yet.** - Who may have which block is not checked (8.4). - On bare metal the virtio disk is read and not written. v3's diff --git a/docs/v4.0.0/V3-PARITY.md b/docs/v4.0.0/V3-PARITY.md index a1e0aaf1..3fa230ac 100644 --- a/docs/v4.0.0/V3-PARITY.md +++ b/docs/v4.0.0/V3-PARITY.md @@ -188,9 +188,10 @@ array behind its four block registers (D-19), which skips the address space, the metadata, the owner, the ACL and the Stadium. **As of 2026-10-07** (`MESH.md` step 6): the registers and the RAM array -are gone. A node's block words make a kernel request and -`v4/system/blocks.c` serves it through v3's block subsystem, so the address -space and the metadata are v3's. Still skipped: the owner and first-touch +are gone. A node's block words are kernel requests by block number, and +`v4/system/blocks.c` serves them through v3's block subsystem into the +node's window of four slots, as v3's block words do for a VM; so the +address space and the metadata are v3's. Still skipped: the owner and first-touch claim, the ACL, and the Stadium touch, which need the node's identity. A first build of that step had block requests passed from node to node and nodes with no storage; Captain Bob withdrew it as a divergence from this diff --git a/logs/20261007-092835/amd64/qemu-amd64-20261007-092835.log b/logs/20261007-092835/amd64/qemu-amd64-20261007-092835.log new file mode 100644 index 00000000..4075237a --- /dev/null +++ b/logs/20261007-092835/amd64/qemu-amd64-20261007-092835.log @@ -0,0 +1,148 @@ +[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: amd64 +Build: Oct 7 2026 09:28:08 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 131 +Total memory: 1023 MB +Usable memory: 966 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248746 + Free pages : 247223 + Used pages : 1523 + Total MB : 971 + Free MB : 965 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2097105936 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=1008484717, tick_hz=100, initial_count=10084847 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x1d4ff6398f190c13 capsule_hash=0x1d4ff6398f190c13 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1c771623a746cc7f +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd8221bd6371d0ef3 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +PCI: ECAM mapped (amd64) +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 83774187] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/logs/20261007-093112/aarch64/qemu-aarch64-20261007-093112.log b/logs/20261007-093112/aarch64/qemu-aarch64-20261007-093112.log new file mode 100644 index 00000000..f3a16e8f --- /dev/null +++ b/logs/20261007-093112/aarch64/qemu-aarch64-20261007-093112.log @@ -0,0 +1,121 @@ +UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026) +[=3hBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found +BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found +BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0) +BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +Collecting boot information... +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: aarch64 +Build: Oct 7 2026 09:30:46 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 108 +Total memory: 4093 MB +Usable memory: 4054 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 1039359 + Free pages : 1037948 + Used pages : 1411 + Total MB : 4059 + Free MB : 4054 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +AArch64: running at EL1 +IDT installed. + +APIC: init... +PSCI: no DTB -- using HVC (QEMU virt-machine default) +GICv2: no DTB GIC node -- using QEMU virt-machine defaults +GICv2: distributor+CPU interface enabled, PPI 30 +APIC: init done + +Timer: init... +Timer: AArch64 generic timer initialised. +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 2147483608 + Free bytes: 2147483608 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 1207959552 + Heap end addr: 3355443200 + +Heartbeat: init... +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x1d4ff6398f190c13 capsule_hash=0x1d4ff6398f190c13 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1c771623a746cc7f +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd8221bd6371d0ef3 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 1376451] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/logs/20261007-093456/riscv64/qemu-riscv64-20261007-093456.log b/logs/20261007-093456/riscv64/qemu-riscv64-20261007-093456.log new file mode 100644 index 00000000..6ec2c4b2 --- /dev/null +++ b/logs/20261007-093456/riscv64/qemu-riscv64-20261007-093456.log @@ -0,0 +1,191 @@ + +OpenSBI v1.8 + ____ _____ ____ _____ + / __ \ / ____| _ \_ _| + | | | |_ __ ___ _ __ | (___ | |_) || | + | | | | '_ \ / _ \ '_ \ \___ \| _ < | | + | |__| | |_) | __/ | | |____) | |_) || |_ + \____/| .__/ \___|_| |_|_____/|____/_____| + | | + |_| + +Platform Name : riscv-virtio,qemu +Platform Features : medeleg +Platform HART Count : 1 +Platform HART Protection : pmp +Platform IPI Device : aclint-mswi +Platform Timer Device : aclint-mtimer @ 10000000Hz +Platform Console Device : uart8250 +Platform HSM Device : --- +Platform PMU Device : --- +Platform Reboot Device : syscon-reboot +Platform Shutdown Device : syscon-poweroff +Platform Suspend Device : --- +Platform CPPC Device : --- +Firmware Base : 0x80000000 +Firmware Size : 321 KB +Firmware RW Offset : 0x40000 +Firmware RW Size : 65 KB +Firmware Heap Offset : 0x47000 +Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free) +Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free) +Runtime SBI Version : 3.0 +Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr +Experimental SBI Extensions : none + +Domain0 Name : root +Domain0 Boot HART : 0 +Domain0 HARTs : 0* +Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: () +Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: () +Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W) +Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W) +Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: () +Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W) +Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W) +Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X) +Domain0 Next Address : 0x0000000020000000 +Domain0 Next Arg1 : 0x00000000bfe00000 +Domain0 Next Mode : S-mode +Domain0 SysReset : yes +Domain0 SysSuspend : yes + +Boot HART ID : 0 +Boot HART Domain : root +Boot HART Priv Version : v1.12 +Boot HART Base ISA : rv64imafdch +Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu +Boot HART PMP Count : 16 +Boot HART PMP Granularity : 2 bits +Boot HART PMP Address Bits : 54 +Boot HART MHPM Info : 16 (0x0007fff8) +Boot HART Debug Triggers : 2 triggers +Boot HART MIDELEG : 0x0000000000001666 +Boot HART MEDELEG : 0x0000000000f4b509 +[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.3 +Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850 +BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr +om PciRoot(0x0)/Pci(0x1,0x0): Not Found +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +Calling kernel_main (monolithic)... +riscv64 item 4.3.5a: satp state at kernel entry (before switch) + satp.MODE = 0x000000000000000a + satp.PPN = 0x00000000000bf868 + __kernel_start = 0x00000000bdcab9f8 +riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a) + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: riscv64 +Build: Oct 7 2026 09:34:18 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 100 +Total memory: 1020 MB +Usable memory: 975 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 250276 + Free pages : 249624 + Used pages : 652 + Total MB : 977 + Free MB : 975 + Used MB : 2 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000) +PLIC: init (S-mode context 1, threshold=0) +APIC: init done + +Timer: init... +Timer: RISC-V time CSR @ 10000000 Hz (FALLBACK, no devicetree) +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 2214588416 + Heap end addr: 2751459328 + +Heartbeat: init... +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x1d4ff6398f190c13 capsule_hash=0x1d4ff6398f190c13 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1c771623a746cc7f +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd8221bd6371d0ef3 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 23947707] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/v4/Makefile b/v4/Makefile index 2608683d..4200c747 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -51,15 +51,15 @@ node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -D # BLOCKS (docs/v4.0.0/MESH.md section 8). A node asks its kernel for a # block, and system/blocks.c serves it from the kernel's block subsystem, -# which is v3's and is built as v3 builds it. Tests named test_blocks*.c -# and test_host_*.c link them. V3_BLOCK_SRCS is all of v3 that the block +# which is v3's and is built as v3 builds it. Tests named +# test_host_*.c link them. V3_BLOCK_SRCS is all of v3 that the block # subsystem needs: its two device back ends, the log and the clock. ROOT_DIR := $(abspath $(HERE)/..) V3_BLOCK_SRCS := $(addprefix $(ROOT_DIR)/v3/src/,block_subsystem.c blkio_ram.c blkio_file.c log.c platform/platform_init.c platform/linux/time.c) V3_CFLAGS := -std=gnu99 -Wall $(OPT) -I$(ROOT_DIR)/v3/include -I$(ROOT_DIR)/kernel/include BLOCKS_SRC := $(HERE)/system/blocks.c V3_INC := -isystem $(ROOT_DIR)/v3/include -isystem $(ROOT_DIR)/kernel/include -uses_blocks = $(or $(findstring /test_blocks,$(1)),$(findstring /test_host_,$(1))) +uses_blocks = $(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 diff --git a/v4/README.md b/v4/README.md index 802b5423..c6e3de1e 100644 --- a/v4/README.md +++ b/v4/README.md @@ -133,11 +133,14 @@ The two products are not yet this: they become the five nodes at steps 8 and 9. A node she births is not POSTed: POST is the kernel's, once, on Hera. -**Blocks** (`MESH.md` step 6). A node asks its kernel for a block, as a v3 -VM does: `BLOCK` and its family keep two buffers, and the word beneath them -writes a request to port 0 with the block's number and the buffer's address -on the stack (`v4/include/v4/blocks.h`). `v4/system/blocks.c` serves it from -the kernel's block subsystem, which is v3's (`v3/src/block_subsystem.c`): +**Blocks** (`MESH.md` step 6). The block words are the kernel's, as they +are for a v3 VM: `BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS` and +`EMPTY-BUFFERS` each write one request to port 0, by block number only +(`v4/include/v4/blocks.h`). The node has a window of four slots in its +memory; the kernel copies blocks into it, keeps the record of what is in +it, and decides when a block is written (`v4/system/blocks.c`). The blocks +are the kernel's block subsystem's, which is v3's +(`v3/src/block_subsystem.c`): one chain, the same blocks at the same numbers for every node. A hosted program has the chain's fast RAM, blocks 1 to 2047. On bare metal the chain is fast RAM, the ramdrive and the virtio disk; the disk is read and not yet diff --git a/v4/capsule/blocks.v4 b/v4/capsule/blocks.v4 index 00f86390..ef99b8ef 100644 --- a/v4/capsule/blocks.v4 +++ b/v4/capsule/blocks.v4 @@ -4,103 +4,65 @@ \ EMPTY-BUFFERS LIST LOAD SCR BLK, and v3's FLUSH THRU and --> . Part of the \ compiler capsule; rests on everything before it. \ -\ STORAGE IS THE KERNEL'S (docs/v4.0.0/MESH.md section 8, ENGINE.md 3.3). A -\ node only ever asks for a block by number, and it asks its kernel: with -\ the number and the word address of 256 cells on the data stack it writes -\ the request to port 0, where its kernel is, and when the write has been -\ served the status is on the stack in their place -- 0 it worked, 2 it was -\ refused, 3 there is no such block. -\ -\ THE BUFFERS. There are two, of 256 cells each, so two blocks can be in -\ memory at once -- one can be copied to another. A buffer is given to the -\ block that asks for one, turn about; if it held a block that was UPDATEd, -\ that block is written first. BLOCK and BUFFER give a byte address, as PAD -\ and TIB are: what C@ CMOVE and TYPE take. +\ STORAGE IS THE KERNEL'S (docs/v4.0.0/MESH.md section 8, ENGINE.md 3.3), as +\ it is for a v3 VM. A node only ever asks for a block by number, and it +\ asks its kernel: it writes a request to port 0, where its kernel is, and +\ when the write has been served the answer is on the data stack. The +\ node has a window in its memory, four slots of 256 cells; the kernel +\ keeps the record of which block is in which slot, does the copying, and +\ decides when a block is written. The requests (v4/include/v4/blocks.h): +\ BLOCK# ( n -- waddr status ) the slot holding block n +\ BUFFER# ( n -- waddr status ) a slot for block n, not read +\ UPDATE# ( -- status ) the block last asked for is changed +\ SAVE-BUFFERS# ( -- status ) every changed block is written +\ EMPTY-BUFFERS# ( -- ) every slot is let go +\ The status is 0, or 2 storage refused (error 17), or 3 there is no such +\ block (error 13). BLOCK and BUFFER give a byte address, as PAD and TIB +\ are: what C@ CMOVE and TYPE take. \ \ Constants the loader supplies: -\ BLOCK-READ# BLOCK-WRITE# the numbers of the kernel's two requests -\ BUF0 BUF1 word addresses of the two buffers -\ (B) word address of six cells: (B)+0 the block in buffer 0, or 0 -\ (B)+1 whether it has been UPDATEd (B)+2 (B)+3 the same for -\ buffer 1 (B)+4 which buffer was asked for last, 0 or 2 -\ (B)+5 the block number while a buffer is found for it +\ BLOCK# BUFFER# UPDATE# SAVE-BUFFERS# EMPTY-BUFFERS# the requests' numbers \ SCR word address of the variable: the block LIST showed last \ BLK (SRC) (SRC-HOOK) see input.v4 header SCR inline : SCR' SCR ; header BLK inline : BLK' BLK ; -\ ( i -- addr ) the word address of buffer i, where i is 0 or 2 -: (BUF) if Z drop BUF1 ; Z: drop BUF0 ; - -\ ( command i -- ) read buffer i's block from storage, command 1, or -\ write it, command 2: a request of this node's kernel ( n waddr -- status ). -\ If it did not work nothing is in the buffer, and it is an error: 17 if -\ storage refused, 13 if there is no such block. -: (DEVICE) - dup push SWAP push \ i R: i command - dup (B) + a! @ SWAP (BUF) \ n waddr - pop -1 + if RD drop BLOCK-WRITE# jump ASK - RD: drop BLOCK-READ# - ASK: (PORT) b! !b \ status - pop SWAP \ i status - if FINE - push (B) + a! 0 !+ 0 ! pop - -2 + if E17 drop NODE-ERROR b! 13 !b ; +\ ( status -- ) a request that did not work is an error +: (B-ERR) + if FINE -2 + if E17 drop NODE-ERROR b! 13 !b ; E17: drop NODE-ERROR b! 17 !b ; - FINE: drop drop ; + FINE: drop ; -\ ( i -- ) if buffer i's block has been UPDATEd, write it -: (SAVE) - dup (B) + 1 + a! @ if CLEAN - drop 0 ! 2 SWAP jump (DEVICE) - CLEAN: drop drop ; - -\ ( n -- i flag ) the buffer for block n: the one that has it, flag -1; or -\ the other of the one asked for last, its old block written if need be and -\ n's number put on it, flag 0 -- what it holds is not block n yet. A block -\ number below 1 is an error (code 13). -: (FIND-BUF) - -if POS jump BAD - POS: if BAD - (B)+5 a! ! - (B) a! @ (B)+5 a! @ xor if HIT0 drop - (B)+2 a! @ (B)+5 a! @ xor if HIT2 drop - (B)+4 a! @ 2 xor dup ! \ the other one - dup (SAVE) - dup (B) + a! (B)+5 b! @b !+ 0 ! \ its block now, not updated - 0 ; - HIT0: drop 0 (B)+4 a! ! 0 -1 ; - HIT2: drop 2 (B)+4 a! ! 2 -1 ; - BAD: drop NODE-ERROR b! 13 !b 0 -1 ; +\ ( waddr status -- baddr ) the slot's byte address, or the error +: (B-GOT) + if OK SWAP drop jump (B-ERR) + OK: drop 2* 2* ; \ ( n -- baddr ) FORTH-79: the address of a buffer assigned to block n. \ What the buffer holds is not read from storage. header BUFFER -: BUFFER (FIND-BUF) drop (BUF) 2* 2* ; +: BUFFER BUFFER# (PORT) b! !b jump (B-GOT) \ ( n -- baddr ) FORTH-79: the address of a buffer that holds block n, \ read from storage if it is not already in one. header BLOCK -: BLOCK - (FIND-BUF) if READ drop (BUF) 2* 2* ; - READ: drop drop \ which buffer is in (B)+4: nothing waits on the stack - 1 (B)+4 a! @ (DEVICE) (B)+4 a! @ (BUF) 2* 2* ; +: BLOCK BLOCK# (PORT) b! !b jump (B-GOT) \ FORTH-79: mark the block most recently asked for as changed, so that it is \ written before its buffer is used for another. header UPDATE -: UPDATE (B)+4 a! @ (B) + a! @+ if NONE drop -1 ! ; NONE: drop ; +: UPDATE UPDATE# (PORT) b! !b jump (B-ERR) \ FORTH-79: write every block that has been UPDATEd. header SAVE-BUFFERS -: SAVE-BUFFERS 0 (SAVE) 2 jump (SAVE) +: SAVE-BUFFERS SAVE-BUFFERS# (PORT) b! !b jump (B-ERR) \ As v3: the same. header FLUSH : FLUSH jump SAVE-BUFFERS \ FORTH-79: forget what is in the buffers; nothing is written. header EMPTY-BUFFERS -: EMPTY-BUFFERS (B) a! 0 !+ 0 !+ 0 !+ 0 ! ; +: EMPTY-BUFFERS EMPTY-BUFFERS# (PORT) b! !b ; \ ---- LIST ---------------------------------------------------------------------- \ ( n -- ) as v3: a new line, "Block n", then sixteen lines of 64 diff --git a/v4/include/v4/blocks.h b/v4/include/v4/blocks.h index d6465d99..499fa6c9 100644 --- a/v4/include/v4/blocks.h +++ b/v4/include/v4/blocks.h @@ -4,42 +4,81 @@ * * A node only ever asks for a block by number, and it asks the kernel: a * request written to the port where its kernel is, as any kernel word is. - * There are two, the same for every node, + * This is v3's way (v3/src/word_source/block_words.c). The node has a + * WINDOW in its memory, V4_BLOCK_SLOTS slots of 256 cells, as a v3 VM has + * four slots of 1 KiB at the top of its memory. The kernel keeps the + * record of which block is in which slot and does all the copying, and it + * writes into the window and nowhere else in the node. The requests, the + * same for every node: * - * V4_REQ_BLOCK_READ ( n waddr -- status ) block n into the 256 cells at waddr - * V4_REQ_BLOCK_WRITE ( n waddr -- status ) block n from them + * V4_REQ_BLOCK ( n -- waddr status ) the slot that holds block n, read + * from storage if it is not in one + * V4_REQ_BUFFER ( n -- waddr status ) a slot for block n, of zeros if it + * is not in one: nothing is read + * V4_REQ_UPDATE ( -- status ) the block most recently asked for + * is marked changed + * V4_REQ_SAVE_BUFFERS ( -- status ) every block marked is written + * V4_REQ_EMPTY_BUFFERS ( -- ) every slot is let go; nothing is + * written * - * 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 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. + * Their numbers are below zero: the requests a host names for its own + * words (KERNEL-WORD) count up from 1. + * + * waddr is the word address of the slot, or 0 when the status is not 0. + * The status is 0; or V4_BLOCK_RANGE, there is no such block; or + * V4_BLOCK_REFUSED: storage would not have a block that had to be written, + * the stack did not hold the request's argument, or the window is not all + * in the node's memory. + * + * WHEN A BLOCK IS WRITTEN is FORTH-79's: UPDATE marks it, and it is written + * by SAVE-BUFFERS or when its slot is wanted for another block. (v3's + * UPDATE copies the slot to the kernel at once; the standard is followed.) + * A slot whose block is not marked is taken before one whose block is. A + * block that storage would not take is let go of: its slot is empty. + * + * When the kernel's chain of devices changes, everything in the slots is + * let go and nothing of it is written, as v3 does (blk_vm_check_epoch). * * 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 - * zero, and a write takes the low 32 bits. + * lowest, whatever the cell width: a block read leaves the rest of each + * cell zero, and a write takes the low 32 bits. * - * The blocks are the kernel's block subsystem's (v3/src/block_subsystem.c): - * the device chain, the metadata and the map are its, as they were. Who - * may have which block is not checked here yet (MESH.md 8.4). + * The blocks are the kernel's block subsystem's (v3/src/block_subsystem.c). + * Who may have which block is not checked here yet (MESH.md 8.4). */ #ifndef V4_BLOCKS_H #define V4_BLOCKS_H #include "v4/node.h" -#define V4_REQ_BLOCK_READ (-1) -#define V4_REQ_BLOCK_WRITE (-2) +#define V4_REQ_BLOCK (-1) +#define V4_REQ_BUFFER (-2) +#define V4_REQ_UPDATE (-3) +#define V4_REQ_SAVE_BUFFERS (-4) +#define V4_REQ_EMPTY_BUFFERS (-5) #define V4_BLOCK_OK 0 #define V4_BLOCK_REFUSED 2 #define V4_BLOCK_RANGE 3 -/* If `request` is one of the two, serve it for the node, whose data stack - * holds its arguments and is left holding its status, and return 1. +#define V4_BLOCK_SLOTS 4u + +/* What the kernel keeps for one node. */ +typedef struct { + v4_cell window; /* word address of the first slot */ + uint32_t block[V4_BLOCK_SLOTS]; /* the block in each slot; 0: none */ + unsigned char changed[V4_BLOCK_SLOTS]; /* UPDATEd and not yet written */ + unsigned next; /* where the search for a slot to take begins */ + int last; /* the slot most recently asked for; -1: none */ + uint64_t epoch; /* the chain as it was when the slots were filled */ +} v4_blocks; + +/* A node's window is at `window`, and nothing is in it. */ +void v4_blocks_init(v4_blocks *w, v4_cell window); + +/* If `request` is one of the five, serve it for the node and return 1. * Otherwise touch nothing and return 0. The node is stopped, blocked at * its port, when this is called. */ -int v4_blocks_serve(v4_node *n, v4_cell request); +int v4_blocks_serve(v4_blocks *w, v4_node *n, v4_cell request); #endif /* V4_BLOCKS_H */ diff --git a/v4/include/v4/image.h b/v4/include/v4/image.h index 82273eee..9356c40f 100644 --- a/v4/include/v4/image.h +++ b/v4/include/v4/image.h @@ -54,6 +54,10 @@ typedef struct { * that loads capsules when it boots sets them again when it has * (v4_image_seal). */ v4_cell boot_cells, fence; + + /* the block window: the word address of the slots the kernel copies + * blocks into (v4/blocks.h) */ + v4_cell block_window; } v4_image; /* The description of the nucleus built from v4/capsule (the generated file diff --git a/v4/system/blocks.c b/v4/system/blocks.c index 0971aab8..dcb74661 100644 --- a/v4/system/blocks.c +++ b/v4/system/blocks.c @@ -6,48 +6,141 @@ #include "v4/blocks.h" #include "block_subsystem.h" -int v4_blocks_serve(v4_node *n, v4_cell request) +static void let_go(v4_blocks *w) { - v4_cell addr, num; - uint32_t block; - uint8_t *b; unsigned i; + for (i = 0; i < V4_BLOCK_SLOTS; i++) { w->block[i] = 0; w->changed[i] = 0; } + w->next = 0; + w->last = -1; +} + +void v4_blocks_init(v4_blocks *w, v4_cell window) +{ + w->window = window; + w->epoch = blk_subsys_epoch(); + let_go(w); +} + +static v4_cell slot_addr(const v4_blocks *w, unsigned s) +{ + return w->window + (v4_cell)(s * V4_BLOCK_CELLS); +} + +/* The block in slot s is written. Whether or not storage takes it, it is + * no longer marked. */ +static int write_slot(v4_blocks *w, v4_node *n, unsigned s) +{ + uint8_t *b = blk_get_buffer(w->block[s], 1); + v4_cell at = slot_addr(w, s); + unsigned i; + + w->changed[s] = 0; + if (!b) return V4_BLOCK_REFUSED; + for (i = 0; i < V4_BLOCK_CELLS; i++) { + v4_ucell c = (v4_ucell)n->mem[at + (v4_cell)i]; + b[4u * i] = (uint8_t)(c & 0xFFu); + b[4u * i + 1u] = (uint8_t)((c >> 8) & 0xFFu); + b[4u * i + 2u] = (uint8_t)((c >> 16) & 0xFFu); + b[4u * i + 3u] = (uint8_t)((c >> 24) & 0xFFu); + } + return (blk_update(w->block[s]) == BLK_OK && blk_flush(w->block[s]) == BLK_OK) ? V4_BLOCK_OK : V4_BLOCK_REFUSED; +} + +/* A slot to put a block in: an empty one, or one whose block is not marked, + * or, with every one marked, the next in turn, its block written first. + * *status is not V4_BLOCK_OK if that write was refused; the slot is empty + * all the same. */ +static unsigned take_slot(v4_blocks *w, v4_node *n, int *status) +{ + unsigned i, s; + + *status = V4_BLOCK_OK; + for (i = 0; i < V4_BLOCK_SLOTS; i++) if (w->block[i] == 0) return i; + for (i = 0; i < V4_BLOCK_SLOTS; i++) { + s = (w->next + i) % V4_BLOCK_SLOTS; + if (!w->changed[s]) break; + } + if (i == V4_BLOCK_SLOTS) { + s = w->next; + *status = write_slot(w, n, s); + } + w->next = (s + 1u) % V4_BLOCK_SLOTS; + w->block[s] = 0; + w->changed[s] = 0; + if (w->last == (int)s) w->last = -1; + return s; +} + +/* BLOCK and BUFFER: ( n -- waddr status ) */ +static void slot_for(v4_blocks *w, v4_node *n, int read) +{ + v4_cell num, at; + uint32_t block; + unsigned i, s; 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); + if (n->ds.depth < 1u) { v4_dstack_reset(&n->ds); v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, (v4_cell)V4_BLOCK_REFUSED); return; } + num = v4_dstack_pop(&n->ds); + block = (uint32_t)num; + if (num < 1 || (uint64_t)num > 0xFFFFFFFFull || !blk_is_valid(block)) { + v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, (v4_cell)V4_BLOCK_RANGE); + return; + } + for (s = 0; s < V4_BLOCK_SLOTS && w->block[s] != block; s++) { } + if (s == V4_BLOCK_SLOTS) { + const uint8_t *b = 0; + s = take_slot(w, n, &status); + if (status == V4_BLOCK_OK && read) { + b = blk_get_buffer(block, 0); + if (!b) status = V4_BLOCK_REFUSED; + } + if (status != V4_BLOCK_OK) { v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, (v4_cell)status); return; } + at = slot_addr(w, s); + for (i = 0; i < V4_BLOCK_CELLS; i++) + n->mem[at + (v4_cell)i] = b ? (v4_cell)((v4_ucell)b[4u * i] | ((v4_ucell)b[4u * i + 1u] << 8) + | ((v4_ucell)b[4u * i + 2u] << 16) | ((v4_ucell)b[4u * i + 3u] << 24)) + : 0; + w->block[s] = block; + w->changed[s] = 0; + } + w->last = (int)s; + v4_dstack_push(&n->ds, slot_addr(w, s)); + v4_dstack_push(&n->ds, (v4_cell)V4_BLOCK_OK); +} + +int v4_blocks_serve(v4_blocks *w, v4_node *n, v4_cell request) +{ + unsigned s; + int status = V4_BLOCK_OK; + + if (request > V4_REQ_BLOCK || request < V4_REQ_EMPTY_BUFFERS) return 0; + + /* the chain is not what it was: what the slots hold is of no chain now */ + if (blk_subsys_epoch() != w->epoch) { let_go(w); w->epoch = blk_subsys_epoch(); } + + if (request == V4_REQ_EMPTY_BUFFERS) { let_go(w); return 1; } + + /* the window is all in the node's memory, or nothing is done */ + if (!v4_node_addr_ok(w->window) || !v4_node_addr_ok(w->window + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS - 1u))) { + if (request == V4_REQ_BLOCK || request == V4_REQ_BUFFER) { + if (n->ds.depth >= 1u) (void)v4_dstack_pop(&n->ds); + v4_dstack_push(&n->ds, 0); + } 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; - if (!v4_node_addr_ok(addr) || !v4_node_addr_ok(addr + (v4_cell)(V4_BLOCK_CELLS - 1u))) { - status = V4_BLOCK_REFUSED; - } else if (num < 1 || (uint64_t)num > 0xFFFFFFFFull || !blk_is_valid(block)) { - status = V4_BLOCK_RANGE; - } else if (request == V4_REQ_BLOCK_READ) { - b = blk_get_buffer(block, 0); - status = b ? V4_BLOCK_OK : V4_BLOCK_REFUSED; - if (b) - for (i = 0; i < V4_BLOCK_CELLS; i++) - n->mem[addr + (v4_cell)i] = (v4_cell)((v4_ucell)b[4u * i] | ((v4_ucell)b[4u * i + 1u] << 8) - | ((v4_ucell)b[4u * i + 2u] << 16) | ((v4_ucell)b[4u * i + 3u] << 24)); - } else { - b = blk_get_buffer(block, 1); - status = V4_BLOCK_REFUSED; - if (b) { - for (i = 0; i < V4_BLOCK_CELLS; i++) { - v4_ucell w = (v4_ucell)n->mem[addr + (v4_cell)i]; - b[4u * i] = (uint8_t)(w & 0xFFu); - b[4u * i + 1u] = (uint8_t)((w >> 8) & 0xFFu); - b[4u * i + 2u] = (uint8_t)((w >> 16) & 0xFFu); - b[4u * i + 3u] = (uint8_t)((w >> 24) & 0xFFu); + if (request == V4_REQ_BLOCK) { slot_for(w, n, 1); return 1; } + if (request == V4_REQ_BUFFER) { slot_for(w, n, 0); return 1; } + if (request == V4_REQ_UPDATE) { + if (w->last >= 0 && w->block[w->last] != 0) w->changed[w->last] = 1; + } else { /* SAVE-BUFFERS */ + for (s = 0; s < V4_BLOCK_SLOTS; s++) + if (w->block[s] != 0 && w->changed[s] && write_slot(w, n, s) != V4_BLOCK_OK) { + status = V4_BLOCK_REFUSED; + w->block[s] = 0; + if (w->last == (int)s) w->last = -1; } - if (blk_update(block) == BLK_OK && blk_flush(block) == BLK_OK) status = V4_BLOCK_OK; - } } v4_dstack_push(&n->ds, (v4_cell)status); return 1; diff --git a/v4/system/boot.c b/v4/system/boot.c index da2d2cc1..f69558b3 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -117,6 +117,9 @@ static void post_watch(char c) #define CONSOLE_PORT 1u #define CONSOLE_ID 1 /* the console's number as a sender; the node's is 0 until it is given one */ +/* What the kernel keeps of the node's block window (blocks.h). */ +static v4_blocks boot_blocks; + int v4_boot_line(const v4_boot *b, const char *text, unsigned len) { static v4_message out, in; /* one line at a time: the boot is not re-entered */ @@ -131,7 +134,7 @@ int v4_boot_line(const v4_boot *b, const char *text, unsigned len) if (n->stopped) return V4_BOOT_LINE_STOPPED; if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */ - if (v4_blocks_serve(n, n->request)) { /* a block, as for any node */ + if (v4_blocks_serve(&boot_blocks, n, n->request)) { /* a block, as for any node */ v4_node_port_served(n); } else if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) { b->serve(n, (unsigned)n->request); @@ -352,6 +355,7 @@ int v4_boot_run(const v4_boot *b) * when it is written: the node need never wait before writing to them * (node.h, v4_node_port_status) */ v4_node_port_status(b->n, 0, 1u << KERNEL_PORT | 1u << CONSOLE_PORT); + v4_blocks_init(&boot_blocks, b->im->block_window); if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } /* the kernel's words, before any capsule that might use one */ diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index f51542e1..16b0ed5e 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -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 */ diff --git a/v4/tests/test_blocks.c b/v4/tests/test_blocks.c deleted file mode 100644 index d014a229..00000000 --- a/v4/tests/test_blocks.c +++ /dev/null @@ -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 -#include -#include -#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; -} diff --git a/v4/tests/test_host_blocks.c b/v4/tests/test_host_blocks.c new file mode 100644 index 00000000..df17dcf1 --- /dev/null +++ b/v4/tests/test_host_blocks.c @@ -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 +#include +#include +#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; +} diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 9d72565a..cb090ecd 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -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"); } diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c index 91c71791..c2bcfd6d 100644 --- a/v4/tests/test_host_unit.c +++ b/v4/tests/test_host_unit.c @@ -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; } diff --git a/v4/tools/mkimage.c b/v4/tools/mkimage.c index 68931382..f4d17725 100644 --- a/v4/tools/mkimage.c +++ b/v4/tools/mkimage.c @@ -88,7 +88,7 @@ int main(int argc, char **argv) n.mem[NODE_ERROR] = 0; n.mem[BLK] = 0; n.mem[SCR] = 0; n.mem[SRC] = TIB; n.mem[SRC_HOOK] = 0; for (k = BVARS; k < BVARS + 6; k++) n.mem[k] = 0; - for (k = BUF0_W; k < BUF0_W + 2 * 256; k++) n.mem[k] = 0; + for (k = BUF0_W; k < BUF0_W + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS); k++) n.mem[k] = 0; for (k = TIB_W; k < TIB_W + 260; k++) n.mem[k] = 0; for (k = WBUF_W; k < WBUF_W + WBUF_CELLS; k++) n.mem[k] = 0; for (k = PAD_W; k < PAD_W + 21; k++) n.mem[k] = 0; @@ -113,9 +113,9 @@ int main(int argc, char **argv) fprintf(out, "const v4_image v4_capsule_image = {\n %uu, %uu, %uu, %uu,\n", (unsigned)V4_CELL_BITS, (unsigned)V4_NODE_WORDS, (unsigned)V4_DATA_RING, (unsigned)V4_RET_RING); fprintf(out, " %ld,\n %ld, %ld,\n %ld, %ld,\n", (long)idle, (long)faults, (long)PORT, (long)w_key, (long)w_key_end); - fprintf(out, " %ld, %ld, %ld,\n %ld, %ld,\n %ld,\n %ld, %ld,\n %ld, %ld\n};\n", (long)CONSOLE_TX, (long)CONSOLE_RX, (long)CONSOLE_ST, + fprintf(out, " %ld, %ld, %ld,\n %ld, %ld,\n %ld,\n %ld, %ld,\n %ld, %ld,\n %ld\n};\n", (long)CONSOLE_TX, (long)CONSOLE_RX, (long)CONSOLE_ST, (long)DSTACK_REG, (long)RSTACK_REG, (long)NODE_ERROR, (long)DP, (long)LATEST, - (long)BOOT_CELLS, (long)FENCE); + (long)BOOT_CELLS, (long)FENCE, (long)BUF0_W); if (fclose(out) != 0) die("cannot write", argv[1]); fprintf(stderr, "mkimage: a capsule of %u words, dictionary to word %ld, %d-bit cells -> %s, %s\n", count, (long)((n.mem[DP] + 3) / 4), V4_CELL_BITS, argv[2], argv[1]); return 0;