feat(v4.0.0): a node is sent text as a message and sends back what it prints

MESH.md step 3.  The ports are the transport; the message is what is
transported: to, from, type, heat and TTL, ACL tag, sequence, length, then
text four characters to a word.

- quit.v4: a node with nothing to do is blocked reading "any port"; text
  for it is interpreted; (FINISH) sends what it printed and then how the
  text ended, and it waits again
- core.v4: EMIT keeps what is printed, (FLUSH-OUT) and (HDR) send it to the
  sender on the port the message came on.  EMIT still needs one free data
  cell and no more; it works on the return stack and in A and B
- message.h/.c: the same format for whatever is on a port and is not a node
- boot.c: the boot is the node's console on port 1 and its kernel on port 0
- the prompt tests are a console that speaks messages
- gone: v4_line_begin, v4_line_done, v4_line_status; writing a node's input
  buffer and setting its P from outside; any use of CONSOLE-TX

Verified: make -C v4 test (test_host_quit.c 1283 checks, the full-stack
figures unchanged) and make -C v4 sanitize pass; hosted-check passes on
three ISAs with POST 550 of 550; clean qemu with STARFORTH_V4=1 passes POST
and answers lines typed at each prompt on amd64, aarch64 and riscv64
(logs/20261006-110551, -111621, -111341).  -110837 is an aarch64 run ended
by the test wrapper's limit while still in UEFI firmware; it shows nothing
about v4.

Not done: KEY, EXPECT and QUERY still read the console's input registers;
a message not for this node is let go (step 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 11:19:46 -04:00
co-authored by Claude Opus 5.5
parent 42610e844f
commit a041b401ea
24 changed files with 844 additions and 178 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-06T14:30:09Z -->
<!-- Generated by mkcapsule --manifest 2026-10-06T15:15:54Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
Binary file not shown.
+23
View File
@@ -287,6 +287,29 @@ Each is tested, committed and pushed before the next.
6).
3. **Messages (section 6):** a StarForth node that reads messages when
idle; the console as a device; a line typed is a message.
**Done 2026-10-06, but for the keyboard.** A node with nothing to do is
blocked reading "any port" (`(IDLE)`, `v4/capsule/quit.v4`). What
arrives is a message; text for this node is interpreted; what it prints
is kept and sent back as messages to the sender, on the port the
message came on, and then a message saying how the text ended (`EMIT`,
`(FLUSH-OUT)`, `(HDR)` in `core.v4`; `(FINISH)` in `quit.v4`).
`v4/src/message.c` is the same format for whatever is on the other end
of a port and is not a node. The boot is that, on the node's port 1, as
the console (`v4_boot_line`); the prompt tests are too. Handing a node a
line by writing its input buffer and setting its `P` is gone
(`v4_line_begin` and the rest). Nothing reads the `CONSOLE-TX` register
any more.
`EMIT` still needs one free cell of the data stack and no more, as
before; it keeps its working values on the return stack. The full-stack
tests hold at the same figures as before.
All v4 tests pass at both widths and under ASan and UBSan;
`hosted-check` on three ISAs; bare metal `logs/20261006-110551` (amd64),
`-111621` (aarch64), `-111341` (riscv64). `-110837` is an aarch64 run
that was ended by the test wrapper's limit while still in UEFI firmware,
before the kernel had started; it shows nothing about v4.
**Not done:** `KEY`, `EXPECT` and `QUERY` still read the console's two
input registers, not a message. A message not for this node, or not
text, is let go: passing it on is step 4.
4. **Finding the way (section 7).**
5. **Birth and Hera's requests (section 9); the unit of five.**
6. **Storage (section 8).**
+1 -1
View File
@@ -613,7 +613,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \
KERNEL_CFLAGS += $(V4_DEFS)
LOADER_CFLAGS += $(V4_DEFS)
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c)
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c)
V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
+2 -2
View File
@@ -100,8 +100,8 @@ void sk_v4_run(void)
console_puts("ok> ");
len = sk_v4_readline();
how = v4_boot_line(&boot, sk_v4_text, len);
if (how == V4_LINE_COMPLETED) console_puts(" ok\n");
else if (how == V4_LINE_ERROR) console_puts(" ERROR\n");
if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
else if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
else if (how == V4_BOOT_LINE_STOPPED) {
console_println("StarForth v4: the node stopped on a fault");
for (;;) { }
@@ -0,0 +1,130 @@
[=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 6 2026 11:05:25
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 131
Total memory: 1023 MB
Usable memory: 968 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 249252
Free pages : 247729
Used pages : 1523
Total MB : 973
Free MB : 967
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=2101540994 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=1006649045, tick_hz=100, initial_count=10066490
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=0x97a655081b2af559 capsule_hash=0x97a655081b2af559 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x594818fc69677603
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x8768fb163c8529f4
PARITY:OK
POST: PASSED
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> BYE
BYE: cold restart
@@ -0,0 +1,2 @@
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
[=3h
@@ -0,0 +1,174 @@
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 = 0x00000000bdea78f8
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 6 2026 11:13:04
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 977 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250782
Free pages : 250130
Used pages : 652
Total MB : 979
Free MB : 977
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=0x97a655081b2af559 capsule_hash=0x97a655081b2af559 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x594818fc69677603
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x8768fb163c8529f4
PARITY:OK
POST: PASSED
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> BYE
BYE: cold restart
@@ -0,0 +1,104 @@
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 6 2026 11:15:55
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 108
Total memory: 4093 MB
Usable memory: 4056 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 1039866
Free pages : 1038455
Used pages : 1411
Total MB : 4061
Free MB : 4056
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=0x97a655081b2af559 capsule_hash=0x97a655081b2af559 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x594818fc69677603
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x8768fb163c8529f4
PARITY:OK
POST: PASSED
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> BYE
BYE: cold restart
+1 -1
View File
@@ -69,7 +69,7 @@ ROOT := $(abspath $(HERE)/..)
NUCLEUS_64 := $(ROOT)/capsules/v4/nucleus-64.f18
NUCLEUS_32 := $(BINDIR)/nucleus-32.f18
HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)
ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c)
ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c)
define IMAGE_RULE
$(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile
+12 -12
View File
@@ -59,7 +59,7 @@ 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 name=v4:nucleus-64.f18 capsule_id=... capsule_hash=0xbcc90cc5a638a299 words=298
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=... capsule_hash=0x97a655081b2af559 words=298
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=...
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=...
@@ -68,23 +68,23 @@ POST: PASSED
ok>
```
Bare-metal logs: `logs/20261006-102421/amd64/`, `logs/20261006-102706/aarch64/`,
`logs/20261006-103048/riscv64/`. On each, seven lines were then typed at the
Bare-metal logs: `logs/20261006-110551/amd64/`, `logs/20261006-111621/aarch64/`,
`logs/20261006-111341/riscv64/`. On each, seven lines were then typed at the
prompt through the serial port — a definition, its use with the capsule's
`U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), 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
its own command line and prints no prompt. Its host puts the text in the
node's input buffer and starts it at `(LINE)`; the node interprets the text
and stops at `(IDLE)`, leaving in `(LINE-STATUS)` how it ended. The host says
` ok` or ` ERROR` and prompts, as the kernel's REPL does for a v3 VM. A line
may be 1024 characters, a block. `v4_line_begin`, `v4_line_done` and
`v4_line_status` (`v4/include/v4/image.h`) are that interface;
`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 is sent text as a message** (`docs/v4.0.0/MESH.md` section 6). It
does not read its own command line, prints no prompt and has no console of
its own. With nothing to do it is blocked reading its ports. A neighbour
writes it a message; text for it is interpreted; what it prints goes back
as messages to the sender, and then a message saying how the text ended.
Its console says ` ok` or ` ERROR` and prompts, as the kernel's REPL does
for a v3 VM. A message carries 1024 characters, a block. `v4/src/message.c`
is the format for a host; `v4_boot_line` (`v4/system/boot.c`) is the
console both products and the capsule loader use.
**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
+56 -8
View File
@@ -5,7 +5,8 @@
\ written here as text for the text assembler (v4/include/v4/text.h).
\
\ Constants the loader supplies: N-1 (cell bits - 1), NODE-ERROR, CONSOLE-TX,
\ CONSOLE-RX, CONSOLE-STATUS, BASE, (EMIT-HOOK).
\ CONSOLE-RX, CONSOLE-STATUS, BASE, (EMIT-HOOK), (MSG), (REPLY), (ME), (OUT),
\ (OUT^).
\
\ ERRORS (D-18). A word that finds an error takes its arguments off the stack
\ and stores a code in NODE-ERROR. On a node with a prompt that store is a
@@ -91,16 +92,63 @@ header CMOVE
DONE: drop drop drop ;
\ ---- section 5.10: the console -------------------------------------------
\ EMIT sends the character to the console, unless (EMIT-HOOK) holds the xt
\ of a word: then that word is given it instead ( c -- ), and returns to
\ EMIT's caller. Everything the node prints goes through EMIT, so a hook
\ sees all of it. The prompt takes the hook off at the end of every line
\ (quit.v4), so that nothing can leave the console silent. POST uses it
\ to compare what a case prints (docs/v4.0.0/NUCLEUS.md 6.3).
\ ---- what a node prints (docs/v4.0.0/MESH.md section 6) ------------------------
\ A node has no console of its own. It does what a message asks, and what
\ it prints while doing it goes back, as a message, to whoever sent that one,
\ on the port it came on. A message is seven words and then its text, four
\ characters to a word:
\ to, from, type, heat and TTL, ACL tag, sequence, length in characters
\ The types: 1 text to be interpreted 2 what a node printed 3 how the
\ text ended (one word of text: 0 QUIT, 1 completed, 2 an error).
\ The header of the message being served is in the seven cells (MSG); (REPLY)
\ is the address of the port it came on; (ME) is this node's number.
\
\ Characters are kept in (OUT), 256 of them, a character to a cell, and sent
\ when it is full and when the text has been done with (quit.v4). (OUT^)
\ is where the next goes.
\
\ THE STACK. What prints may be run with the data stack all but full: EMIT
\ has always needed one free cell and no more, and still does -- it keeps
\ the character and A on the return stack while it works. Sending needs
\ two.
\ ( type -- ) the first six words of a message to the sender of the one
\ being served; its length, and its text, are the caller's to send. Each
\ write waits for the neighbour to take it. B is left at the port.
: (HDR)
(REPLY) a! @ b!
(MSG)+1 a! @ !b \ to
(ME) a! @ !b \ from
!b \ type
0 !b 0 !b 0 !b ; \ heat and TTL, ACL tag, sequence: carried, not used yet
\ ( -- ) send what has been printed: its length, then its characters, four
\ to a word, the first lowest
: (FLUSH-OUT)
(OUT^) a! @ (OUT) xor if NONE drop
2 (HDR)
(OUT^) a! @ (OUT) - dup !b
3 + 2/ 2/ -1 + (OUT) a!
FOR @+ @+ 8* + @+ 8* 8* + @+ 8* 8* 8* + !b NEXT
(OUT) (OUT^) a! ! ;
NONE: drop ;
\ EMIT puts the character with what has been printed, unless (EMIT-HOOK)
\ holds the xt of a word: then that word is given it instead ( c -- ), and
\ returns to EMIT's caller. Everything the node prints goes through EMIT,
\ so a hook sees all of it. The hook is taken off when the text being
\ interpreted ends (quit.v4), so that nothing can leave a node silent.
\ POST uses it to compare what a case prints (docs/v4.0.0/NUCLEUS.md 6.3).
\ A is kept: the words that print have always been free to use it across
\ an EMIT. As v3, only the low 8 bits of the character are printed.
header EMIT
: EMIT ( c -- )
(EMIT-HOOK) b! @b if NONE push ;
NONE: drop CONSOLE-TX b! !b ;
NONE: drop
255 and a push push \ A, and then the character, to the return stack
(OUT^) b! @b a! pop !+ a !b \ the character goes where (OUT^) points, which moves on
a (OUT)+256 xor if FULL drop pop a! ;
FULL: drop (FLUSH-OUT) pop a! ;
header KEY
: KEY ( -- c )
L: CONSOLE-STATUS b! @b if WAIT drop CONSOLE-RX b! @b ;
+66 -26
View File
@@ -4,17 +4,17 @@
\ DECOMPOSITION.md 5.15 and 5.10: QUIT ABORT ABORT" (ABORT") ." (."). Part of
\ the compiler capsule. Rests on all the files before it.
\
\ A NODE IS HANDED A LINE (docs/v4.0.0/ENGINE.md 3.1). It does not read its
\ own command line and prints no prompt: that is its host's, as it is the
\ kernel's REPL's for a v3 VM. The host puts the text in TIB and starts
\ (LINE); the node interprets it and stops at (IDLE), having left in the
\ variable (LINE-STATUS) how the line ended:
\ 1 it completed the host says " ok"
\ A NODE IS SENT TEXT (docs/v4.0.0/MESH.md section 6). It does not read its
\ own command line and prints no prompt. A node with nothing to do waits at
\ its ports; a neighbour writes it a message; if that is text for this node
\ it is interpreted; what it printed goes back as a message, and then a
\ message saying how the text ended:
\ 1 it completed a console says " ok"
\ 2 it ended in an error the message is already printed; " ERROR"
\ 0 QUIT nothing is said
\ What the console then shows is v3's:
\ ok> 65 EMIT the host's prompt, and the line
\ A ok what the line printed, then the host's " ok"
\ What a console then shows is v3's:
\ ok> 65 EMIT the console's prompt, and the line
\ A ok what the line printed, then " ok"
\ ok> NOSUCH
\ UNKNOWN WORD: 'NOSUCH'
\ ERROR
@@ -22,7 +22,8 @@
\ Address out of range an address fault (D-14), from any depth
\ ERROR
\ KEY, EXPECT and QUERY are FORTH-79 words that read characters, as before;
\ they are not how a line reaches the node.
\ they are not how text reaches the node. They still read the console's
\ registers, CONSOLE-RX and CONSOLE-STATUS, and not a message.
\
\ THE STACKS are guarded (D-16): each counts what it holds, and a push onto
\ a full one or a pop from an empty one is a fault. So QUIT and ABORT, which
@@ -30,7 +31,10 @@
\ the data stack too. A store to RSTACK-DEPTH or DSTACK-DEPTH does that.
\
\ Constants the loader supplies:
\ (LINE-STATUS) word address of the variable: how the last line ended
\ (LINE-STATUS) word address of the variable: how the last text ended
\ (MSG) (REPLY) (ME) (OUT) (OUT^) see core.v4, "what a node prints"
\ (PORT) word address of the node's port 0; (PORT)+8 is "any port" and
\ (PORT)+9 the port the last read from that came on
\ (Q) word address of four cells of scratch, which system.v4 and
\ blocks.v4 use too
\ DSTACK-DEPTH RSTACK-DEPTH word addresses of the stack registers
@@ -52,26 +56,62 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
\ (docs/v4.0.0/NUCLEUS.md 6.3). (EMIT-HOOK), core.v4, is set to 0 here at
\ the end of every line, caught or not.
\
\ ( k -- ) the line is over. It is entered with how -- 0 QUIT, 1 completed,
\ anything else an error -- and never returns: it leaves that in
\ (LINE-STATUS) and stops at (IDLE), where the host finds the node. It is
\ always jumped to, never called, by something that has emptied the return
\ stack (or by a fault, which empties it).
: (IDLE) L: jump L
\ ( s -- ) the text is done with. What it printed is sent, then a message
\ saying how it ended -- 0 QUIT, 1 completed, 2 an error -- and the node waits
\ for the next. (DONE) is entered with the same number and decides whether
\ an error was one to catch; it is always jumped to, never called, by
\ something that has emptied the return stack (or by a fault, which empties
\ it). (LINE-STATUS) keeps how the last text ended.
: (FINISH) ( s -- )
(LINE-STATUS) b! dup !b
(FLUSH-OUT)
3 (HDR) 4 !b !b \ a message of type 3, four characters long: how it ended
jump (IDLE)
: (DONE)
(EMIT-HOOK) b! 0 !b \ the console is the console again
(EMIT-HOOK) b! 0 !b \ what is printed is printed again
if GO -1 + if OK
drop (RESET)
(CATCH) b! @b if LOUD drop -1 !b 1 jump (DONE) \ caught: the line completed
LOUD: drop 2 (LINE-STATUS) b! !b jump (IDLE)
OK: drop 1 (LINE-STATUS) b! !b jump (IDLE)
GO: drop 0 (LINE-STATUS) b! !b jump (IDLE)
(CATCH) b! @b if LOUD drop -1 !b 1 jump (DONE) \ caught: the text completed
LOUD: drop 2 jump (FINISH)
OK: drop 1 jump (FINISH)
GO: drop 0 jump (FINISH)
\ ( -- ) THE LINE ENTRY. The host has put a line of text in TIB, a zero
\ after it, and its length in SPAN, has emptied the return stack and set P
\ here. The line is interpreted with one return entry under it, this
\ word's call of INTERPRET; whatever is on the data stack stays there.
\ ( -- ) WAITING. A node with nothing to do is blocked reading its ports
\ (docs/v4.0.0/MESH.md 4.1): it executes nothing until a neighbour writes.
\ What arrives is a message (core.v4). Its first word is taken from any
\ port and the rest from the port that came on. Its text goes into TIB,
\ which holds 1024 characters, the most a message carries; a longer one is
\ read to its end, so that what follows it is not taken for a message, and
\ is error 12. A message of type 1 for this node is text to interpret: it
\ is done as a line was, and (FINISH) reports how it ended. Any other is
\ let go: passing a message on is MESH.md step 4.
: (IDLE)
(PORT)+8 b! @b (MSG) a! !+ \ to, from any port
(PORT)+9 b! @b (PORT) + dup b! \ B: the port it came on
5 FOR @b !+ UNEXT \ from, type, heat and TTL, ACL tag, sequence, length
(REPLY) a! !
(MSG)+6 a! @ -if SIZED jump BAD \ a length below zero: no telling what follows
SIZED: dup -1025 + -if LONG drop jump FITS
LONG: drop 3 + 2/ 2/ -1 + FOR @b drop UNEXT 0 \ too long: read it and let it go
BAD: drop 0 (MSG)+6 a! ! 0 SPAN a! ! NODE-ERROR b! 12 !b
FITS:
3 + 2/ 2/ if EMPTY
-1 + TIB 2/ 2/ a! FOR @b !+ UNEXT
jump READ
EMPTY: drop
READ:
0 (MSG)+6 a! @ TIB + C! \ a zero after the text
(MSG)+6 a! @ SPAN a! !
(MSG) a! @ (ME) a! @ xor if MINE drop jump (IDLE)
MINE: drop
(MSG)+2 a! @ -1 + if TEXT drop jump (IDLE)
TEXT: drop jump (LINE)
\ ( -- ) THE TEXT, INTERPRETED. It is in TIB with a zero after it and its
\ length in SPAN, and the return stack is empty. It is interpreted with one
\ return entry under it, this word's call of INTERPRET; whatever is on the
\ data stack stays there.
: (LINE)
0 BLK a! ! \ the terminal, whatever was being loaded
TIB (SRC) a! ! 0 >IN a! !
+4 -2
View File
@@ -33,6 +33,7 @@
#define V4_BOOT_H
#include "v4/image.h"
#include "v4/message.h"
typedef struct {
v4_node *n;
@@ -66,8 +67,9 @@ typedef struct {
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks);
/* Hand the node a line and run it to its end, sending what it prints to
* `out`. Returns how it ended -- V4_LINE_QUIT, V4_LINE_COMPLETED or
* V4_LINE_ERROR (image.h) -- or one of these: */
* `out`. The line is sent to the node as a message and what it prints
* comes back as messages (message.h). Returns how it ended -- V4_TEXT_QUIT,
* V4_TEXT_COMPLETED or V4_TEXT_ERROR -- or one of these: */
#define V4_BOOT_LINE_TOO_LONG (-1) /* more than the node's input buffer holds; nothing was run */
#define V4_BOOT_LINE_STOPPED (-2) /* the node stopped on a fault, or never finished */
#define V4_BOOT_LINE_NO_INPUT (-3) /* the line read the keyboard and there is no more input */
+8 -30
View File
@@ -10,6 +10,10 @@
* (capsules/v4/nucleus-<width>.f18). docs/v4.0.0/MESH.md section 5.
* - this description of it, a C file: the addresses a host needs.
*
* A node that has taken the nucleus in is sent text as messages and sends
* back what it prints (v4/include/v4/message.h); nothing here hands it a
* line.
*
* A description is for one cell width, one node size and one pair of stack
* sizes; v4_image_born refuses one the engine was not built for.
*/
@@ -22,16 +26,10 @@ typedef struct {
/* what the image was built for */
unsigned cell_bits, node_words, data_ring, ret_ring;
/* The node is handed a line (docs/v4.0.0/ENGINE.md 3.1). Its host puts
* text in the input buffer and starts it at `line`; it stops at `idle`,
* with how the line ended in the cell `line_status`. At switch-on it
* is at `idle`. */
v4_cell line; /* (LINE) */
v4_cell idle; /* (IDLE) */
v4_cell line_status; /* word address of the variable (LINE-STATUS) */
v4_cell tib; /* byte address of the input buffer */
unsigned tib_bytes; /* its size: a line may be one less */
v4_cell span; /* word address of the variable SPAN */
/* Where the nucleus waits for a message (v4/capsule/quit.v4, (IDLE)):
* the node's P is there, blocked reading its ports, when it has nothing
* to do. */
v4_cell idle;
v4_cell fault_table; /* (FAULTS): where a fault goes */
v4_cell port; /* word address of the node's port (node.h) */
@@ -67,11 +65,6 @@ extern const v4_image v4_capsule_image;
* back to, and FORGET will not go below it. */
void v4_image_seal(v4_node *n, const v4_image *im);
/* How a line ended. */
#define V4_LINE_QUIT 0 /* QUIT: nothing is to be said */
#define V4_LINE_COMPLETED 1 /* the host says " ok" */
#define V4_LINE_ERROR 2 /* the message is printed; the host says " ERROR" */
/* A node newly born (node.h), with the registers the nucleus expects of the
* node it runs on -- and `disk`, `blocks` blocks of 1024 bytes, as its block
* storage, or none if `disk` is 0. Its memory is empty and it is blocked
@@ -81,21 +74,6 @@ void v4_image_seal(v4_node *n, const v4_image *im);
int v4_image_born(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
unsigned char *disk, unsigned blocks);
/* Hand the node `len` characters of text to interpret. Returns 1, or 0 if
* the text is longer than the node's input buffer holds (nothing is then
* done). After it the host runs the node -- v4_exec_step_word again and
* again, taking what the node prints from n->console and setting
* n->console_len back to 0 -- until v4_line_done; if v4_image_waiting comes
* first, the line is reading the keyboard, and the host gives it characters
* with v4_node_console_feed. */
int v4_line_begin(v4_node *n, const v4_image *im, const char *text, unsigned len);
/* 1 when the node has finished the line it was handed and is idle again. */
int v4_line_done(const v4_node *n, const v4_image *im);
/* How the line just finished ended: V4_LINE_QUIT, _COMPLETED or _ERROR. */
int v4_line_status(const v4_node *n, const v4_image *im);
/* 1 if the node is waiting for a character: it is inside KEY and none is
* pending. */
int v4_image_waiting(const v4_node *n, const v4_image *im);
+63
View File
@@ -0,0 +1,63 @@
/* message.h -- a message, as it is written to a port and read from one.
* docs/v4.0.0/MESH.md section 6.
*
* The ports are the transport; the message is what is transported. It is
* v3's Hermes message with what it carries, a word at a time:
*
* word 0 to: the node it is for
* word 1 from: the node that sent it
* word 2 type
* word 3 heat and TTL
* word 4 ACL tag: the sender's identity fingerprint
* word 5 sequence
* word 6 how many characters of text follow, 0 to 1024
* word 7.. the text, four characters to a word, the first lowest
*
* Words 3, 4 and 5 are carried and not yet used: checking them is the
* router's, and awaits rulings (MESH.md section 3).
*
* This is for whatever is on the other end of a node's port and is not a
* node -- a console, a host -- to speak what the nodes speak (v4/capsule/
* core.v4 and quit.v4 are the same thing in the node). Nothing here uses
* the C library.
*/
#ifndef V4_MESSAGE_H
#define V4_MESSAGE_H
#include "v4/cell.h"
#define V4_MSG_HEADER 7u
#define V4_MSG_MAX_CHARS 1024u /* a block */
#define V4_MSG_MAX_WORDS (V4_MSG_HEADER + V4_MSG_MAX_CHARS / 4u)
#define V4_MSG_TEXT 1 /* text to be interpreted */
#define V4_MSG_OUTPUT 2 /* what a node printed */
#define V4_MSG_DONE 3 /* how the text ended: one word, V4_TEXT_* */
/* how text ended */
#define V4_TEXT_QUIT 0 /* QUIT: nothing is to be said */
#define V4_TEXT_COMPLETED 1 /* a console says " ok" */
#define V4_TEXT_ERROR 2 /* the message is printed; a console says " ERROR" */
typedef struct {
v4_cell word[V4_MSG_MAX_WORDS];
unsigned count; /* words in it */
} v4_message;
/* A message of `len` characters, at most V4_MSG_MAX_CHARS. Returns how many
* words it is, or 0 if `len` is too many. */
unsigned v4_message_text(v4_message *m, v4_cell to, v4_cell from, v4_cell type, const char *chars, unsigned len);
/* A message arriving a word at a time. Start with m->count = 0 and give
* each word as it comes: returns 1 when the message is whole, 0 if there is
* more to come. A length that is not 0 to V4_MSG_MAX_CHARS is taken as 0. */
int v4_message_word(v4_message *m, v4_cell word);
/* Its parts. */
#define v4_message_to(m) ((m)->word[0])
#define v4_message_from(m) ((m)->word[1])
#define v4_message_type(m) ((m)->word[2])
unsigned v4_message_length(const v4_message *m); /* characters of text */
char v4_message_char(const v4_message *m, unsigned i); /* the i-th of them */
#endif /* V4_MESSAGE_H */
-33
View File
@@ -29,39 +29,6 @@ int v4_image_waiting(const v4_node *n, const v4_image *im)
return n->input_pos == n->input_len && n->p >= im->key_start && n->p < im->key_end;
}
/* One byte of the node's memory: four to a cell, the first lowest, at either
* cell width -- as C! stores them (v4/capsule/core.v4). */
static void put_byte(v4_node *n, v4_cell baddr, unsigned c)
{
v4_cell word = baddr / 4;
unsigned shift = 8u * (unsigned)(baddr % 4);
v4_ucell mask = (v4_ucell)0xFFu << shift;
n->mem[word] = (v4_cell)(((v4_ucell)n->mem[word] & ~mask) | ((v4_ucell)(c & 0xFFu) << shift));
}
int v4_line_begin(v4_node *n, const v4_image *im, const char *text, unsigned len)
{
unsigned i;
if (len + 1u > im->tib_bytes) return 0;
for (i = 0; i < len; i++) put_byte(n, im->tib + (v4_cell)i, (unsigned char)text[i]);
put_byte(n, im->tib + (v4_cell)len, 0);
n->mem[im->span] = (v4_cell)len;
v4_rstack_reset(&n->rs);
n->p = im->line;
return 1;
}
int v4_line_done(const v4_node *n, const v4_image *im)
{
return n->p == im->idle;
}
int v4_line_status(const v4_node *n, const v4_image *im)
{
return (int)n->mem[im->line_status];
}
void v4_image_seal(v4_node *n, const v4_image *im)
{
n->mem[im->boot_cells] = n->mem[im->dp];
+44
View File
@@ -0,0 +1,44 @@
/* message.c -- a message, as it is written to a port and read from one.
* See message.h. Nothing here uses the C library.
*/
#include "v4/message.h"
unsigned v4_message_text(v4_message *m, v4_cell to, v4_cell from, v4_cell type, const char *chars, unsigned len)
{
unsigned i, words;
if (len > V4_MSG_MAX_CHARS) return 0;
words = (len + 3u) / 4u;
m->word[0] = to;
m->word[1] = from;
m->word[2] = type;
m->word[3] = 0;
m->word[4] = 0;
m->word[5] = 0;
m->word[6] = (v4_cell)len;
for (i = 0; i < words; i++) m->word[V4_MSG_HEADER + i] = 0;
for (i = 0; i < len; i++)
m->word[V4_MSG_HEADER + i / 4u] = (v4_cell)((v4_ucell)m->word[V4_MSG_HEADER + i / 4u]
| ((v4_ucell)(unsigned char)chars[i] << (8u * (i % 4u))));
m->count = V4_MSG_HEADER + words;
return m->count;
}
unsigned v4_message_length(const v4_message *m)
{
v4_cell len = m->word[6];
return (len < 0 || len > (v4_cell)V4_MSG_MAX_CHARS) ? 0u : (unsigned)len;
}
char v4_message_char(const v4_message *m, unsigned i)
{
return (char)(((v4_ucell)m->word[V4_MSG_HEADER + i / 4u] >> (8u * (i % 4u))) & 0xFFu);
}
int v4_message_word(v4_message *m, v4_cell word)
{
if (m->count < V4_MSG_MAX_WORDS) m->word[m->count] = word;
m->count++;
if (m->count < V4_MSG_HEADER) return 0;
return m->count >= V4_MSG_HEADER + (v4_message_length(m) + 3u) / 4u;
}
+56 -16
View File
@@ -5,6 +5,7 @@
* which only a whole system has.
*/
#include "v4/boot.h"
#include "v4/message.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/capsule_sig.h"
@@ -126,22 +127,29 @@ static void post_watch(char c)
post_len = 0;
}
/* The boot is the node's neighbour on two of its ports. On port 0 it is
* the kernel: the node writes the number of a request there (ENGINE.md
* 3.3). On port 1 it is the console: it writes the node a message of text
* and reads back messages of what the node printed and of how the text
* ended (message.h). */
#define KERNEL_PORT 0u
#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 */
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 */
v4_node *n = b->n;
uint64_t steps = 0;
unsigned i;
unsigned sent = 0, i;
if (!v4_line_begin(n, b->im, text, len)) return V4_BOOT_LINE_TOO_LONG;
if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
in.count = 0;
for (;;) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->console_len) {
for (i = 0; i < n->console_len; i++) post_watch((char)n->console[i]);
b->out((const char *)n->console, n->console_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 (n->asking && n->ask_port == KERNEL_PORT) { /* a request: 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);
@@ -153,8 +161,34 @@ int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
}
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 */
if (n->asking && n->ask_port == CONSOLE_PORT) { /* a word of a message from the node */
v4_cell word = n->request;
v4_node_port_served(n);
if (v4_message_word(&in, word)) {
unsigned chars = v4_message_length(&in);
if (v4_message_type(&in) == V4_MSG_OUTPUT) {
for (i = 0; i < chars; i++) {
char c = v4_message_char(&in, i);
post_watch(c);
b->out(&c, 1);
}
} else if (v4_message_type(&in) == V4_MSG_DONE) {
return (int)in.word[V4_MSG_HEADER];
}
in.count = 0;
}
continue;
}
if (n->asking) return V4_BOOT_LINE_STOPPED; /* a port with nothing on it */
if (n->reading && !n->given) {
if (sent < out.count && (n->read_port == CONSOLE_PORT || n->read_port == V4_PORT_ANY)) {
v4_node_port_give(n, CONSOLE_PORT, out.word[sent++]); /* a word of the message to the node */
continue;
}
return V4_BOOT_LINE_STOPPED; /* waiting for what will not come */
}
if (v4_image_waiting(n, b->im)) { /* the text is reading the keyboard */
int c = b->key ? b->key() : V4_BOOT_KEY_END;
unsigned char ch = (unsigned char)c;
if (c == V4_BOOT_KEY_END) return V4_BOOT_LINE_NO_INPUT;
@@ -225,10 +259,10 @@ static int load_capsule(const v4_boot *b, const char *name)
if (len) {
for (i = 0; i < len; i++) text[i] = (char)p[i];
int how = v4_boot_line(b, text, len);
if (how != V4_LINE_COMPLETED) {
if (how != V4_TEXT_COMPLETED) {
say_where(b, name, block, line);
say(b, how == V4_LINE_ERROR ? " ended in an error: "
: how == V4_LINE_QUIT ? " ended with QUIT: "
say(b, how == V4_TEXT_ERROR ? " ended in an error: "
: how == V4_TEXT_QUIT ? " ended with QUIT: "
: how == V4_BOOT_LINE_NO_INPUT ? " reads the keyboard: "
: " stopped the node: ");
b->out(text, len); say(b, "\n");
@@ -289,7 +323,9 @@ static int load_nucleus(const v4_boot *b)
if (!p || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; }
words = cap->length / CELL_BYTES;
while (at < words || !v4_line_done(n, b->im)) {
/* until it has taken every word and is waiting for a message: reading
* its ports again, but from its memory now and not from the port */
while (at < words || !(n->reading && !n->given && n->p != b->im->port + (v4_cell)V4_PORT_ANY)) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
say(b, "V4: the node did not take the nucleus in\n");
@@ -298,7 +334,11 @@ static int load_nucleus(const v4_boot *b)
if (n->reading && !n->given) {
v4_ucell word = 0;
unsigned i;
if (at >= words) { say(b, "V4: the node wants more than the nucleus holds\n"); return 0; }
if (at >= words) {
if (n->p != b->im->port + (v4_cell)V4_PORT_ANY) break; /* it is waiting for a message */
say(b, "V4: the node wants more than the nucleus holds\n");
return 0;
}
for (i = 0; i < CELL_BYTES; i++) word |= (v4_ucell)p[at * CELL_BYTES + i] << (8 * i);
v4_node_port_give(n, 0, (v4_cell)word);
at++;
@@ -335,7 +375,7 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
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) {
if (b->words[i][k] || v4_boot_line(b, text, at) != V4_TEXT_COMPLETED) {
say(b, "V4: kernel word "); say(b, b->words[i]); say(b, " could not be made\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
+12
View File
@@ -69,6 +69,12 @@
#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 - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */
#define ME (BUF0_W - 13) /* this node's number: a message is for it when its first word is this */
#define OUT_PTR (BUF0_W - 14) /* where the next character printed goes: a cell of the output buffer */
#define REPLY (BUF0_W - 15) /* the address of the port the message being served came on */
#define MSG (BUF0_W - 48) /* the header of the message being served, 7 cells */
#define OUT_W (BUF0_W - 400) /* the output buffer: 256 cells, a character to a cell */
#define OUT_CELLS 256
#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */
#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 */
@@ -108,6 +114,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
unsigned i;
v4_node_reset(n);
n->mem[OUT_PTR] = OUT_W; /* nothing has been printed: what is, is kept from here (core.v4) */
v4_text_begin(tx, n, 16);
v4_text_constant(tx, "N-1", V4_CELL_BITS - 1);
v4_text_constant(tx, "NODE-ERROR", NODE_ERROR);
@@ -148,6 +155,11 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
v4_text_constant(tx, "(CATCH)", CATCH);
v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS);
v4_text_constant(tx, "(PORT)", PORT);
v4_text_constant(tx, "(ME)", ME);
v4_text_constant(tx, "(OUT^)", OUT_PTR);
v4_text_constant(tx, "(REPLY)", REPLY);
v4_text_constant(tx, "(MSG)", MSG);
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-NUMBER", STORAGE_REG);
+4 -1
View File
@@ -263,7 +263,10 @@ int main(void)
}
n.mem[BASE] = 10;
CHECK(v4_node_console_feed(&n, "Z", 1) == 1 && run("KEY", 0, 0, 0, 0) && pop() == 'Z' && clean(), "KEY");
CHECK(run("EMIT", 1, 'q', 0, 0) && clean() && n.console_len == 1 && n.console[0] == 'q', "EMIT");
/* a node has no console of its own: what it prints is kept to be sent (core.v4) */
n.mem[OUT_PTR] = OUT_W;
CHECK(run("EMIT", 1, 'q', 0, 0) && clean() && n.mem[OUT_PTR] == OUT_W + 1 && n.mem[OUT_W] == 'q', "EMIT");
CHECK(run("EMIT", 1, 0x141, 0, 0) && clean() && n.mem[OUT_PTR] == OUT_W + 2 && n.mem[OUT_W + 1] == 'A', "EMIT prints the low 8 bits");
}
/* ---- the in-line names ---- */
+75 -38
View File
@@ -41,7 +41,7 @@
* 3 ROLL is ROT. v3's count from zero.
*/
#include "v4/text.h"
#include "v4/image.h"
#include "v4/message.h"
#include "v4/testcode.h"
#include <stdint.h>
#include <stdio.h>
@@ -59,7 +59,7 @@ static v4_exec_state es;
static v4_heat h;
static v4_text tx;
static v4_cell w_key, w_key_end, w_fault, capsule_latest;
static v4_image img; /* where a line is handed to the node: v4/include/v4/image.h */
static v4_cell w_idle; /* (IDLE): where the node waits for a message */
static char out[V4_CONSOLE_CAP + 1];
/* The storage device's blocks: zeros at every switch-on. */
@@ -73,16 +73,6 @@ static void put_block(unsigned num, const char *text)
}
static long last_steps;
/* THE HOST. A node is handed a line and prints no prompt (ENGINE.md 3.1);
* these tests are its host, and do what tools/hosted.c does: take what is
* typed a line at a time, hand each line over, and say " ok" or " ERROR" and
* the next prompt. What is typed after a line is there for a word in it
* that reads the keyboard, and what such a word does not take is the next
* line. */
static char typed[8192]; /* typed and not yet taken */
static unsigned typed_len;
static int line_open; /* a line has been handed over and has not ended */
/* THE KERNEL. A node asks its kernel by writing to its port (node.h), and
* these tests are that too. Request 1 is "an entry has been made" ( xt -- )
* and request 2 "the entries from here up have gone" ( w -- ): the kernel's
@@ -149,7 +139,31 @@ static const char *node_name(v4_cell xt)
return name;
}
/* Run until the line ends, or until it has been inside KEY with nothing to
/* THE CONSOLE. A node is sent text as a message and sends back what it
* prints (MESH.md section 6); these tests are its console, on its port 1,
* and do what tools/hosted.c does: take what is typed a line at a time,
* send each line as a message, show what comes back, and say " ok" or
* " ERROR" and the next prompt. What is typed after a line is there for a
* word in it that reads the keyboard, and what such a word does not take is
* the next line. */
#define CONSOLE_PORT 1u
static char typed[8192]; /* typed and not yet taken */
static unsigned typed_len;
static int line_open; /* a line has been sent and has not ended */
static v4_message to_node, from_node; /* the message going in, and the one coming out */
static unsigned to_sent; /* how many words of to_node the node has taken */
static char shown[V4_CONSOLE_CAP]; /* what the node has printed since the console last looked */
static unsigned shown_len;
static int shown_dropped;
static int text_ended; /* how the text ended, when it has */
/* 1 if the node is where it waits for a message, with nothing given it. */
static int node_idle(void)
{
return n.reading && !n.given && n.read_port == V4_PORT_ANY && n.p > w_idle && n.p <= w_idle + 3;
}
/* Run until the text ends, or until it has been inside KEY with nothing to
* read for 64 instruction words; or until `max` words. 1: ended, 2: waiting
* for a character, 0: neither. */
static int run_line(long max)
@@ -157,14 +171,36 @@ static int run_line(long max)
long steps = 0;
unsigned idle = 0;
while (steps < max && idle < 64) {
if (v4_line_done(&n, &img)) { last_steps = steps; return 1; }
(void)v4_exec_step_word(&n, &es, &h);
steps++;
if (n.asking) { kernel_serve(); v4_node_port_served(&n); } /* blocked at its port: the kernel's turn */
if (n.asking && n.ask_port == 0) { kernel_serve(); v4_node_port_served(&n); continue; } /* a request */
if (n.asking && n.ask_port == CONSOLE_PORT) { /* a word of a message from the node */
v4_cell word = n.request;
v4_node_port_served(&n);
if (v4_message_word(&from_node, word)) {
unsigned i, chars = v4_message_length(&from_node);
if (v4_message_type(&from_node) == V4_MSG_OUTPUT) {
for (i = 0; i < chars; i++) {
if (shown_len < sizeof shown) shown[shown_len++] = v4_message_char(&from_node, i);
else shown_dropped = 1;
}
} else if (v4_message_type(&from_node) == V4_MSG_DONE) {
text_ended = (int)from_node.word[V4_MSG_HEADER];
from_node.count = 0;
last_steps = steps;
return 1;
}
from_node.count = 0;
}
continue;
}
if (n.reading && !n.given && to_sent < to_node.count && (n.read_port == CONSOLE_PORT || n.read_port == V4_PORT_ANY)) {
v4_node_port_give(&n, CONSOLE_PORT, to_node.word[to_sent++]); /* a word of the message to the node */
continue;
}
if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
}
last_steps = steps;
if (v4_line_done(&n, &img)) return 1;
return idle >= 64 ? 2 : 0;
}
@@ -193,7 +229,6 @@ static void boot_with(unsigned depth)
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(disk, 0, sizeof disk);
v4_node_storage_attach(&n, STORAGE_REG, disk, DISK_BLOCKS);
v4_node_console_attach(&n, CONSOLE_TX);
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST);
v4_node_fault_attach(&n, w_fault);
v4_node_error_attach(&n, NODE_ERROR);
@@ -208,10 +243,16 @@ static void boot_with(unsigned depth)
n.mem[WORD_FORGOTTEN] = 0;
known_n = 0;
last_request = 0;
n.mem[LINE_STATUS] = V4_LINE_COMPLETED;
n.p = img.idle; /* idle, waiting to be handed a line */
n.mem[LINE_STATUS] = 1;
n.mem[ME] = 0; /* it has no number yet: a message to 0 is for it */
n.mem[OUT_PTR] = OUT_W;
n.mem[REPLY] = PORT + CONSOLE_PORT;
n.p = w_idle; /* waiting for a message */
typed_len = 0;
line_open = 0;
to_node.count = 0; to_sent = 0; from_node.count = 0;
shown_len = 0; shown_dropped = 0;
(void)v4_exec_step_word(&n, &es, &h); /* it reads its ports, and is blocked */
}
static void boot(void) { boot_with(0); }
/* The same with nothing at all on the data stack. */
@@ -252,27 +293,27 @@ static const char *say(const char *input)
unsigned len;
if (!nl) break;
len = (unsigned)(nl - typed);
if (!v4_line_begin(&n, &img, typed, len)) return "(line too long)";
if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, typed, len)) return "(line too long)";
to_sent = 0;
memmove(typed, nl + 1, typed_len - len - 1);
typed_len -= len + 1;
line_open = 1;
}
v4_node_console_attach(&n, CONSOLE_TX); /* empties the capture */
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST); /* and the keyboard */
shown_len = 0; shown_dropped = 0;
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST); /* empties the keyboard */
if (v4_node_console_feed(&n, typed, typed_len) != typed_len) return "(input queue full)";
ended = run_line(40000000);
took = n.input_pos; /* what a word in the line read */
memmove(typed, typed + took, typed_len - took);
typed_len -= took;
if (n.console_dropped || olen + n.console_len + 16 > sizeof out) return "(too much output)";
memcpy(out + olen, n.console, n.console_len);
olen += n.console_len;
if (shown_dropped || olen + shown_len + 16 > sizeof out) return "(too much output)";
memcpy(out + olen, shown, shown_len);
olen += shown_len;
out[olen] = 0;
if (ended == 0) return "(still running)";
if (ended == 2) break;
line_open = 0;
tail = v4_line_status(&n, &img) == V4_LINE_COMPLETED ? " ok\nok> "
: v4_line_status(&n, &img) == V4_LINE_ERROR ? " ERROR\nok> " : "ok> ";
tail = text_ended == V4_TEXT_COMPLETED ? " ok\nok> " : text_ended == V4_TEXT_ERROR ? " ERROR\nok> " : "ok> ";
memcpy(out + olen, tail, strlen(tail) + 1);
olen += (unsigned)strlen(tail);
}
@@ -739,12 +780,7 @@ int main(void)
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16);
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
img.line = v4_text_word(&tx, "(LINE)");
img.idle = v4_text_word(&tx, "(IDLE)");
img.line_status = LINE_STATUS;
img.tib = TIB;
img.tib_bytes = 1025u;
img.span = SPAN;
w_idle = v4_text_word(&tx, "(IDLE)");
w_key = v4_text_word(&tx, "KEY");
w_key_end = v4_text_word(&tx, "CR"); /* the word after KEY in core.v4 */
w_fault = v4_text_word(&tx, "(FAULTS)");
@@ -753,11 +789,12 @@ int main(void)
/* ---- switch-on ---- */
boot();
CHECK(v4_line_done(&n, &img) && n.console_len == 0, "the node is idle and has printed nothing: the prompt is its host's");
CHECK(run_line(5000) == 1 && last_steps == 0, "and goes on waiting to be handed a line");
CHECK(node_idle() && n.mem[OUT_PTR] == OUT_W, "the node is waiting at its ports and has printed nothing: the prompt is its console's");
{ v4_uheat_t clock = es.anticlock;
CHECK(run_line(5000) == 0 && es.anticlock == clock, "and goes on waiting, executing nothing"); }
CHECK(canary_under_expect(), "with the data stack as it found it");
(void)v4_exec_step_word(&n, &es, &h);
CHECK(v4_line_done(&n, &img), "it stays idle however long it is run");
CHECK(node_idle(), "it stays waiting however long it is run");
/* ---- the sessions ---- */
for (i = 0; i < NSCRIPT; i++) {
@@ -883,7 +920,7 @@ int main(void)
memcpy(line, "65 EMIT", 7);
line[1025] = '\n'; line[1026] = 0;
CHECK(is(say(line), "(line too long)"), "one of 1025 is not taken: the node's input buffer holds a block");
CHECK(v4_line_done(&n, &img) && n.console_len == 0, "and nothing of it was run");
CHECK(node_idle() && n.mem[OUT_PTR] == OUT_W, "and nothing of it was sent");
}
/* ---- ABORT and QUIT from deep in a programme, again and again ---- */
@@ -1247,7 +1284,7 @@ int main(void)
boot_bare();
CHECK(load_source("tools.fth"), "tools.fth compiles, every line of it");
CHECK(is(say(": SQ DUP * ; SEE SQ\n"), ": SQ\n dup call * \n ; \n ok\nok> "), "a definition: an in-line word, a call by name, the return");
CHECK(is(say("SEE DUP\n"), ": DUP\n dup ; \n ok\nok> ") && is(say("SEE EMIT\n"), out) && strncmp(out, ": EMIT\n @p ", 12) == 0 && strstr(out, " b! !b ; \n"),
CHECK(is(say("SEE DUP\n"), ": DUP\n dup ; \n ok\nok> ") && is(say("SEE EMIT\n"), out) && strncmp(out, ": EMIT\n @p ", 12) == 0 && strstr(out, " b! @b \n if ") && strstr(out, " pop a! ; \n"),
"the capsule's own words; a literal's value follows its @p");
CHECK(is(say("VARIABLE V 7 V ! SEE V\n"), ": V\n data: 7 \n ok\nok> ") && is(say("5 CONSTANT K SEE K\n"), ": K\n data: 5 \n ok\nok> "), "a variable and a constant show what they hold");
CHECK(is(say(": IM 1 ; IMMEDIATE SEE IM\n"), ": IM\n @p 1 ; \nIMMEDIATE\n ok\nok> "), "an immediate word says so");
+2 -2
View File
@@ -67,9 +67,9 @@ int main(void)
len = strlen(line);
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 (how == V4_TEXT_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_TEXT_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; }
else if (how == V4_BOOT_LINE_STOPPED) { fflush(stdout); fprintf(stderr, "starforth4: the node stopped on a fault\n"); return 1; }
+4 -5
View File
@@ -52,7 +52,7 @@ int main(int argc, char **argv)
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
FILE *out;
v4_cell k, line, idle, faults, voc;
v4_cell k, idle, faults, voc;
unsigned count = 0;
if (argc != 3) die("usage: mkimage DESCRIPTION.c CAPSULE.f18", NULL);
@@ -60,7 +60,6 @@ int main(int argc, char **argv)
if (!host_load(&tx, &n, files, (unsigned)(sizeof files / sizeof files[0]))) die("the capsule does not assemble", NULL);
if (!v4_text_finish(&tx)) die("not everything is defined", v4_text_error(&tx));
if (v4_text_here(&tx) >= DICT_W) die("the capsule's code runs into the dictionary space", NULL);
line = v4_text_word(&tx, "(LINE)");
idle = v4_text_word(&tx, "(IDLE)");
faults = v4_text_word(&tx, "(FAULTS)");
w_key = v4_text_word(&tx, "KEY");
@@ -99,7 +98,8 @@ int main(int argc, char **argv)
for (voc = n.mem[VOC_LINK]; voc != 0; voc = n.mem[voc + 1]) clear_acl(n.mem[voc]);
if (!v4_node_guards_intact(&n)) die("the node's guards are damaged", NULL);
n.mem[LINE_STATUS] = V4_LINE_COMPLETED; /* idle, with nothing to report */
n.mem[LINE_STATUS] = 1; /* nothing has ended badly */
n.mem[OUT_PTR] = OUT_W; /* nothing has been printed */
/* the nucleus as a capsule of F18 code: the words a neighbour writes to
* an empty node's port, each in V4_CELL_BITS / 8 bytes, low byte first */
@@ -113,8 +113,7 @@ int main(int argc, char **argv)
fprintf(out, "/* Generated by v4/tools/mkimage.c from v4/capsule -- do not edit. */\n#include \"v4/image.h\"\n\n");
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, %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,\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,\n %ld, %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,
(long)BOOT_CELLS, (long)FENCE);