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>
1179 lines
35 KiB
C
1179 lines
35 KiB
C
/*
|
||
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.
|
||
*/
|
||
|
||
/**
|
||
* @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__ */
|