feat(v4.0.0): a node asks its kernel by a blocking write to its port
ENGINE.md step 2, the carrier. Ruled 2026-10-05 (V3-PARITY.md 1i), on DECOMPOSITION.md section 6: a write to a port blocks until the neighbour reads. - node: v4_node_port_attach, v4_node_port_served; a store to the port keeps the value as the request and blocks the node - exec: a blocked node executes nothing; served, it goes on from the opcode after the store, in the same instruction word; a fault meanwhile abandons the rest of the word - compile.v4: n KERNEL-WORD name makes a word whose body writes n to the port; its arguments and results are on the data stack - boot: the kernel's words are made by handing the node text, and requests are served between the node's opcodes; one no one serves is error 12 - BYE, the first kernel word: hosted it leaves the program, as hosted v3; on the lone node it is v3's cold restart - ENGINE.md 3a: multiuser, multitasking, preemptive and cooperative, and what that asks of the engine Verified: make -C v4 test passes at both widths, with tests/test_port.c; hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST with the same hashes as hosted, and a kernel word no one serves and BYE typed at each prompt are answered (logs/20261005-185506, -185734, -190101; -185234 is an amd64 run in which those two lines were not typed). Not done: v3's own C functions serving a node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5f1f60fc84
commit
8df6c16766
+18
-8
@@ -59,19 +59,20 @@ State, 2026-10-05, after `ENGINE.md` step 1: all six builds start the nucleus,
|
||||
load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines:
|
||||
|
||||
```
|
||||
PARITY:V4_NUCLEUS words=295 image_hash=0x3b5a9a7a2f7c5361
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=0xc45c4db43ea0b1f1
|
||||
PARITY:V4_NUCLEUS words=296 image_hash=0x736921433a2e0e97
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=0x01a3a48159f28a3e
|
||||
PARITY:V4_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=0x88df0873060da73e
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=0x212b7b70848080e7
|
||||
PARITY:OK
|
||||
POST: PASSED
|
||||
ok>
|
||||
```
|
||||
|
||||
Bare-metal logs: `logs/20261005-180922/amd64/`, `logs/20261005-181152/aarch64/`,
|
||||
`logs/20261005-181541/riscv64/`. On each, three lines were then typed at the
|
||||
Bare-metal logs: `logs/20261005-185506/amd64/`, `logs/20261005-185734/aarch64/`,
|
||||
`logs/20261005-190101/riscv64/`. On each, six lines were then typed at the
|
||||
prompt through the serial port — a definition, its use with the capsule's
|
||||
`U*`, and an unknown word — and each answered as the hosted binary does. The
|
||||
`U*`, an unknown word, a kernel word no one serves, and `BYE` — and each
|
||||
was answered as the hosted binary answers it. The
|
||||
capsules were unsigned (no signing key on this machine).
|
||||
|
||||
**A node is handed a line** (`docs/v4.0.0/ENGINE.md` 3.1). It does not read
|
||||
@@ -84,10 +85,19 @@ may be 1024 characters, a block. `v4_line_begin`, `v4_line_done` and
|
||||
`v4_boot_line` (`v4/system/boot.c`) is the one loop the hosted binary, the
|
||||
kernel and the capsule loader all run it with.
|
||||
|
||||
**A node asks its kernel by writing to its port** (`ENGINE.md` 3.3). The
|
||||
write blocks the node until it has been served. A kernel word is a
|
||||
dictionary entry made by `n KERNEL-WORD name`, whose body writes `n` to the
|
||||
port; its arguments and results are on the data stack. `BYE` is the one
|
||||
kernel word so far. A node can be stopped between any two instruction
|
||||
words and is blocked while it waits at its port, which is what a system of
|
||||
many users and tasks, preemptive and cooperative, needs of it
|
||||
(`ENGINE.md` 3a).
|
||||
|
||||
**This is still the lone node.** On bare metal `kernel_main.c` starts it
|
||||
before the fleet tables, beside the kernel's own system and not in the VM's
|
||||
place. `ENGINE.md` sets out the steps from here; step 1 is the only one
|
||||
done. `V3-PARITY.md` records the rulings it is built from.
|
||||
place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is
|
||||
part done. `V3-PARITY.md` records the rulings it is built from.
|
||||
|
||||
**What this does not yet show.** `forth79.4th` holds two definitions, `U*`
|
||||
and `U/MOD`. Every other word is still in the assembled nucleus, so POST is
|
||||
|
||||
@@ -246,6 +246,20 @@ header ; immediate compile-only
|
||||
LATEST (FLAGS) a! @ -3 and !
|
||||
jump LEFT-BRACKET
|
||||
|
||||
\ ( n -- ) KERNEL-WORD xxx A WORD THAT ASKS THE KERNEL (docs/v4.0.0/
|
||||
\ ENGINE.md 3.3). xxx is made a word whose whole body is "write n to the
|
||||
\ port": n is the number of a request, the port is the node's way to its
|
||||
\ kernel, and the write blocks the node until the kernel has served it
|
||||
\ (DECOMPOSITION.md section 6). xxx's arguments are whatever it finds on
|
||||
\ the data stack and its results are what the kernel leaves there. This is
|
||||
\ what a word that is a C function in v3 -- BIRTH, BLOCK, BYE -- is on a
|
||||
\ node. Not FORTH-79 and not in v3, where such a word is registered from C.
|
||||
\ The constant (PORT) is the port's word address.
|
||||
header KERNEL-WORD
|
||||
: KERNEL-WORD
|
||||
COLON (LIT,) (PORT) (LIT,) 30 (OP,) 14 (OP,) \ n port b! !b
|
||||
jump SEMICOLON
|
||||
|
||||
\ FORTH-79: return from the definition at this point
|
||||
header EXIT immediate compile-only
|
||||
: EXIT 0 jump (OP,)
|
||||
|
||||
@@ -41,6 +41,18 @@ typedef struct {
|
||||
const v4_image *im; /* the nucleus */
|
||||
void (*out)(const char *text, unsigned len); /* the console */
|
||||
int (*key)(void); /* a character for a line that reads the keyboard, or 0: there is none */
|
||||
|
||||
/* THE KERNEL'S WORDS (docs/v4.0.0/ENGINE.md 3.3). `words` names them,
|
||||
* `word_count` of them; the boot makes each a word on the node (KERNEL-
|
||||
* WORD, v4/capsule/compile.v4) whose request number is its place in the
|
||||
* list, counting from 1. When the node writes a request to its port it
|
||||
* is blocked, and `serve` is called with the number: it takes the
|
||||
* word's arguments from the node's data stack and leaves its results
|
||||
* there, and when it returns the node goes on. A request that is not
|
||||
* in the list is an error on the node: 12, Argument out of range. */
|
||||
const char *const *words;
|
||||
unsigned word_count;
|
||||
void (*serve)(v4_node *n, unsigned request);
|
||||
} v4_boot;
|
||||
|
||||
/* What `key` returns when it has no character. */
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
|
||||
typedef struct {
|
||||
v4_uheat_t anticlock;
|
||||
|
||||
/* A node blocked at its port in the middle of an instruction word
|
||||
* (node.h) goes on, when it has been served, from the slot after the
|
||||
* store: the word and the slot are kept here meanwhile. */
|
||||
int resume;
|
||||
v4_iword resume_iw;
|
||||
unsigned resume_slot;
|
||||
unsigned resume_faults; /* n->faults when it blocked: a fault since then means P is a handler and the rest of the word is abandoned */
|
||||
} v4_exec_state;
|
||||
|
||||
void v4_exec_reset(v4_exec_state *es);
|
||||
|
||||
@@ -40,6 +40,7 @@ typedef struct {
|
||||
unsigned tib_bytes; /* its size: a line may be one less */
|
||||
v4_cell span; /* word address of the variable SPAN */
|
||||
v4_cell fault_table; /* (FAULTS): where a fault goes */
|
||||
v4_cell port; /* word address of the node's port (node.h) */
|
||||
|
||||
/* KEY's code: a node whose P is in key_start .. key_end - 1 with no
|
||||
* character pending is waiting for one */
|
||||
|
||||
@@ -88,6 +88,13 @@ typedef struct {
|
||||
unsigned faults; /* how many there have been */
|
||||
int stopped; /* non-zero: faulted with no handler */
|
||||
|
||||
/* The port (DECOMPOSITION.md section 6): a write to it blocks the node
|
||||
* until its neighbour has taken what was written. See
|
||||
* v4_node_port_attach below. */
|
||||
v4_cell port; /* its word address, or -1: no port */
|
||||
int asking; /* non-zero: blocked, having written `request` */
|
||||
v4_cell request; /* what it wrote */
|
||||
|
||||
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
|
||||
v4_cell dstack_reg; /* its word address, or -1 */
|
||||
v4_cell rstack_reg; /* its word address, or -1 */
|
||||
@@ -149,6 +156,28 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
|
||||
* Passing -1 detaches. The address is the caller's choice. */
|
||||
void v4_node_console_attach(v4_node *n, v4_cell addr);
|
||||
|
||||
/* THE PORT. A node reaches its neighbour through a port, and a port is an
|
||||
* address (DECOMPOSITION.md section 6): "a write blocks until the neighbour
|
||||
* reads. This is the GA144 model. No instruction is added."
|
||||
*
|
||||
* After v4_node_port_attach(n, addr), a store to word address `addr` does
|
||||
* not write memory: the node keeps the value in n->request, sets n->asking
|
||||
* and is blocked. A blocked node executes nothing -- v4_exec_step_word
|
||||
* returns 0 and changes nothing -- until its neighbour calls
|
||||
* v4_node_port_served(n). It then goes on from the opcode after the store,
|
||||
* in the same instruction word if there are any left.
|
||||
*
|
||||
* This is how a node asks its kernel for something (docs/v4.0.0/ENGINE.md
|
||||
* 3.3): the neighbour on the port is the kernel, what is written is the
|
||||
* number of the request, and the arguments and results are on the node's
|
||||
* data stack, which the kernel may use while the node is blocked. Only the
|
||||
* write is here. Nothing reads from a port yet, and a fetch from the
|
||||
* address is a fetch from memory.
|
||||
*
|
||||
* A node has no port until one is attached: v4_node_reset detaches it. */
|
||||
void v4_node_port_attach(v4_node *n, v4_cell addr);
|
||||
void v4_node_port_served(v4_node *n);
|
||||
|
||||
/* CONSOLE-RX and CONSOLE-STATUS, the console's receive side, on the
|
||||
* single-node model.
|
||||
*
|
||||
|
||||
+30
-5
@@ -87,6 +87,10 @@ static int stack_faulted(v4_node *n, unsigned op)
|
||||
void v4_exec_reset(v4_exec_state *es)
|
||||
{
|
||||
es->anticlock = 0;
|
||||
es->resume = 0;
|
||||
es->resume_iw = 0;
|
||||
es->resume_slot = 0;
|
||||
es->resume_faults = 0;
|
||||
}
|
||||
|
||||
void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
|
||||
@@ -244,11 +248,21 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
|
||||
unsigned executed = 0;
|
||||
unsigned slot = 0;
|
||||
|
||||
/* D-14: a node that faulted with no handler has stopped; and P itself
|
||||
* may be what is outside memory. */
|
||||
if (n->stopped || faulted(n, n->p)) return 0;
|
||||
iw = fetch_iword(n, n->p);
|
||||
n->p = add_wrap(n->p, 1);
|
||||
/* D-14: a node that faulted with no handler has stopped. A node that
|
||||
* has written to its port is blocked until it has been served. */
|
||||
if (n->stopped || n->asking) return 0;
|
||||
if (es->resume && es->resume_faults != n->faults) es->resume = 0; /* it faulted while blocked: P is the handler */
|
||||
if (es->resume) {
|
||||
/* served: the rest of the word the store was in */
|
||||
es->resume = 0;
|
||||
iw = es->resume_iw;
|
||||
slot = es->resume_slot;
|
||||
} else {
|
||||
/* P itself may be what is outside memory */
|
||||
if (faulted(n, n->p)) return 0;
|
||||
iw = fetch_iword(n, n->p);
|
||||
n->p = add_wrap(n->p, 1);
|
||||
}
|
||||
|
||||
while (slot < V4_SLOT_COUNT) {
|
||||
unsigned op = v4_iword_op(iw, slot);
|
||||
@@ -264,6 +278,17 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
|
||||
}
|
||||
executed++;
|
||||
|
||||
if (n->asking) {
|
||||
/* The store was to the port. The node is blocked here; what is
|
||||
* left of the word waits until it has been served. */
|
||||
if (slot + 1 < V4_SLOT_COUNT) {
|
||||
es->resume = 1;
|
||||
es->resume_iw = iw;
|
||||
es->resume_slot = slot + 1;
|
||||
es->resume_faults = n->faults;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (again) {
|
||||
/* Same word, slot 0, no refetch and P untouched: an `@p` or `!p`
|
||||
* inside a unext loop therefore walks the words that follow, as
|
||||
|
||||
@@ -24,6 +24,7 @@ int v4_image_boot(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
|
||||
v4_node_stack_regs_attach(n, im->dstack_reg, im->rstack_reg);
|
||||
v4_node_error_attach(n, im->node_error);
|
||||
v4_node_fault_attach(n, im->fault_table);
|
||||
v4_node_port_attach(n, im->port);
|
||||
if (disk && blocks) v4_node_storage_attach(n, im->storage_reg, disk, blocks);
|
||||
|
||||
n->mem[im->line_status] = V4_LINE_COMPLETED;
|
||||
|
||||
@@ -17,6 +17,7 @@ void v4_node_reset(v4_node *n)
|
||||
v4_node_fault_attach(n, -1);
|
||||
v4_node_stack_regs_attach(n, -1, -1);
|
||||
v4_node_error_attach(n, -1);
|
||||
v4_node_port_attach(n, -1);
|
||||
v4_node_storage_attach(n, -1, 0, 0);
|
||||
}
|
||||
|
||||
@@ -119,6 +120,9 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
|
||||
n->console_dropped++;
|
||||
return;
|
||||
}
|
||||
/* The port: the node keeps what it wrote and is blocked until its
|
||||
* neighbour has taken it. -1 when no port is attached. */
|
||||
if (addr == n->port) { n->request = value; n->asking = 1; return; }
|
||||
/* The stack registers: a store empties the stack. -1 when not attached. */
|
||||
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; }
|
||||
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
|
||||
@@ -177,3 +181,15 @@ void v4_node_console_attach(v4_node *n, v4_cell addr)
|
||||
n->console_len = 0;
|
||||
n->console_dropped = 0;
|
||||
}
|
||||
|
||||
void v4_node_port_attach(v4_node *n, v4_cell addr)
|
||||
{
|
||||
n->port = addr;
|
||||
n->asking = 0;
|
||||
n->request = 0;
|
||||
}
|
||||
|
||||
void v4_node_port_served(v4_node *n)
|
||||
{
|
||||
n->asking = 0;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,18 @@ int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
|
||||
n->console_len = 0;
|
||||
}
|
||||
if (n->stopped) return V4_BOOT_LINE_STOPPED;
|
||||
if (n->asking) { /* blocked at its port: the kernel's turn */
|
||||
if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) {
|
||||
b->serve(n, (unsigned)n->request);
|
||||
v4_node_port_served(n);
|
||||
} else {
|
||||
/* a request no one serves is an error, as any other (D-18):
|
||||
* 12, Argument out of range */
|
||||
v4_node_port_served(n);
|
||||
v4_node_store(n, n->error_reg, 12); /* a store to NODE-ERROR raises it */
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (v4_line_done(n, b->im)) return v4_line_status(n, b->im);
|
||||
if (v4_image_waiting(n, b->im)) { /* the line is reading the keyboard */
|
||||
int c = b->key ? b->key() : V4_BOOT_KEY_END;
|
||||
@@ -261,6 +273,20 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
|
||||
say(b, " image_hash="); say_hex(b, image_hash(b->im));
|
||||
say(b, "\n");
|
||||
|
||||
/* the kernel's words, before any capsule that might use one */
|
||||
for (i = 0; i < b->word_count; i++) {
|
||||
char text[64];
|
||||
unsigned at = 0, k, num = i + 1, div = 1;
|
||||
while (num / div >= 10) div *= 10;
|
||||
for (; div; div /= 10) text[at++] = (char)('0' + num / div % 10);
|
||||
for (k = 0; " KERNEL-WORD "[k]; k++) text[at++] = " KERNEL-WORD "[k];
|
||||
for (k = 0; b->words[i][k] && at < sizeof text; k++) text[at++] = b->words[i][k];
|
||||
if (b->words[i][k] || v4_boot_line(b, text, at) != V4_LINE_COMPLETED) {
|
||||
say(b, "V4: kernel word "); say(b, b->words[i]); say(b, " could not be made\nPARITY:FAIL\nPOST: FAILED\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
|
||||
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
||||
#if V4_POST_AT_BOOT
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
#define EMIT_HOOK (BUF0_W - 7) /* the xt of a word EMIT gives its character to, or 0: the console */
|
||||
#define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */
|
||||
#define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */
|
||||
#define PORT (BUF0_W - 10) /* the node's port: a write to it blocks until its neighbour, the kernel, has served it */
|
||||
#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 */
|
||||
@@ -144,6 +145,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
|
||||
v4_text_constant(tx, "(EMIT-HOOK)", EMIT_HOOK);
|
||||
v4_text_constant(tx, "(CATCH)", CATCH);
|
||||
v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS);
|
||||
v4_text_constant(tx, "(PORT)", PORT);
|
||||
v4_text_constant(tx, "BLOCK-NUMBER", STORAGE_REG);
|
||||
v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1);
|
||||
v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/* test_port.c -- the node's port: a write blocks the node until its
|
||||
* neighbour has served it (node.h; DECOMPOSITION.md section 6).
|
||||
*
|
||||
* At the level of opcodes: no word of the vocabulary is defined here.
|
||||
*/
|
||||
#include "v4/asm.h"
|
||||
#include "v4/testcode.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int failures = 0, checks = 0;
|
||||
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
|
||||
|
||||
/* The memory map is open (D-4); the test chooses the address. */
|
||||
#define PORT ((v4_cell)(V4_NODE_WORDS - 9u))
|
||||
#define MARK ((v4_cell)0x1234)
|
||||
#define HOME ((v4_cell)8) /* where a word returns to when the test called it */
|
||||
|
||||
static v4_node n;
|
||||
static v4_exec_state es;
|
||||
static v4_heat h;
|
||||
static v4_asm as;
|
||||
|
||||
#define O(name) v4_asm_op(&as, V4_OP_##name)
|
||||
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
|
||||
|
||||
static void start(v4_cell word)
|
||||
{
|
||||
v4_dstack_reset(&n.ds);
|
||||
v4_rstack_reset(&n.rs);
|
||||
v4_exec_reset(&es);
|
||||
v4_heat_reset(&h);
|
||||
v4_rstack_push(&n.rs, HOME);
|
||||
n.p = word;
|
||||
}
|
||||
|
||||
/* Run until the node is blocked, is back at HOME, or `max` words. */
|
||||
static unsigned run(unsigned max)
|
||||
{
|
||||
unsigned words = 0;
|
||||
while (words < max && !n.asking && n.p != HOME) { (void)v4_exec_step_word(&n, &es, &h); words++; }
|
||||
return words;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
v4_cell w_ask, w_mid, w_two;
|
||||
v4_uheat_t clock;
|
||||
|
||||
printf("v4 port tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
|
||||
|
||||
v4_asm_begin(&as, &n, 16);
|
||||
/* what KERNEL-WORD lays down: the request, the port, b! !b ; */
|
||||
w_ask = v4_asm_label(&as);
|
||||
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
|
||||
/* a write with more of the instruction word after it: dup + */
|
||||
w_mid = v4_asm_label(&as);
|
||||
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
|
||||
/* two writes one after the other */
|
||||
w_two = v4_asm_label(&as);
|
||||
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
|
||||
CHECK(v4_asm_ok(&as), "the test words assemble");
|
||||
|
||||
/* ---- no port: the address is memory ---- */
|
||||
v4_node_reset(&n);
|
||||
v4_asm_begin(&as, &n, 16);
|
||||
w_ask = v4_asm_label(&as);
|
||||
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
|
||||
w_mid = v4_asm_label(&as);
|
||||
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
|
||||
w_two = v4_asm_label(&as);
|
||||
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
|
||||
CHECK(n.port == -1 && !n.asking, "a node has no port until one is attached");
|
||||
n.mem[PORT] = MARK;
|
||||
start(w_ask);
|
||||
(void)run(100);
|
||||
CHECK(n.p == HOME && !n.asking && n.mem[PORT] == 7, "with none, a store to the address writes memory and the word returns");
|
||||
|
||||
/* ---- the write blocks ---- */
|
||||
v4_node_port_attach(&n, PORT);
|
||||
n.mem[PORT] = MARK;
|
||||
start(w_ask);
|
||||
v4_dstack_push(&n.ds, 11);
|
||||
v4_dstack_push(&n.ds, 22);
|
||||
(void)run(100);
|
||||
CHECK(n.asking && n.request == 7, "a store to the port blocks the node, with what it wrote: %ld", (long)n.request);
|
||||
CHECK(n.mem[PORT] == MARK, "and does not write memory");
|
||||
CHECK(n.p != HOME, "the word has not returned");
|
||||
CHECK(n.ds.depth == 2, "what was on the data stack is there for its neighbour");
|
||||
clock = es.anticlock;
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && v4_exec_step_word(&n, &es, &h) == 0, "a blocked node executes nothing");
|
||||
CHECK(es.anticlock == clock && n.asking, "however often it is stepped: the anti-clock does not move");
|
||||
|
||||
/* the neighbour takes two values and leaves one, then says it is served */
|
||||
{
|
||||
v4_cell b = v4_dstack_pop(&n.ds), a = v4_dstack_pop(&n.ds);
|
||||
v4_dstack_push(&n.ds, a + b);
|
||||
}
|
||||
v4_node_port_served(&n);
|
||||
(void)run(100);
|
||||
CHECK(n.p == HOME && !n.asking, "served, the word goes on and returns");
|
||||
CHECK(n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 33, "with what its neighbour left on the stack");
|
||||
|
||||
/* ---- the rest of the instruction word waits ---- */
|
||||
start(w_mid);
|
||||
v4_dstack_push(&n.ds, 100); /* to be replaced while the node is blocked */
|
||||
(void)run(100);
|
||||
CHECK(n.asking && n.request == 5, "blocked in the middle of an instruction word");
|
||||
(void)v4_dstack_pop(&n.ds);
|
||||
v4_dstack_push(&n.ds, 21); /* the neighbour's result */
|
||||
v4_node_port_served(&n);
|
||||
(void)run(100);
|
||||
CHECK(n.p == HOME && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 42,
|
||||
"the opcodes after the store run when it is served, on what the neighbour left: dup + of 21");
|
||||
|
||||
/* ---- one write after another ---- */
|
||||
start(w_two);
|
||||
(void)run(100);
|
||||
CHECK(n.asking && n.request == 1, "the first write blocks");
|
||||
v4_node_port_served(&n);
|
||||
(void)run(100);
|
||||
CHECK(n.asking && n.request == 2, "served, the node runs to the second and blocks again");
|
||||
v4_node_port_served(&n);
|
||||
(void)run(100);
|
||||
CHECK(n.p == HOME && !n.asking, "and returns when that is served");
|
||||
|
||||
/* ---- a fault while blocked abandons the rest of the word ---- */
|
||||
start(w_mid);
|
||||
v4_dstack_push(&n.ds, 1);
|
||||
(void)run(100);
|
||||
CHECK(n.asking, "blocked again");
|
||||
v4_node_port_served(&n);
|
||||
v4_node_fault(&n, V4_FAULT_RAISED, 12); /* no handler attached: the node stops */
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.stopped, "a node its neighbour faults does not go on with the word");
|
||||
|
||||
/* ---- detaching ---- */
|
||||
v4_node_reset(&n);
|
||||
CHECK(n.port == -1 && !n.asking, "reset detaches the port");
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
return failures != 0;
|
||||
}
|
||||
@@ -31,6 +31,20 @@ static int console_key(void)
|
||||
return c == EOF ? V4_BOOT_KEY_END : c;
|
||||
}
|
||||
|
||||
/* THE KERNEL'S WORDS, for a node that is a Linux program: the words that
|
||||
* are C in hosted v3 because only the host can do them. */
|
||||
static const char *const kernel_words[] = { "BYE" };
|
||||
static int leaving;
|
||||
|
||||
static void serve(v4_node *node, unsigned request)
|
||||
{
|
||||
(void)node;
|
||||
if (request == 1) { /* BYE ( -- ): as hosted v3, say so and leave */
|
||||
fputs("Goodbye!\n", stdout);
|
||||
leaving = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* The prompt is the host's, as it is the kernel's REPL's for a v3 VM: read a
|
||||
* line, hand it to the node, say how it ended. */
|
||||
int main(void)
|
||||
@@ -40,6 +54,7 @@ int main(void)
|
||||
|
||||
boot.n = &n; boot.es = &es; boot.h = &h; boot.im = &v4_capsule_image;
|
||||
boot.out = console_out; boot.key = console_key;
|
||||
boot.words = kernel_words; boot.word_count = sizeof kernel_words / sizeof kernel_words[0]; boot.serve = serve;
|
||||
if (!v4_boot_run(&boot, disk, BLOCKS)) { fflush(stdout); return 1; }
|
||||
|
||||
for (;;) {
|
||||
@@ -53,6 +68,7 @@ int main(void)
|
||||
if (len && line[len - 1] == '\n') len--;
|
||||
how = v4_boot_line(&boot, line, (unsigned)len);
|
||||
if (how == V4_LINE_COMPLETED) fputs(" ok\n", stdout);
|
||||
if (leaving) { fflush(stdout); return 0; }
|
||||
else if (how == V4_LINE_ERROR) fputs(" ERROR\n", stdout);
|
||||
else if (how == V4_BOOT_LINE_TOO_LONG) fputs("Line too long\n ERROR\n", stdout);
|
||||
else if (how == V4_BOOT_LINE_NO_INPUT) { putchar('\n'); return 0; }
|
||||
|
||||
+2
-2
@@ -100,8 +100,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
fprintf(out, "};\n\nconst v4_image v4_capsule_image = {\n cells, %uu,\n %uu, %uu, %uu, %uu,\n", count,
|
||||
(unsigned)V4_CELL_BITS, (unsigned)V4_NODE_WORDS, (unsigned)V4_DATA_RING, (unsigned)V4_RET_RING);
|
||||
fprintf(out, " %ld, %ld, %ld,\n %ld, %uu, %ld,\n %ld,\n %ld, %ld,\n", (long)line, (long)idle, (long)LINE_STATUS,
|
||||
(long)TIB, 1025u, (long)SPAN, (long)faults, (long)w_key, (long)w_key_end);
|
||||
fprintf(out, " %ld, %ld, %ld,\n %ld, %uu, %ld,\n %ld, %ld,\n %ld, %ld,\n", (long)line, (long)idle, (long)LINE_STATUS,
|
||||
(long)TIB, 1025u, (long)SPAN, (long)faults, (long)PORT, (long)w_key, (long)w_key_end);
|
||||
fprintf(out, " %ld, %ld, %ld,\n %ld, %ld,\n %ld,\n %ld,\n %ld, %ld\n};\n", (long)CONSOLE_TX, (long)CONSOLE_RX, (long)CONSOLE_ST,
|
||||
(long)DSTACK_REG, (long)RSTACK_REG, (long)NODE_ERROR, (long)STORAGE_REG, (long)DP, (long)LATEST);
|
||||
if (fclose(out) != 0) die("cannot write", argv[1]);
|
||||
|
||||
Reference in New Issue
Block a user