Real per-VM log persistence: source attribution + ACL pin (FABRIC-3.md §XXVII)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s

Wires the previously-unused vm_log_attributed_vm() into LOG-APPEND's kernel
primitive so persisted log records carry a trustworthy source (the real
attributed VM's registry name, or "HADES" pseudo-source) instead of a
caller-supplied, trivially forgeable string. Drops src-addr/src-u from
LOG-APPEND's stack signature accordingly. Pins LOG-APPEND via bare ACL-PIN
in Artemis's own init.4th, matching BIRTH/CAPSULE-BIRTH's precedent for a
privileged word that can't reach the shared, host-portable ACL.4th.

Also fixes two console-banner nitpicks: a mis-rendering em dash (U+2014)
in the boot banner, and drops "Emergency" from the CLI banner text.

Doc corrections to artemis_sig.h/zuse_eligibility_list.h reconciling the
three fixed devblock ranges now in play. LOG-FLUSH (the intended normal
entry point) and level-aware log eviction remain open, flagged not fixed.

Re-verified clean boot to ok> on all 3 architectures after every change.
riscv64 showed one new, unrelated virtio_blk write-timeout anomaly during
Artemis's early physics self-test (self-recovered, boot unaffected,
sector doesn't map to the log region) -- flagged, not investigated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
This commit is contained in:
Robert Allan James
2026-09-13 08:56:33 -04:00
co-authored by Claude Sonnet 5
parent 61755fde78
commit 2a30212bd3
28 changed files with 63928 additions and 2051 deletions
+73
View File
@@ -3394,3 +3394,76 @@ investigated further this session -- flagged here for a deliberate decision (ret
`VM-NAMES-INIT` to the `~user` identities and re-run, or confirm the console-proxy target was
actually fine for what this campaign measures) rather than assumed either way.
## XXVII. §XXVI follow-on Step 4: real per-VM log persistence -- source attribution wired,
LOG-APPEND pinned, two console nitpicks fixed, all 3 archs re-accepted (2026-09-13)
**Built this step (present on disk at session resume, after a power failure interrupted the
prior session mid-verification): a growable, ring-buffered, disk-backed log region for
Artemis.** `src/starkernel/log_region.c` + `include/starkernel/log_region.h` -- a
CRC-guarded control header at a fixed `devblock_from_top` constant
(`LOG_REGION_DEVBLOCK_FROM_TOP_BASE`), slot devblocks growing from 4 up to a 32-devblock
ceiling (`LOG_REGION_GROWTH_INCREMENT`/`LOG_REGION_MAX_DEVBLOCKS`), FIFO eviction of the
oldest record once the ceiling is reached. Reached from FORTH via `LOG-APPEND`
(`capsules/artemis/init.4th`, Block 4860), a thin wrapper around the kernel-only
`(LOG-APPEND-RAW)` primitive (`log_words.c`). `include/starkernel/artemis_sig.h` and
`include/starkernel/zuse_eligibility_list.h` were corrected in the same commit to document the
three fixed devblock ranges now in play (eligibility list: 1-63, `artemis_sig_t`: 64, log
region: 65-96) after a fixed-offset collision was caught and fixed earlier this session
(`61755fd`, genesis-stamp/BAM offset) -- the lesson from that bug (state the real bound in
writing, don't trust "it'll never get that big") applied here too.
**Two policy gaps were identified and closed before this was called done, both raised by the
user directly rather than found and silently fixed.** First: `vm_log_attributed_vm()`
(`log_attrib.h`/`vm_core.c` -- save/restore of "which VM is genuinely executing" across nested
`vm_interpret()` calls, for exactly this attribution purpose) had been built but never called
anywhere -- confirmed by grep, only its own definition and declaration matched. Meanwhile
`(LOG-APPEND-RAW)` took a `source-addr`/`source-u` pair straight off the FORTH stack, meaning
any caller could claim to be any source; trivially forgeable. Fixed: the stack signature
dropped `src-addr`/`src-u` entirely (`LOG-APPEND ( level ts msg-addr msg-u -- )`, down from six
stack args to four); `log_word_append_raw()` now calls `vm_log_attributed_vm()`, looks the
returned VM up by `stadium_vm_id` via `capsule_vm_registry_get()` for its registry `name`, and
falls back to the fixed `"HADES"` pseudo-source (matching `log_attrib.h`'s own documented
convention) when no VM is attributed. Second: `LOG-APPEND` had no ACL pinning at all, unlike
`BIRTH`/`CAPSULE-BIRTH` (both pinned STRICT in `kernel_main.c` after Mama's own birth). Fixed
with `' LOG-APPEND ACL-PIN` appended directly in `capsules/artemis/init.4th`'s own Block 4860 --
*not* `ACL-STRICT` first, because that FORTH policy word is defined by `ACL.4th`
(Block 4002), which only Hera's `init.4th` loads; Artemis never runs `S" ACL.4th" EXEC`, so
`ACL-STRICT` does not exist in her dictionary at all, only the bare `ACL-PIN` C primitive
(registered for every VM at bootstrap, `register_acl_words()`) is safely reachable here. Same
reasoning `ACL-BOOT`'s own comment already gives for excluding `BIRTH`/`CAPSULE-BIRTH` from
`ACL.4th` -- a kernel-only-or-VM-local privileged word gets pinned locally, not folded into the
shared/host-portable policy file.
**Still open, flagged not fixed, per this project's report-don't-fix-unprompted discipline:**
`LOG-FLUSH` -- the word both `init.4th`'s and `log_region.c`'s own doc comments describe as the
normal way `LOG-APPEND` gets reached ("dispatched by a sender's `LOG-FLUSH` via a genuine
`MSG-SEND`/`MSG-TICK` round trip") -- does not exist anywhere in the tree; grep for it matches
only those two comments. The only live entry points right now are the raw primitive itself or a
manual console call. And eviction is unconditional FIFO with no level awareness -- a flood of
DEBUG/INFO noise (already flagged separately as needing cleanup, see the production-logging
memory) could evict real ERROR/WARN history once the 32-devblock ceiling is reached. Both are
real gaps in what "real per-VM log persistence" should eventually mean, not attempted this
session.
**Two unrelated console-banner nitpicks, fixed on the same pass at the user's request:** the
boot banner's em dash (`U+2014`, multi-byte UTF-8) rendered as garbled `â??` on the framebuffer
console font (`repl.c`, `sk_repl_run()`) -- replaced with a plain space, no dash at all, per the
user's supplied before/after screenshot. And `"StarForth Emergency CLI"` shortened to
`"StarForth CLI"` (same line) -- "Emergency" dropped from the banner text itself; the doc-comment
file headers in `repl.c`/`repl.h` describing the REPL's own purpose were deliberately left alone,
out of scope for a UI-text request.
**Acceptance: all 3 architectures re-verified clean after every fix above, one at a time,
foreground, QMP `quit` once each reached its prompt (never a background `&`/`run_in_background`,
per this project's hard rule -- the Bash tool's own timeout-driven auto-backgrounding on a
still-running interactive QEMU process was flagged separately as harness feedback, not a
workaround taken here).** amd64: clean build, boot to `zuse@Hera ok>`, both banner fixes visible
correctly in the log. aarch64: same, clean. riscv64: same, clean, **with one new anomaly not
seen on the other two arches** -- a single `virtio_blk: vblk_io timed out (write=1 sector=3040)`
error during Artemis's own early physics self-test (not log-region I/O; sector 3040 does not map
to the log region's top-of-disk devblock range). Boot self-recovered and continued to `ok>`
without further incident. Not investigated further -- flagged here, consistent with this
project's known pattern of occasional riscv64/TCG timing flakiness (see the unexplained-SIGTERM
and aarch64 Stadium/COOL-scaling entries elsewhere in this doc), not assumed to be caused by
this session's changes given the sector mismatch, but also not confirmed benign.
+4 -2
View File
@@ -457,7 +457,8 @@ LOADER_SRCS_BASE := \
$(KERNEL_SRC)/doe_log.c \
$(KERNEL_SRC)/heartbeat.c \
$(KERNEL_SRC)/homeblocks_sig.c \
$(KERNEL_SRC)/artemis_sig.c
$(KERNEL_SRC)/artemis_sig.c \
$(KERNEL_SRC)/log_region.c
LOADER_ASM := \
$(KERNEL_SRC)/arch/$(ARCH)/boot.S \
@@ -513,7 +514,8 @@ KERNEL_SRCS_BASE := \
$(KERNEL_SRC)/doe_log.c \
$(KERNEL_SRC)/heartbeat.c \
$(KERNEL_SRC)/homeblocks_sig.c \
$(KERNEL_SRC)/artemis_sig.c
$(KERNEL_SRC)/artemis_sig.c \
$(KERNEL_SRC)/log_region.c
KERNEL_ASM := $(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.S)
+55 -54
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-13T11:29:27Z -->
<!-- Generated by mkcapsule --manifest 2026-09-13T12:53:27Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -10,7 +10,7 @@
|---------|----------------|----------|--------|
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4015 | `0xd781d22148ff171d` | yes |
| `acl-std79.4th` | 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048 | `0x773bf9209df191d1` | yes |
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858 | `0xac215ecb873c4db3` | yes |
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4860 | `0xf55edc4e76a8c294` | yes |
| `block-acl.4th` | 4019, 4020 | `0xf6cc2a59e3a6734e` | yes |
| `common:messaging.4th` | 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042 | `0xedde53f6faed7355` | yes |
| `common:msg.4th` | 4055 | `0x850a0382344ea6c4` | yes |
@@ -148,52 +148,52 @@
| 4063 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4064 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4065 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4110 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4111 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4112 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4113 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4122 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4123 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4124 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4125 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4126 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4127 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4128 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4129 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4130 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4131 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4132 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4133 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4134 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4135 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4136 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4137 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4138 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4139 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4140 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4141 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4110 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4111 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4112 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4113 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4122 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4123 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4124 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4125 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4126 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4127 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4128 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4129 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4130 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4131 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4132 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4133 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4134 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4135 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4136 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4137 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4138 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4139 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4140 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4141 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4153 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
| 4160 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4161 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4162 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4163 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4164 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4165 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4166 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4167 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4168 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4169 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4170 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4171 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4172 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4173 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4174 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4177 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4178 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4179 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4180 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4181 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4182 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4160 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4161 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4162 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4163 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4164 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4165 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4166 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4167 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4168 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4169 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4170 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4171 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4172 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4173 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4174 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4177 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4178 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4179 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4180 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4181 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4182 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4200 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
| 4201 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
| 4202 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
@@ -233,14 +233,15 @@
| 4840 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4841 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4842 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4851 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4852 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4853 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4854 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4851 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4852 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4853 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4854 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4855 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
| 4856 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4857 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4858 | `artemis:init.4th` | `0xac215ecb873c4db3` | ok |
| 4856 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4857 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4858 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4860 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4900 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 4901 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 4902 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
+15
View File
@@ -537,3 +537,18 @@ Block 4858
S" BLK-ATTACH-ACK" ACK-APPEND
BLK-ATTACH-EVENT 2 0 ATTACH-ACK-BUF
ATTACH-ACK-LEN @ 0 MSG-SEND ;
Block 4860
( LOG-APPEND ( level ts msg-addr msg-u -- ) )
( FABRIC-3.md SXXVI follow-on Step 4, 2026-09-13: real per-VM )
( log persistence, reached via a sender's LOG-FLUSH/MSG-SEND )
( round trip, or by hand at the console for a smoke test. )
( Thin wrapper -- kernel-only (LOG-APPEND-RAW) (log_words.c/ )
( log_region.c) does the real disk write; never calls )
( log_message() (see log_region.c's own doc comment on why). )
( CORRECTION, same day: dropped src-addr/src-u, forgeable -- )
( source now comes from vm_log_attributed_vm() kernel-side. )
( Pinned: writes straight to disk, bypasses log_message(). )
: LOG-APPEND ( level ts msg-addr msg-u -- )
(LOG-APPEND-RAW) ;
' LOG-APPEND ACL-PIN
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+21 -9
View File
@@ -151,15 +151,27 @@ typedef struct {
* boundary" convention homeblocks_sig_t
* uses for an identity's own pool. */
uint32_t log_region_offset; /* Devblock offset where the growable
* per-VM log-persistence region starts;
* 0 = not yet allocated. */
uint32_t log_region_devblocks; /* Current reserved size of the log
* region, in devblocks -- grows over
* time (same growable-reservation
* mechanism the metadata fence design
* already uses elsewhere), not a single
* fixed guess made once at genesis. */
uint32_t log_region_offset; /* CORRECTION, Step 4 (log_region.h,
* 2026-09-13): stays 0 -- informational
* field only, never written or read by
* the real implementation. The log
* region ended up at a fixed, compile-
* time devblock_from_top constant
* (LOG_REGION_DEVBLOCK_FROM_TOP_BASE,
* log_region.h) reached through
* blk_meta_zone_*(), whose own control
* header (log_region_ctrl_t) is the one
* authoritative source of the region's
* live offset/size/head/tail -- a second
* writer of the same fact here would be
* unnecessary drift risk, not a useful
* summary. Left at 0/reserved rather
* than deleted, in case a real second
* reader (a host-side offline tool that
* can't run blk_meta_zone_*() at all)
* ever needs it. */
uint32_t log_region_devblocks; /* See log_region_offset above -- same
* reasoning, stays 0. */
uint64_t hdr_crc; /* Computed over every field above this
* one, same boundary/discipline as
+64
View File
@@ -0,0 +1,64 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* log_attrib.h - "Which VM is currently executing" for log persistence
* attribution (FABRIC-3.md §XXVI follow-on, Step 4, 2026-09-13)
*
* log_message() (shim.c) is a global function called from hundreds of
* existing sites -- most with no VM in scope at all (boot, PCI, xHCI,
* driver code). Rather than thread a VM parameter through every one of
* those call sites (a large, invasive change for no benefit to the ones
* that genuinely have no VM), vm_interpret() (vm_core.c, the single
* dispatch entry point for ALL FORTH execution -- interactive lines,
* LOAD'd block content, and every VM-EXEC/MSG-DELIVER dispatch into a
* target VM) sets this at entry and restores the previous value at exit,
* save/restore style so nested vm_interpret() calls (VM-EXEC dispatching
* into a different VM's own dictionary, mid-interpret) attribute
* correctly to whichever VM is actually running at the moment
* log_message() fires -- not the outermost caller.
*
* NULL means "no VM is currently interpreting" -- boot sequence, PCI/
* xHCI/driver code, anything running outside a vm_interpret() call frame.
* log_message()'s own persistence hook (shim.c) treats NULL as the fixed
* "HADES" pseudo-source, matching the console's own existing
* "[HADES][LEVEL]" prefix convention for exactly this class of message.
*/
#ifndef STARKERNEL_LOG_ATTRIB_H
#define STARKERNEL_LOG_ATTRIB_H
#ifdef __cplusplus
extern "C" {
#endif
struct VM;
/* vm_log_attributed_vm - The VM whose dictionary context is currently
* executing, or NULL if none (see this header's own doc comment). */
struct VM *vm_log_attributed_vm(void);
#ifdef __cplusplus
}
#endif
#endif /* STARKERNEL_LOG_ATTRIB_H */
+177
View File
@@ -0,0 +1,177 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* log_region.h - Growable per-VM log-persistence ring on Artemis's own disk
* (FABRIC-3.md §XXVI follow-on, Step 4, 2026-09-13)
*
* Records real log_message() output (INFO and above, independent of the
* console's own current_level filter) so a run survives past the QEMU
* serial log -- the motivating case is comparing bare-metal and QEMU
* results directly as a DoE factor once bare-metal boot lands, where the
* only artifact both platforms share is whatever got persisted to disk.
*
* Lives in the SAME top-of-device system-metadata fence artemis_sig_t and
* Zuse's genesis marker/eligibility list already use
* (block_subsystem.h's blk_meta_zone_read()/write(), devblock_from_top
* addressing) -- reached the SAME way, deliberately NOT through
* artemis_sig.c's own independent blkio_info()-based arithmetic. That
* arithmetic exists only because artemis_sig_t must be discoverable on a
* device that isn't attached yet (bus-agnostic discovery, repl.c's
* idle-loop USB-MSC scan); this ring is only ever read or written once
* Artemis's disk is already attached and formatted (LOG-APPEND runs
* inside Artemis's own already-live dictionary context, dispatched by a
* sender's MSG-SEND/MSG-TICK), so blk_meta_zone_*() -- which requires
* exactly that already-attached state -- is the correct, simpler,
* already-tested tool, not a limitation to work around.
*
* Fixed devblock_from_top allocation (NOT persisted in artemis_sig_t's own
* log_region_offset/log_region_devblocks fields -- those stay at their
* genesis-time value of 0/0, "informational only, control header below is
* authoritative," documented in artemis_sig.h; two writers of the same
* fact was rejected as unnecessary drift risk):
* 64 -- artemis_sig_t itself (ARTEMIS_SIG_DEVBLOCK_FROM_TOP)
* 65 -- this ring's control header (LOG_REGION_DEVBLOCK_FROM_TOP_BASE)
* 66-96 -- this ring's slot devblocks, growable up to LOG_REGION_MAX_DEVBLOCKS
* Chosen clear of Zuse's genesis marker (devblock_from_top=0) and
* eligibility list (chains upward from 1, unbounded in code -- see
* zuse_eligibility_list.h's own CORRECTION comment, updated alongside this
* file, for the honest real-world headroom that leaves: devblocks 1-63,
* ~8000 possible eligible identities before ever reaching 64).
*
* Ring granularity is one whole devblock-quarter (LOG_SLOT_SIZE, 1024
* bytes = exactly one blkio forth-block) per record -- avoids any
* partial-forth-block read-modify-write for the write itself (a slot
* write is one aligned blkio_write()-equivalent-sized unit); the
* surrounding devblock (4 slots) still needs a read-modify-write via
* blk_meta_zone_read()/write() since that accessor's own unit is one full
* 4 KiB devblock, but that cost is the same regardless of slot size.
* Records are truncated to fit one slot rather than spanning multiple --
* simple, real, and sufficient for what's actually persisted (see
* vm_log_buffer.h's own tighter caps, chosen to fit a whole batch of
* several records inside one VM-EXEC/MSG-SEND line, INPUT_BUFFER_SIZE=1025).
*/
#ifndef STARKERNEL_LOG_REGION_H
#define STARKERNEL_LOG_REGION_H
#include <stdint.h>
#include "starkernel/artemis_sig.h" /* ARTEMIS_SIG_DEVBLOCK_FROM_TOP */
#ifdef __cplusplus
extern "C" {
#endif
/*===========================================================================
* Fence allocation
*===========================================================================*/
#define LOG_REGION_DEVBLOCK_FROM_TOP_BASE (ARTEMIS_SIG_DEVBLOCK_FROM_TOP + 1u) /* 65 */
#define LOG_REGION_INITIAL_DEVBLOCKS 4u /* slot devblocks at first use, excludes control header */
#define LOG_REGION_GROWTH_INCREMENT 4u
#define LOG_REGION_MAX_DEVBLOCKS 32u /* ceiling -- devblock_from_top stays within [66,96] */
#define LOG_SLOTS_PER_DEVBLOCK 4u /* 4 x 1 KiB forth-blocks per 4 KiB devblock */
#define LOG_SLOT_SIZE 1024u
/*===========================================================================
* log_region_ctrl_t - ring control header, one devblock at
* LOG_REGION_DEVBLOCK_FROM_TOP_BASE. head_slot/tail_slot/record_count are
* the authoritative, live ring state -- nothing outside this header (not
* even artemis_sig_t) needs to track it.
*===========================================================================*/
#define LOG_REGION_MAGIC 0x474C474Cull /* 'LGLG' */
#define LOG_REGION_VERSION_0 0
#define LOG_REGION_PACK(ver) \
(LOG_REGION_MAGIC | ((uint64_t)(ver) << 32))
#define LOG_REGION_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFull))
#define LOG_REGION_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
typedef struct {
uint64_t magic; /* LOG_REGION_PACK(...) */
uint32_t devblocks; /* current slot-area size, in devblocks (excludes this header) */
uint32_t head_slot; /* index of the oldest live record */
uint32_t tail_slot; /* index where the NEXT record will be written */
uint32_t record_count; /* live records, <= devblocks * LOG_SLOTS_PER_DEVBLOCK */
uint64_t hdr_crc; /* covers every field above this one */
uint8_t _pad[4096 - (8 + 4 + 4 + 4 + 4 + 8)];
} log_region_ctrl_t;
typedef char log_region_ctrl_size_check[(sizeof(log_region_ctrl_t) == 4096) ? 1 : -1];
/*===========================================================================
* log_slot_t - one record, exactly one 1 KiB forth-block.
*===========================================================================*/
#define LOG_SLOT_SOURCE_MAX 16u /* NUL-padded VM/source tag, e.g. "Hera", "HADES" */
#define LOG_SLOT_MSG_MAX (LOG_SLOT_SIZE - 8u - 2u - 1u - LOG_SLOT_SOURCE_MAX) /* 997 */
/* Field order deliberate: uint64_t, uint16_t, uint8_t, then char arrays --
* every fixed field lands on its natural alignment with zero compiler-
* inserted padding (offsets 0, 8, 10, 11), so sizeof() == the hand-summed
* byte count the static assert below checks, and LOG_SLOT_SIZE (1024,
* already a multiple of 8) needs no trailing padding either. */
typedef struct {
uint64_t timestamp; /* shim.c's own KRELTSC-style relative tick */
uint16_t msg_len; /* used length of msg[], <= LOG_SLOT_MSG_MAX (997, needs 16 bits) */
uint8_t level; /* LogLevel */
char source[LOG_SLOT_SOURCE_MAX];
char msg[LOG_SLOT_MSG_MAX];
} log_slot_t;
typedef char log_slot_size_check[(sizeof(log_slot_t) == LOG_SLOT_SIZE) ? 1 : -1];
/*===========================================================================
* API
*===========================================================================*/
/*
* log_region_append - Write one record to the ring, growing it (within
* LOG_REGION_MAX_DEVBLOCKS) or evicting the oldest record (ring full and
* already at the growth ceiling) as needed. Initializes the ring on first
* use (control header blank). Never calls log_message() or anything that
* might (this runs inside Artemis's own dictionary context during message
* delivery -- see this header's own note on why; a log call here could
* recurse into this same append path via Artemis's own buffered flush).
*
* @param level LogLevel of this record.
* @param timestamp Caller-supplied relative timestamp (same KRELTSC base
* shim.c's own log_message() uses).
* @param source VM/source tag, e.g. "Hera", "HADES", an identity name.
* @param source_len Length of source (truncated to LOG_SLOT_SOURCE_MAX-1).
* @param msg Message text (not NUL-terminated required).
* @param msg_len Length of msg (truncated to LOG_SLOT_MSG_MAX).
* @return 0 on success, -1 on any read/write failure (ring left however
* the failed operation left it -- blk_meta_zone_write() itself
* never partially writes a devblock).
*/
int log_region_append(uint8_t level, uint64_t timestamp,
const char *source, uint32_t source_len,
const char *msg, uint32_t msg_len);
#ifdef __cplusplus
}
#endif
#endif /* STARKERNEL_LOG_REGION_H */
+20 -4
View File
@@ -16,10 +16,26 @@
* "this is the last devblock in the chain") -- entries are appended by
* filling the current tail devblock, then chaining a fresh one out of the
* already-reserved BLK_META_FENCE_INIT budget once it's full. No fence
* growth logic is needed yet: only 2 of the 128 pre-reserved devblocks
* are spoken for (0 = genesis marker, 1 = this list's head) and the
* chain has 126 more slots to grow into before that budget itself would
* need extending.
* growth logic is needed yet.
*
* CORRECTION (FABRIC-3.md SXXVI follow-on, Step 4, 2026-09-13): the fence
* is no longer this list's alone to grow into. artemis_sig_t now owns
* devblock_from_top=64 (include/starkernel/artemis_sig.h), and the
* per-VM log-persistence ring now owns devblock_from_top 65-96
* (include/starkernel/log_region.h) -- both fixed, compile-time
* constants, chosen deliberately clear of this list's own growth
* direction (chaining upward from devblock_from_top=1). This list's real
* remaining headroom is devblocks 1-63 (not "126 more slots" as this
* comment used to say): 63 devblocks x ZUSE_ELIGIBILITY_ENTRIES_PER_DEVBLOCK
* (127) = 8001 possible eligible identities before ever reaching
* devblock_from_top=64 -- vastly beyond any plausible real deployment of
* this project, but genuinely unenforced: there is no code-level ceiling
* stopping this chain from growing into devblock 64+ if that number were
* ever actually approached. Noted here, not silently assumed safe, after
* a fixed-offset collision was caught and fixed once already this session
* (Artemis's own disk vs. its live BAM, artemis_sig.h's own CORRECTION
* comment) -- the lesson being to state the real bound in writing rather
* than trust "it'll never get that big."
*
* Raw, unpacked 4 KiB devblock -- same convention as
* zuse_genesis_marker_t/homeblocks_sig_t: real CRC from day one, this
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* log_region.c - Growable per-VM log-persistence ring on Artemis's disk.
* See starkernel/log_region.h for the format/interface design.
*
* Deliberately never calls log_message() (or anything documented to call
* it) -- this runs inside Artemis's own dictionary context during message
* delivery (LOG-APPEND, dispatched by a sender's MSG-SEND/MSG-TICK); a log
* call here would append to Artemis's own buffer and eventually flush back
* through this same path.
*/
#include "starkernel/log_region.h"
#include <stddef.h>
#include <string.h>
#include "block_subsystem.h" /* blk_meta_zone_read/write, compute_crc64 */
static uint64_t log_region_ctrl_compute_crc(const log_region_ctrl_t *ctrl) {
size_t crc_span = offsetof(log_region_ctrl_t, hdr_crc);
return compute_crc64((const uint8_t *)ctrl, crc_span);
}
/* Reads the control header; returns 0 and *out populated only if magic,
* version, and CRC all check out. Any other result (blank, foreign,
* corrupt, or a read failure) is treated identically by the caller: "not
* yet initialized," matching genesis_marker_read()'s own discipline. */
static int log_region_ctrl_read(log_region_ctrl_t *out) {
if (blk_meta_zone_read(LOG_REGION_DEVBLOCK_FROM_TOP_BASE, (uint8_t *)out) != 0) return -1;
if (LOG_REGION_GET_MAGIC(out->magic) != (uint32_t)(LOG_REGION_MAGIC & 0xFFFFFFFFull)) return -1;
if (LOG_REGION_GET_VERSION(out->magic) != LOG_REGION_VERSION_0) return -1;
uint64_t want_crc = log_region_ctrl_compute_crc(out);
if (want_crc != out->hdr_crc) return -1;
return 0;
}
static int log_region_ctrl_write(log_region_ctrl_t *ctrl) {
ctrl->hdr_crc = log_region_ctrl_compute_crc(ctrl);
return blk_meta_zone_write(LOG_REGION_DEVBLOCK_FROM_TOP_BASE, (const uint8_t *)ctrl) == 0 ? 0 : -1;
}
static void log_region_ctrl_init_fresh(log_region_ctrl_t *ctrl) {
memset(ctrl, 0, sizeof(*ctrl));
ctrl->magic = LOG_REGION_PACK(LOG_REGION_VERSION_0);
ctrl->devblocks = LOG_REGION_INITIAL_DEVBLOCKS;
ctrl->head_slot = 0;
ctrl->tail_slot = 0;
ctrl->record_count = 0;
}
/* Reads/writes the devblock holding slot_index (LOG_SLOTS_PER_DEVBLOCK
* slots per devblock), one devblock_from_top past the control header for
* every LOG_SLOTS_PER_DEVBLOCK slots. */
static uint32_t log_region_slot_devblock_from_top(uint32_t slot_index) {
return LOG_REGION_DEVBLOCK_FROM_TOP_BASE + 1u + (slot_index / LOG_SLOTS_PER_DEVBLOCK);
}
static int log_region_write_slot(uint32_t slot_index, const log_slot_t *slot) {
uint8_t devblock_buf[4096];
uint32_t dft = log_region_slot_devblock_from_top(slot_index);
uint32_t slot_in_devblock = slot_index % LOG_SLOTS_PER_DEVBLOCK;
/* Read-modify-write: blk_meta_zone_*() only operates at whole-devblock
* granularity, and a devblock holds LOG_SLOTS_PER_DEVBLOCK slots, so a
* single-slot write must preserve its siblings. A read failure on a
* never-yet-written devblock (blank fence content) is not fatal here --
* proceed with a zeroed buffer, matching zuse_eligibility.c's own
* "blank fence content is indistinguishable from absent, treat as
* absent" discipline. */
if (blk_meta_zone_read(dft, devblock_buf) != 0) {
memset(devblock_buf, 0, sizeof(devblock_buf));
}
memcpy(devblock_buf + (size_t)slot_in_devblock * LOG_SLOT_SIZE, slot, sizeof(*slot));
return blk_meta_zone_write(dft, devblock_buf) == 0 ? 0 : -1;
}
int log_region_append(uint8_t level, uint64_t timestamp,
const char *source, uint32_t source_len,
const char *msg, uint32_t msg_len) {
log_region_ctrl_t ctrl;
if (log_region_ctrl_read(&ctrl) != 0) {
log_region_ctrl_init_fresh(&ctrl);
}
uint32_t total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
if (ctrl.record_count >= total_slots) {
if (ctrl.devblocks < LOG_REGION_MAX_DEVBLOCKS) {
/* Grow: new slots appear at indices [old_total_slots ..
* new_total_slots-1], nothing already written moves -- safe
* to do at any point in the ring's lifecycle regardless of
* wrap state, since head/tail are slot INDICES modulo
* total_slots, and the modulus is simply larger from here on. */
ctrl.devblocks += LOG_REGION_GROWTH_INCREMENT;
if (ctrl.devblocks > LOG_REGION_MAX_DEVBLOCKS) ctrl.devblocks = LOG_REGION_MAX_DEVBLOCKS;
total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
}
if (ctrl.record_count >= total_slots) {
/* Still full (growth ceiling already reached) -- evict oldest. */
ctrl.head_slot = (ctrl.head_slot + 1u) % total_slots;
ctrl.record_count--;
}
}
log_slot_t slot;
memset(&slot, 0, sizeof(slot));
slot.timestamp = timestamp;
slot.level = level;
uint32_t src_n = source_len;
if (src_n > LOG_SLOT_SOURCE_MAX) src_n = LOG_SLOT_SOURCE_MAX;
memcpy(slot.source, source, src_n);
uint32_t msg_n = msg_len;
if (msg_n > LOG_SLOT_MSG_MAX) msg_n = LOG_SLOT_MSG_MAX;
memcpy(slot.msg, msg, msg_n);
slot.msg_len = (uint16_t)msg_n;
if (log_region_write_slot(ctrl.tail_slot, &slot) != 0) return -1;
ctrl.tail_slot = (ctrl.tail_slot + 1u) % total_slots;
if (ctrl.record_count < total_slots) ctrl.record_count++;
return log_region_ctrl_write(&ctrl);
}
+2 -2
View File
@@ -1492,8 +1492,8 @@ void sk_repl(VM *vm)
console_println(lithos_version);
console_puts("StarForth Version "); console_println(STARFORTH_VERSION);
console_println("");
console_println("StarForth Emergency CLI");
console_println("FORTH-79 interpreter — type BYE or power off to exit");
console_println("StarForth CLI");
console_println("FORTH-79 interpreter type BYE or power off to exit");
console_println("");
sk_repl_run(vm);
+16
View File
@@ -56,6 +56,7 @@
#include "../include/vm_host.h"
#ifdef __STARKERNEL__
#include "starkernel/vm/arena.h"
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
#include "starkernel/console.h" /* g_sk_fault_word */
#include "platform_alloc.h" /* sf_free for call_stack */
#include "starkernel/vm/stadium_words.h" /* item 4.1: word patrons on the Stadium */
@@ -1029,11 +1030,24 @@ void vm_interpret_word(VM* vm, const char* word_str, size_t len)
* @param vm Pointer to VM instance
* @param input String containing Forth code to interpret
*/
/* FABRIC-3.md §XXVI follow-on, Step 4 (log_attrib.h): which VM's dictionary
* context is currently executing, for log persistence attribution. Save/
* restore (not a plain set) so a nested vm_interpret() call -- VM-EXEC
* dispatching into a different VM's own dictionary, mid-interpret --
* attributes to whichever VM is actually running when it returns to the
* outer frame, not silently to the inner one. */
static VM *g_log_attrib_vm; /* zero-initialized (BSS) */
VM *vm_log_attributed_vm(void) { return g_log_attrib_vm; }
void vm_interpret(VM* vm, const char* input)
{
if (!vm || !input) return;
vm_assert_interpreter_enabled(vm, "vm_interpret");
VM *prev_attrib_vm = g_log_attrib_vm;
g_log_attrib_vm = vm;
/* Load into input buffer (cap + NUL) */
size_t n = 0, cap = INPUT_BUFFER_SIZE ? INPUT_BUFFER_SIZE - 1 : 0;
while (n < cap)
@@ -1064,6 +1078,8 @@ void vm_interpret(VM* vm, const char* input)
vm->heartbeat.check_counter = 0;
}
}
g_log_attrib_vm = prev_attrib_vm;
}
/* ====================== VM memory helpers ======================= */
+63
View File
@@ -14,6 +14,13 @@
#include "../../include/log.h"
#include <stdint.h>
#ifdef __STARKERNEL__
#include "starkernel/log_region.h"
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get, VMRegistryEntry */
#include <string.h> /* strlen */
#endif
/* ── Level constants ─────────────────────────────────────────────────── */
/** @brief LOG-ERROR ( -- n ) Push the @c LOG_ERROR level constant. */
@@ -188,6 +195,58 @@ static void log_word_test_str (VM *vm) { log_str_emit(vm, LOG_TEST); }
/** @brief LOG-DEBUG-STR ( c-addr u -- ) Log string from stack at DEBUG level. */
static void log_word_debug_str(VM *vm) { log_str_emit(vm, LOG_DEBUG); }
/* ── (LOG-APPEND-RAW) ( level timestamp msg-addr msg-u -- ) ── */
/*
* Kernel-only. Writes one record directly to Artemis's on-disk log ring
* (log_region.c) -- the primitive LOG-APPEND (capsules/artemis/init.4th)
* wraps this with the FORTH-level word name callers actually use.
* Deliberately does NOT call log_message() anywhere in this path (see
* log_region.c's own doc comment on why) -- errors are reported back via
* vm->error, never logged.
*
* Source is NOT a caller-supplied parameter -- an earlier revision took
* source-addr/source-u off the stack, which let any caller claim to be
* any source. It is derived from vm_log_attributed_vm() (log_attrib.h)
* instead: the real VM currently executing, looked up by name via the
* VM registry, or the fixed "HADES" pseudo-source when no VM is attributed
* (matching log_attrib.h's own documented convention).
*/
#ifdef __STARKERNEL__
static void log_word_append_raw(VM *vm)
{
if (vm->dsp < 3) { vm->error = 1; return; }
cell_t msg_u = vm_pop(vm);
cell_t msg_addr = vm_pop(vm);
cell_t timestamp = vm_pop(vm);
cell_t level = vm_pop(vm);
if (msg_u < 0) { vm->error = 1; return; }
if (msg_addr < 0 || (msg_addr + msg_u) > (cell_t)VM_MEMORY_SIZE) { vm->error = 1; return; }
const char *msg = (const char *)&vm->memory[msg_addr];
char source[VM_NAME_MAX];
VM *attrib = vm_log_attributed_vm();
VMRegistryEntry entry;
if (attrib && capsule_vm_registry_get(attrib->stadium_vm_id, &entry) == 0) {
size_t i = 0;
for (; i < sizeof(source) - 1 && entry.name[i]; i++) source[i] = entry.name[i];
source[i] = '\0';
} else {
const char *hades = "HADES";
size_t i = 0;
for (; i < sizeof(source) - 1 && hades[i]; i++) source[i] = hades[i];
source[i] = '\0';
}
if (log_region_append((uint8_t)level, (uint64_t)timestamp,
source, (uint32_t)strlen(source),
msg, (uint32_t)msg_u) != 0) {
vm->error = 1;
}
}
#endif
/* ── Registration ────────────────────────────────────────────────────── */
/**
@@ -233,4 +292,8 @@ void register_log_words(VM *vm)
register_word(vm, "LOG-INFO-STR", log_word_info_str);
register_word(vm, "LOG-TEST-STR", log_word_test_str);
register_word(vm, "LOG-DEBUG-STR", log_word_debug_str);
#ifdef __STARKERNEL__
register_word(vm, "(LOG-APPEND-RAW)", log_word_append_raw);
#endif
}