Files
LithosAnanake/src/starkernel/capsule/mama_forth_words.c
T
Robert Allan JamesandClaude Sonnet 5 9b305a5be7 starkernel: item 3.8 -- VM identifiers as UUID/GUID
Punch list §25 item 3.8 complete. Added after starting item 4.1
surfaced the need to thread a vm_id into stadium_admit()'s new quota
parameter; Captain Bob ruled UUID/GUID rather than keeping the
narrower uint32_t.

New VMUuid type (vm_uuid.h/vm_uuid.c): two uint64_t halves, RFC-4122-
shaped for logging. Not real randomness -- checked directly against
QEMU 10.2.1's actual CPU feature set: amd64 RDRAND and riscv64 Zkr are
both real, available features here; aarch64 has no RNG property on any
CPU model including "max" (verified exhaustively via QMP
query-cpu-model-expansion). Captain Bob ruled a uniform fallback
across all three ISAs rather than a per-architecture split.

Fallback is a deterministic PRNG (splitmix64) seeded from the Mama
capsule's content hash, pre-filling a 16-entry FIFO pool at boot and
refilling with another batch of the same stream when exhausted --
exactly the shape requested. Same capsule booted twice produces the
same id sequence, preserving the dict_hash reproducibility this
session has relied on throughout.

Hera keeps a fixed, reserved all-zero id, not drawn from the pool --
capsule_birth.c uses vm_id == 0 as a load-bearing sentinel in three
places (KILL protection x2, fleet heat-fanout parent-chain
terminator), found by reading before writing any code.

Two real sentinel-collision bugs caught before shipping, same class as
STADIUM_CONTAINS_NONE: vm_uuid_none() (all-ones, not all-zero) for
"not yet assigned"/"no VM" placeholders; confirmed item 3.7's quota
table already used an in_use boolean rather than a vm_id sentinel, so
no second collision was actually possible there -- the dead,
never-referenced STADIUM_QUOTA_SLOT_EMPTY macro was removed.

Blast radius larger than first scoped, flagged mid-work rather than
silently absorbed: capsule_vm_physics.c/.h (the fleet heat-transfer
layer item 2.1 modified earlier this session) has its own vm_id-keyed
node table and walks parent_vm_id chains through the same identity
space, so it needed the same change, plus its callers in
mama_forth_words.c and sk_vm_bootstrap.c.

One live FORTH word contract changed, by explicit ruling: CAPSULE-BIRTH
was ( capsule-id -- vm-id ), a single cell -- can't hold 128 bits.
Captain Bob picked pushing two cells ("there is doubles support in the
FORTH std word set anyway"): ( capsule-id -- vm-id-hi vm-id-lo ).
MAMA-VM-ID changed the same way: ( -- 0 0 ).

Verified: full (not standalone-file) kernel rebuild to catch cross-file
breakage given the size of this change -- it surfaced the
capsule_vm_physics.c blast radius a narrower check would have missed.
Three-architecture boot (amd64, aarch64, riscv64), all reaching ok>
with identical dict_hash=0x3d4e1daf289da94f matching the item-3.7
baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 19:50:34 -04:00

1179 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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.
*/
/**
* @file mama_forth_words.c
* @brief Mama FORTH vocabulary implementation for kernel capsule system (M7.1)
*
* Implements the MAMA vocabulary - kernel-only words for the LithosAnanke
* capsule birth protocol. This file is ONLY compiled for __STARKERNEL__ builds.
*
* The MAMA vocabulary provides:
* - Capsule directory enumeration
* - Baby VM birth from production capsules
* - Experiment execution on Mama
* - VM registry queries
*/
#ifdef __STARKERNEL__
#include "platform_alloc.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_birth.h"
#include "starkernel/capsule_loader.h"
#include "starkernel/capsule_run.h"
#include "starkernel/capsule_loader.h"
#include "starkernel/capsule_vm_physics.h"
#include "starkernel/vm/vm_internal.h"
#include "starkernel/repl.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/console.h"
#include "starkernel/arch.h"
#include "starkernel/timer.h"
#include "log.h"
#include "vm.h"
#include "word_registry.h"
#include "word_source/include/vocabulary_words.h"
/* Forward declarations for functions defined in vm_bootstrap.c and vocabulary_words.c */
extern void vm_bootstrap_root_vocabulary(VM *vm, const char *name);
extern void vocabulary_word_forth(VM *vm);
extern void vocabulary_word_definitions(VM *vm);
/* ============================================================================
* Capsule Directory Words
* ============================================================================ */
/**
* @brief CAPSULE-COUNT ( -- n )
* Push number of capsules in the capsule directory.
*/
void mama_word_capsule_count(VM *vm)
{
vm_push(vm, (cell_t)capsule_get_desc_count());
}
/**
* @brief CAPSULE@ ( idx -- desc )
* Get capsule descriptor address by index.
* Returns 0 if index is out of bounds.
*/
void mama_word_capsule_fetch(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t idx = vm_pop(vm);
if ((uint32_t)idx >= capsule_get_desc_count()) {
vm_push(vm, 0); /* Out of bounds */
return;
}
/* Push address of descriptor */
vm_push(vm, (cell_t)(uintptr_t)&capsule_get_descriptors()[idx]);
}
/**
* @brief CAPSULE-HASH@ ( desc -- hash )
* Get content hash from capsule descriptor.
*/
void mama_word_capsule_hash_fetch(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t desc_addr = vm_pop(vm);
const CapsuleDesc *desc = (const CapsuleDesc *)(uintptr_t)desc_addr;
if (!desc) {
vm_push(vm, 0);
return;
}
vm_push(vm, (cell_t)desc->content_hash);
}
/**
* @brief CAPSULE-FLAGS@ ( desc -- flags )
* Get flags from capsule descriptor.
*/
void mama_word_capsule_flags_fetch(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t desc_addr = vm_pop(vm);
const CapsuleDesc *desc = (const CapsuleDesc *)(uintptr_t)desc_addr;
if (!desc) {
vm_push(vm, 0);
return;
}
vm_push(vm, (cell_t)desc->flags);
}
/**
* @brief CAPSULE-LEN@ ( desc -- len )
* Get payload length from capsule descriptor.
*/
void mama_word_capsule_len_fetch(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t desc_addr = vm_pop(vm);
const CapsuleDesc *desc = (const CapsuleDesc *)(uintptr_t)desc_addr;
if (!desc) {
vm_push(vm, 0);
return;
}
vm_push(vm, (cell_t)desc->length);
}
/* ============================================================================
* VM Lifecycle State Stack
*
* Save/restore Hera's ECW interpreter state around lifecycle primitives
* (BIRTH, VM-EXEC, START). ECW pushes a resume IP onto vm->rsp before
* each word call and pops it after; if any lifecycle primitive corrupts
* vm->rsp the pop returns garbage and ip → fault. The state stack is
* dynamically allocated (sf_realloc) so its depth is unbounded.
* ============================================================================ */
static int vm_state_push(VM *vm)
{
if (vm->call_sp >= vm->call_stack_cap) {
int new_cap = vm->call_stack_cap ? vm->call_stack_cap * 2 : 8;
VMCallState *ns = (VMCallState *)sf_realloc(vm->call_stack,
(size_t)new_cap * sizeof(VMCallState));
if (!ns) {
log_message(LOG_ERROR, "vm_state_push: allocation failed at depth %d", vm->call_sp);
return 0;
}
vm->call_stack = ns;
vm->call_stack_cap = new_cap;
}
VMCallState *s = &vm->call_stack[vm->call_sp++];
s->rsp = vm->rsp;
s->exit_colon = vm->exit_colon;
s->ecw_nesting = vm->ecw_nesting;
if (vm->call_sp > vm->call_stack_max)
vm->call_stack_max = vm->call_sp;
return 1;
}
static void vm_state_pop(VM *vm)
{
if (vm->call_sp <= 0) return;
VMCallState *s = &vm->call_stack[--vm->call_sp];
vm->rsp = s->rsp;
vm->exit_colon = s->exit_colon;
vm->ecw_nesting = s->ecw_nesting;
}
/* ============================================================================
* VM Birth and Experiment Words
* ============================================================================ */
/**
* @brief BIRTH ( c-addr u -- )
* Birth a named VM from its capsule. Idempotent — if a live VM with
* that name already exists (case-insensitive), logs and returns.
*
* Name mapping: S" Artemis" → capsule "artemis:init.4th"
* Exception: S" Hera" → rejected (cannot re-birth Mama)
*/
void mama_word_birth(VM *vm)
{
char name_buf[VM_NAME_MAX];
char capsule_name[VM_NAME_MAX + 12]; /* name + ":init.4th" + NUL */
VMRegistryEntry existing;
uint32_t i, j;
cell_t u, caddr;
const char *src;
char lower[VM_NAME_MAX];
CapsuleRunResult result;
VMUuid new_vm_id;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("BIRTH: name too long or empty");
return;
}
/* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
/* Idempotency: live VM with same name → skip */
if (capsule_vm_find_by_name_nocase(name_buf, &existing) == 0 &&
existing.state == VM_STATE_LIVE) {
console_puts("BIRTH: ");
console_puts(name_buf);
console_println(" already live (idempotent)");
return;
}
/* Lowercase name for capsule path */
for (i = 0; name_buf[i]; i++) {
char c = name_buf[i];
lower[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c;
}
lower[i] = '\0';
/* Hera cannot be re-birthed */
if (lower[0]=='h' && lower[1]=='e' && lower[2]=='r' && lower[3]=='a' && lower[4]=='\0') {
console_println("BIRTH: cannot re-birth Hera");
return;
}
/* Build "lower:init.4th" */
j = 0;
for (i = 0; lower[i]; i++) capsule_name[j++] = lower[i];
capsule_name[j++] = ':';
{ const char *suf = "init.4th"; for (i = 0; suf[i]; i++) capsule_name[j++] = suf[i]; }
capsule_name[j] = '\0';
new_vm_id = vm_uuid_none();
/* Switch console prefix to the baby's name so its init capsule output
* appears tagged [Hermes] / [Artemis] rather than [Hera]. */
{
const char *saved_prefix = console_get_vm_name();
console_set_vm_name(name_buf);
vm_state_push(vm);
result = capsule_birth_baby(
capsule_name,
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
&new_vm_id,
(void **)0
);
vm_state_pop(vm);
console_set_vm_name(saved_prefix); /* restore [Hera] (or whoever called BIRTH) */
}
if (result == CAPSULE_RUN_OK) {
capsule_vm_registry_set_name(new_vm_id, name_buf);
vm_physics_init(new_vm_id);
console_puts("BIRTH: ");
console_puts(name_buf);
console_println(" live");
} else {
console_puts("BIRTH: ");
console_puts(name_buf);
console_println(" FAILED");
}
/* D3: stack clean on exit */
}
/**
* @brief START ( c-addr u -- )
* Enter a named VM's REPL loop synchronously. The calling VM blocks
* inside sk_repl_run() until the target halts (via STOP or BYE).
* Cannot start a LIVE, DEAD, or STILLBORN VM.
*/
void mama_word_start(VM *vm)
{
char name_buf[VM_NAME_MAX];
VMRegistryEntry entry;
uint32_t i;
cell_t u, caddr;
const char *src;
const char *caller_name;
VM *target;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("START: name too long or empty");
return;
}
/* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
if (capsule_vm_find_by_name_nocase(name_buf, &entry) != 0) {
console_puts("START: ");
console_puts(name_buf);
console_println(" not found");
return;
}
if (entry.state == VM_STATE_LIVE) {
console_puts("START: ");
console_puts(name_buf);
console_println(" already live");
return;
}
if (entry.state == VM_STATE_DEAD || entry.state == VM_STATE_STILLBORN) {
console_puts("START: ");
console_puts(name_buf);
console_println(" dead/stillborn — BIRTH first");
return;
}
target = (VM *)entry.vm_ptr;
if (!target) {
console_puts("START: ");
console_puts(name_buf);
console_println(" no VM pointer");
return;
}
/* Switch console prefix to target VM's name */
caller_name = console_get_vm_name();
console_set_vm_name(entry.name);
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
console_puts("START: entering ");
console_println(entry.name);
/* Run target's REPL — blocks until target->halted */
vm_state_push(vm);
sk_repl_run(target);
vm_state_pop(vm);
/* Target halted (STOP or BYE) — restore caller's context */
capsule_vm_set_state(entry.vm_id, VM_STATE_STOPPED);
console_set_vm_name(caller_name);
console_puts("START: ");
console_puts(entry.name);
console_println(" stopped");
/* Stack clean on exit */
}
/**
* @brief STOP ( -- )
* Self-stop: set vm->halted so sk_repl_run() exits on the next iteration.
* State is updated to VM_STATE_STOPPED by the START word after the REPL
* returns. STOP is registered in every VM's dictionary (including children)
* so any VM can stop itself.
*/
void mama_word_stop(VM *vm)
{
vm->halted = 1;
/* sk_repl_run's while(!vm->halted) loop exits after this word returns */
}
/**
* @brief USE ( c-addr u -- )
* Redirect system-wide REPL input to a named VM without touching the C
* call stack. The console prefix changes to [VMName].
* USE Hera (vm_id 0) resets dispatch to the default (Mama's VM, NULL slot).
*/
void mama_word_use(VM *vm)
{
char name_buf[VM_NAME_MAX];
VMRegistryEntry entry;
uint32_t i;
cell_t u, caddr;
const char *src;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("USE: name too long or empty");
return;
}
/* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
if (capsule_vm_find_by_name_nocase(name_buf, &entry) != 0) {
console_puts("USE: ");
console_puts(name_buf);
console_println(" not found");
return;
}
if (entry.state == VM_STATE_DEAD || entry.state == VM_STATE_STILLBORN) {
console_puts("USE: ");
console_puts(name_buf);
console_println(" dead/stillborn");
return;
}
/* Hera — restore default dispatch (NULL = use REPL's own vm) */
if (vm_uuid_is_hera(entry.vm_id)) {
sk_repl_set_active_vm((void *)0);
} else {
sk_repl_set_active_vm((VM *)entry.vm_ptr);
}
console_set_vm_name(entry.name);
console_puts("USE: now using ");
console_println(entry.name);
/* Stack clean on exit */
}
/**
* @brief KILL ( c-addr u -- )
* Destroy a named VM unconditionally. Hera cannot be killed.
* Idempotent: killing an already-dead VM is a no-op.
*/
void mama_word_kill(VM *vm)
{
char name_buf[VM_NAME_MAX];
uint32_t i;
cell_t u, caddr;
const char *src;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("KILL: name too long or empty");
return;
}
/* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
/* Resolve vm_id and fan out its heat to survivors before the registry
* entry is torn down -- capsule_vm_kill() only takes a name and
* leaves no live entry to query afterward. */
{
VMRegistryEntry entry;
if (capsule_vm_find_by_name_nocase(name_buf, &entry) == 0 &&
entry.state == VM_STATE_LIVE) {
vm_physics_retire(entry.vm_id);
}
}
capsule_vm_kill(name_buf);
/* Stack clean on exit */
}
/**
* @brief VM-STEP ( c-addr u -- )
* Give one REPL quantum to a named VM.
* Prints the VM's prompt, reads one line, executes it, returns to caller.
* This is the Compudynamics context-switch primitive — Hera yields one
* REPL turn to the named VM without surrendering the outer loop.
*/
static void mama_word_vm_step(VM *vm)
{
char name_buf[VM_NAME_MAX];
uint32_t i;
cell_t u, caddr;
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
if (vm->dsp < 1) { vm->error = 1; return; }
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("VM-STEP: name too long or empty");
return;
}
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
if (capsule_vm_find_by_name_nocase(name_buf, &entry) != 0) {
console_puts("VM-STEP: VM not found: ");
console_println(name_buf);
return;
}
target = (VM *)entry.vm_ptr;
if (!target || entry.state == VM_STATE_DEAD || entry.state == VM_STATE_STILLBORN) {
console_puts("VM-STEP: VM not available: ");
console_println(name_buf);
return;
}
vm_physics_touch(entry.vm_id);
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
sk_repl_step(target);
console_set_vm_name(saved_name);
/* Stack clean on exit */
}
/**
* @brief VM-EXEC ( cmd-caddr cmd-u vm-name-caddr vm-name-u -- )
* Inject a command string into a named VM and execute it immediately —
* no readline, no blocking. This is the autonomous Compudynamics primitive:
* Hera can drive a FORTH command into any child VM without surrendering the
* outer loop. Console prefix is saved/restored around the call.
*
* Stack order: push cmd string first, then VM name. Example:
* S" DOE-WORK" S" Hermes" VM-EXEC
*/
static void mama_word_vm_exec(VM *vm)
{
char vm_name[VM_NAME_MAX];
char cmd_buf[INPUT_BUFFER_SIZE];
uint32_t i;
cell_t vm_u, vm_caddr, cmd_u, cmd_caddr;
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
if (vm->dsp < 3) { vm->error = 1; return; }
/* TOS: vm-name-u, vm-name-caddr, cmd-u, cmd-caddr */
vm_u = vm_pop(vm);
vm_caddr = vm_pop(vm);
cmd_u = vm_pop(vm);
cmd_caddr = vm_pop(vm);
if (vm_u <= 0 || (uint32_t)vm_u >= VM_NAME_MAX) {
console_println("VM-EXEC: VM name too long or empty");
return;
}
if (cmd_u < 0 || (uint32_t)cmd_u >= INPUT_BUFFER_SIZE) {
console_println("VM-EXEC: command too long");
return;
}
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)vm_caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)vm_u; i++) vm_name[i] = src[i];
vm_name[vm_u] = '\0';
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)cmd_caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)cmd_u; i++) cmd_buf[i] = src[i];
cmd_buf[cmd_u] = '\0';
if (capsule_vm_find_by_name_nocase(vm_name, &entry) != 0) {
console_puts("VM-EXEC: VM not found: ");
console_println(vm_name);
return;
}
target = (VM *)entry.vm_ptr;
if (!target || entry.state == VM_STATE_DEAD || entry.state == VM_STATE_STILLBORN) {
console_puts("VM-EXEC: VM not available: ");
console_println(vm_name);
return;
}
vm_physics_touch(entry.vm_id);
log_message(LOG_INFO, "VM-EXEC: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
vm_state_push(vm);
vm_interpret(target, cmd_buf);
vm_state_pop(vm);
console_set_vm_name(saved_name);
if (target->error) {
console_puts("VM-EXEC: ERROR in ");
console_println(vm_name);
target->error = 0;
}
/* Stack clean on exit */
}
/**
* @brief VM-CALL ( cmd-caddr cmd-u vm-name-caddr vm-name-u -- n )
* Like VM-EXEC but pops one cell from the target VM's TOS and pushes
* it onto the caller's stack after execution. Used for cross-VM
* queries (e.g. HERMES-K in K-FLEET). Pushes 0 on error or empty
* target stack and sets vm->error if target left nothing.
*/
static void mama_word_vm_call(VM *vm)
{
char vm_name[VM_NAME_MAX];
char cmd_buf[INPUT_BUFFER_SIZE];
uint32_t i;
cell_t vm_u, vm_caddr, cmd_u, cmd_caddr;
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
if (vm->dsp < 3) { vm->error = 1; return; }
vm_u = vm_pop(vm);
vm_caddr = vm_pop(vm);
cmd_u = vm_pop(vm);
cmd_caddr = vm_pop(vm);
if (vm_u <= 0 || (uint32_t)vm_u >= VM_NAME_MAX) {
console_println("VM-CALL: VM name too long or empty");
vm_push(vm, 0); return;
}
if (cmd_u < 0 || (uint32_t)cmd_u >= INPUT_BUFFER_SIZE) {
console_println("VM-CALL: command too long");
vm_push(vm, 0); return;
}
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)vm_caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)vm_u; i++) vm_name[i] = src[i];
vm_name[vm_u] = '\0';
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)cmd_caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)cmd_u; i++) cmd_buf[i] = src[i];
cmd_buf[cmd_u] = '\0';
if (capsule_vm_find_by_name_nocase(vm_name, &entry) != 0) {
console_puts("VM-CALL: VM not found: ");
console_println(vm_name);
vm_push(vm, 0); return;
}
target = (VM *)entry.vm_ptr;
if (!target || entry.state == VM_STATE_DEAD || entry.state == VM_STATE_STILLBORN) {
console_puts("VM-CALL: VM not available: ");
console_println(vm_name);
vm_push(vm, 0); return;
}
vm_physics_touch(entry.vm_id);
log_message(LOG_DEBUG, "VM-CALL: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
vm_state_push(vm);
vm_interpret(target, cmd_buf);
vm_state_pop(vm);
console_set_vm_name(saved_name);
if (target->error) {
console_puts("VM-CALL: ERROR in ");
console_println(vm_name);
target->error = 0;
vm_push(vm, 0); return;
}
if (target->dsp >= 0) {
vm_push(vm, vm_pop(target));
} else {
console_println("VM-CALL: target left empty stack");
vm->error = 1;
vm_push(vm, 0);
}
}
/**
* @brief CAPSULE-BIRTH ( capsule-id -- vm-id-hi vm-id-lo )
* Birth a baby VM from a production (p) capsule.
* Returns the new VM's 128-bit ID as a double (item 3.8 -- FORTH already
* has double-cell words for exactly this), high cell on top, or
* vm_uuid_none()'s hi/lo (both all-ones) on failure.
*/
void mama_word_capsule_birth(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t capsule_idx = vm_pop(vm);
VMUuid new_vm_id = vm_uuid_none();
if ((uint64_t)capsule_idx >= capsule_get_desc_count()) {
vm_push(vm, (cell_t)new_vm_id.lo);
vm_push(vm, (cell_t)new_vm_id.hi);
return;
}
const char *cap_name = capsule_get_names()[(uint32_t)capsule_idx].name;
CapsuleRunResult result = capsule_birth_baby(
cap_name,
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
&new_vm_id,
(void **)0 /* Don't need VM context back */
);
if (result != CAPSULE_RUN_OK) {
new_vm_id = vm_uuid_none(); /* Birth failed */
}
vm_push(vm, (cell_t)new_vm_id.lo);
vm_push(vm, (cell_t)new_vm_id.hi);
}
/**
* @brief CAPSULE-RUN ( capsule-id -- )
* Run an experiment (e) capsule on Mama.
*/
void mama_word_capsule_run(VM *vm)
{
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t capsule_idx = vm_pop(vm);
if ((uint64_t)capsule_idx >= capsule_get_desc_count()) {
vm->error = 1;
return;
}
const char *cap_name = capsule_get_names()[(uint32_t)capsule_idx].name;
/* Run experiment on Mama (this VM) */
capsule_run_experiment(
vm,
cap_name,
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
(uint64_t *)0 /* Don't need run_id back */
);
}
/* ============================================================================
* VM Registry Words
* ============================================================================ */
/**
* @brief MAMA-VM-ID ( -- 0 0 )
* Push Mama's VM ID as a double (item 3.8): vm_uuid_hera() is all-zero,
* so both cells are 0. High cell on top, matching CAPSULE-BIRTH.
*/
void mama_word_mama_vm_id(VM *vm)
{
vm_push(vm, 0);
vm_push(vm, 0);
}
/**
* @brief VM-COUNT ( -- n )
* Push number of registered VMs.
*/
void mama_word_vm_count(VM *vm)
{
vm_push(vm, (cell_t)capsule_vm_registry_count());
}
/**
* @brief VM-CONSERVED? ( -- flag )
* FORTH-79 boolean: TRUE (-1) if |fleet_heat_sum - Q.1| < epsilon, else
* FALSE (0). Replaces fleet-k.4th's K-CONSERVED?.
*/
static void mama_word_vm_conserved(VM *vm)
{
vm_push(vm, vm_physics_conserved() ? (cell_t)-1 : (cell_t)0);
}
/**
* @brief VM-PHYSICS-STATUS ( -- )
* Print the fleet physics diagnostic report. Replaces fleet-k.4th's
* K-STATUS and compudynamics.4th's VM-STATUS.
*/
static void mama_word_vm_physics_status(VM *vm)
{
(void)vm;
vm_physics_status();
}
/* ============================================================================
* Diagnostic Words
* ============================================================================ */
/**
* @brief CAPSULE-TEST ( -- )
* Print diagnostic message confirming capsule system is active.
*/
void mama_word_capsule_test(VM *vm)
{
(void)vm;
console_println("Mama FORTH Capsule System (M7.1)");
console_puts(" Capsules: ");
console_println("");
console_puts(" VMs: ");
console_println("");
}
/* ============================================================================
* Vocabulary Registration
* ============================================================================ */
/**
* @brief EXEC ( c-addr u -- )
* Execute a named capsule in the current VM — same path as mama init auto-run.
*/
void mama_word_exec(VM *vm)
{
char name_buf[VM_NAME_MAX];
uint32_t i;
cell_t u, caddr;
const char *src;
CapsuleRunResult result;
int saved_dsp;
int saved_rsp;
if (vm->dsp < 1) { vm->error = 1; return; }
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("EXEC: name too long or empty");
return;
}
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
src = (const char *)p;
}
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
name_buf[u] = '\0';
/* Save both stacks so a crashing capsule cannot corrupt the caller.
* DSP: any partial pushes by the capsule are discarded.
* RSP: DO-LOOP indices pushed by the caller (e.g. the DoE loop) are
* preserved; a capsule that crashed mid->R/R> cannot corrupt them. */
saved_dsp = vm->dsp;
saved_rsp = vm->rsp;
log_message(LOG_INFO, "EXEC: capsule '%s'", name_buf);
result = capsule_exec_init(
vm,
name_buf,
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena());
/* Restore stacks and clear error/exit flags so the DoE loop survives
* a workload crash and continues cleanly to the next run. */
vm->dsp = saved_dsp;
vm->rsp = saved_rsp;
vm->error = 0;
vm->exit_colon = 0;
if (result != CAPSULE_RUN_OK) {
console_puts("EXEC: failed: ");
console_println(name_buf);
}
}
/**
* @brief CONNECT-ARTEMIS ( -- ) — Enter Artemis's REPL, birthing it first if needed.
*/
static void mama_word_connect_artemis(VM *vm __attribute__((unused)))
{
VMRegistryEntry entry;
VM *artemis;
const char *saved_name;
if (capsule_vm_find_by_name_nocase("Artemis", &entry) != 0 ||
entry.state == VM_STATE_DEAD ||
entry.state == VM_STATE_STILLBORN) {
VMUuid new_vm_id = vm_uuid_none();
const char *saved = console_get_vm_name();
CapsuleRunResult r;
console_set_vm_name("Artemis");
r = capsule_birth_baby(
"artemis:init.4th",
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
&new_vm_id, (void **)0);
console_set_vm_name(saved);
if (r != CAPSULE_RUN_OK) {
console_println("CONNECT-ARTEMIS: birth failed");
return;
}
capsule_vm_registry_set_name(new_vm_id, "Artemis");
if (capsule_vm_find_by_name_nocase("Artemis", &entry) != 0) {
console_println("CONNECT-ARTEMIS: registry error");
return;
}
}
artemis = (VM *)entry.vm_ptr;
if (!artemis) {
console_println("CONNECT-ARTEMIS: no VM pointer");
return;
}
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
sk_repl_run(artemis);
capsule_vm_set_state(entry.vm_id, VM_STATE_STOPPED);
console_set_vm_name(saved_name);
}
/**
* @brief BYE ( -- ) — Hera-only: reap all children then cold-restart the machine.
*
* In child VMs this word is never registered; children use the standard
* system_word_bye which sets vm->halted and returns to the parent's REPL.
*/
static void mama_word_bye(VM *vm __attribute__((unused)))
{
console_println("BYE: reaping children");
capsule_vm_kill_all_nonmama();
console_println("BYE: cold restart");
arch_cold_reset();
}
/**
* @brief CONNECT-HERMES ( -- ) — Enter Hermes's REPL, birthing it first if needed.
*
* Idempotent: if Hermes is already born (LIVE or STOPPED) it is entered
* directly without re-birthing. On BYE from Hermes, control returns here.
*/
static void mama_word_connect_hermes(VM *vm __attribute__((unused)))
{
VMRegistryEntry entry;
VM *hermes;
const char *saved_name;
/* Birth if not found or previously dead/stillborn */
if (capsule_vm_find_by_name_nocase("Hermes", &entry) != 0 ||
entry.state == VM_STATE_DEAD ||
entry.state == VM_STATE_STILLBORN) {
VMUuid new_vm_id = vm_uuid_none();
const char *saved = console_get_vm_name();
CapsuleRunResult r;
console_set_vm_name("Hermes");
r = capsule_birth_baby(
"hermes:init.4th",
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
&new_vm_id, (void **)0);
console_set_vm_name(saved);
if (r != CAPSULE_RUN_OK) {
console_println("CONNECT-HERMES: birth failed");
return;
}
capsule_vm_registry_set_name(new_vm_id, "Hermes");
if (capsule_vm_find_by_name_nocase("Hermes", &entry) != 0) {
console_println("CONNECT-HERMES: registry error");
return;
}
}
hermes = (VM *)entry.vm_ptr;
if (!hermes) {
console_println("CONNECT-HERMES: no VM pointer");
return;
}
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
sk_repl_run(hermes);
capsule_vm_set_state(entry.vm_id, VM_STATE_STOPPED);
console_set_vm_name(saved_name);
}
/**
* @brief Register Mama FORTH vocabulary words with the VM
*
* Creates the MAMA vocabulary and registers all capsule-related words.
*
* @param vm Pointer to the VM instance
*/
void register_mama_forth_words(VM *vm)
{
/* Register words in FORTH vocabulary first */
register_word(vm, "BYE", mama_word_bye);
register_word(vm, "CONNECT-HERMES", mama_word_connect_hermes);
register_word(vm, "CONNECT-ARTEMIS", mama_word_connect_artemis);
register_word(vm, "BIRTH", mama_word_birth);
register_word(vm, "KILL", mama_word_kill);
register_word(vm, "START", mama_word_start);
register_word(vm, "STOP", mama_word_stop);
register_word(vm, "USE", mama_word_use);
register_word(vm, "EXEC", mama_word_exec);
register_word(vm, "CAPSULE-COUNT", mama_word_capsule_count);
register_word(vm, "CAPSULE@", mama_word_capsule_fetch);
register_word(vm, "CAPSULE-HASH@", mama_word_capsule_hash_fetch);
register_word(vm, "CAPSULE-FLAGS@", mama_word_capsule_flags_fetch);
register_word(vm, "CAPSULE-LEN@", mama_word_capsule_len_fetch);
register_word(vm, "CAPSULE-BIRTH", mama_word_capsule_birth);
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
register_word(vm, "VM-COUNT", mama_word_vm_count);
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);
register_word(vm, "VM-PHYSICS-STATUS", mama_word_vm_physics_status);
register_word(vm, "VM-STEP", mama_word_vm_step);
register_word(vm, "VM-EXEC", mama_word_vm_exec);
register_word(vm, "VM-CALL", mama_word_vm_call);
register_word(vm, "CAPSULE-TEST", mama_word_capsule_test);
/* Create and switch to MAMA vocabulary */
vm_bootstrap_root_vocabulary(vm, "MAMA");
/* Re-register in MAMA vocabulary context */
register_word(vm, "BYE", mama_word_bye);
register_word(vm, "CONNECT-HERMES", mama_word_connect_hermes);
register_word(vm, "CONNECT-ARTEMIS", mama_word_connect_artemis);
register_word(vm, "BIRTH", mama_word_birth);
register_word(vm, "KILL", mama_word_kill);
register_word(vm, "START", mama_word_start);
register_word(vm, "STOP", mama_word_stop);
register_word(vm, "USE", mama_word_use);
register_word(vm, "EXEC", mama_word_exec);
register_word(vm, "CAPSULE-COUNT", mama_word_capsule_count);
register_word(vm, "CAPSULE@", mama_word_capsule_fetch);
register_word(vm, "CAPSULE-HASH@", mama_word_capsule_hash_fetch);
register_word(vm, "CAPSULE-FLAGS@", mama_word_capsule_flags_fetch);
register_word(vm, "CAPSULE-LEN@", mama_word_capsule_len_fetch);
register_word(vm, "CAPSULE-BIRTH", mama_word_capsule_birth);
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
register_word(vm, "VM-COUNT", mama_word_vm_count);
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);
register_word(vm, "VM-PHYSICS-STATUS", mama_word_vm_physics_status);
register_word(vm, "VM-STEP", mama_word_vm_step);
register_word(vm, "VM-EXEC", mama_word_vm_exec);
register_word(vm, "VM-CALL", mama_word_vm_call);
register_word(vm, "CAPSULE-TEST", mama_word_capsule_test);
register_word(vm, "EXEC", mama_word_exec);
/* Return to FORTH vocabulary */
vocabulary_word_forth(vm);
vocabulary_word_definitions(vm);
}
/**
* register_child_vm_words - Register the minimal word set needed by child VMs.
*
* Child VMs are not bootstrapped through sk_vm_bootstrap_parity, so they
* do not get register_mama_forth_words(). They only need STOP (self-halt)
* and EXEC (load a capsule). Keeping the registrations here — in the same
* translation unit as the word functions — avoids cross-TU function-pointer
* loads that produce R_X86_64_REX_GOTPCRELX relocations; those are not
* relaxed by the PE32+ linker, causing the function code bytes to be read
* as the pointer value instead of the actual address.
*/
void register_child_vm_words(VM *vm)
{
register_word(vm, "STOP", mama_word_stop);
register_word(vm, "EXEC", mama_word_exec);
register_word(vm, "VM-EXEC", mama_word_vm_exec);
register_word(vm, "VM-CALL", mama_word_vm_call);
}
#endif /* __STARKERNEL__ */