/* 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. */ /** * capsule_birth.c - VM Birth Protocol Implementation (M7.1) * * Mama init, baby birth, and experiment execution. * Freestanding - no libc dependency. * * Birth sequence for a baby VM: * 1. Find capsule by name (capsule_find_by_name) * 2. Assert CAPSULE_BIRTH_ELIGIBLE * 3. Validate content hash * 4. vm_alloc_hook() — fresh VM * 5. vm_exec_hook(payload) — IDENTITY (init capsule from Hera's store) * 6. vm_exec_hook("1 LOAD") — PERSONALITY (baby's personal init.4th from block 1, if present) * 7. Log parity record */ #include "starkernel/capsule_birth.h" #include "starkernel/capsule.h" #include "starkernel/capsule_run.h" #include "starkernel/kmalloc.h" #include "starkernel/console.h" #include "vm.h" #include "platform_alloc.h" /*=========================================================================== * VM Execution Hooks *===========================================================================*/ static CapsuleExecFn vm_exec_fn = 0; static CapsuleDictHashFn vm_dict_hash_fn = 0; static CapsuleVMAllocFn vm_alloc_fn = 0; void capsule_birth_set_hooks( CapsuleExecFn exec_fn, CapsuleDictHashFn dict_hash_fn, CapsuleVMAllocFn vm_alloc_fn_arg) { vm_exec_fn = exec_fn; vm_dict_hash_fn = dict_hash_fn; vm_alloc_fn = vm_alloc_fn_arg; } /*=========================================================================== * VM Registry — dynamic linked list, heap-allocated via kmalloc *===========================================================================*/ typedef struct vm_node { VMRegistryEntry entry; struct vm_node *next; } vm_node_t; static vm_node_t *vm_registry_head = (void *)0; static uint32_t vm_registry_count = 0; static uint32_t next_vm_id = 1; /* VM 0 reserved for Mama */ /* Copy at most VM_NAME_MAX-1 chars, always null-terminate */ static void vm_name_copy(char *dst, const char *src) { uint32_t i; for (i = 0; i < (VM_NAME_MAX - 1u) && src[i]; i++) dst[i] = src[i]; dst[i] = '\0'; } /* Case-sensitive equality test (no libc) */ static int vm_name_eq(const char *a, const char *b) { while (*a && *b) { if (*a != *b) return 0; a++; b++; } return *a == *b; } /* Internal: return mutable pointer into registry node for vm_id */ static VMRegistryEntry *vm_find_entry_ptr(uint32_t vm_id) { vm_node_t *node = vm_registry_head; while (node) { if (node->entry.vm_id == vm_id) return &node->entry; node = node->next; } return (void *)0; } void capsule_vm_registry_init(void *mama_vm_ptr) { uint32_t i; vm_node_t *node; vm_node_t *next; vm_node_t *mama; /* Free any nodes from a previous init (defensive) */ node = vm_registry_head; while (node) { next = node->next; kfree(node); node = next; } vm_registry_head = (void *)0; vm_registry_count = 0; next_vm_id = 1; /* Mama is always VM 0 */ mama = (vm_node_t *)kmalloc(sizeof(vm_node_t)); if (!mama) return; mama->entry.vm_id = 0; mama->entry.state = VM_STATE_LIVE; mama->entry.birth_capsule_id = 0; mama->entry.birth_timestamp_ns = 0; mama->entry.birth_dict_hash = 0; mama->entry.flags = 0; mama->entry.parent_vm_id = 0; /* self-referential: Hera is the root */ mama->entry.vm_ptr = mama_vm_ptr; for (i = 0; i < VM_NAME_MAX; i++) mama->entry.name[i] = '\0'; vm_name_copy(mama->entry.name, "Hera"); mama->next = (void *)0; vm_registry_head = mama; vm_registry_count = 1; /* From this point on all console output is prefixed [Hera] */ console_set_vm_name("Hera"); } static VMRegistryEntry *vm_registry_alloc(void) { uint32_t i; vm_node_t *node; vm_node_t *tail; node = (vm_node_t *)kmalloc(sizeof(vm_node_t)); if (!node) return (void *)0; node->entry.vm_id = 0; node->entry.state = VM_STATE_EMBRYO; node->entry.birth_capsule_id = 0; node->entry.birth_timestamp_ns = 0; node->entry.birth_dict_hash = 0; node->entry.flags = 0; node->entry.parent_vm_id = 0; /* only Hera calls BIRTH today; see * design doc's "explicitly out of * scope" for making this dynamic */ node->entry.vm_ptr = (void *)0; for (i = 0; i < VM_NAME_MAX; i++) node->entry.name[i] = '\0'; node->next = (void *)0; /* Append to tail */ if (!vm_registry_head) { vm_registry_head = node; } else { tail = vm_registry_head; while (tail->next) tail = tail->next; tail->next = node; } vm_registry_count++; return &node->entry; } int capsule_vm_registry_get(uint32_t vm_id, VMRegistryEntry *out) { VMRegistryEntry *entry; if (!out) return -1; entry = vm_find_entry_ptr(vm_id); if (!entry) return -1; *out = *entry; return 0; } uint32_t capsule_vm_registry_count(void) { return vm_registry_count; } int capsule_vm_find_by_name(const char *name, VMRegistryEntry *out) { vm_node_t *node; if (!name || !out) return -1; node = vm_registry_head; while (node) { if (vm_name_eq(node->entry.name, name)) { *out = node->entry; return 0; } node = node->next; } return -1; } /* Fold ASCII letter to lowercase (no libc) */ static char vm_to_lower(char c) { return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; } /* Case-insensitive ASCII equality (no libc) */ static int vm_name_eq_nocase(const char *a, const char *b) { while (*a && *b) { if (vm_to_lower(*a) != vm_to_lower(*b)) return 0; a++; b++; } return *a == *b; } int capsule_vm_find_by_name_nocase(const char *name, VMRegistryEntry *out) { vm_node_t *node; if (!name || !out) return -1; node = vm_registry_head; while (node) { if (vm_name_eq_nocase(node->entry.name, name)) { *out = node->entry; return 0; } node = node->next; } return -1; } void capsule_vm_set_state(uint32_t vm_id, uint32_t state) { VMRegistryEntry *entry = vm_find_entry_ptr(vm_id); if (entry) entry->state = state; } void capsule_vm_registry_set_name(uint32_t vm_id, const char *name) { VMRegistryEntry *entry; if (!name) return; entry = vm_find_entry_ptr(vm_id); if (!entry) return; vm_name_copy(entry->name, name); } /*=========================================================================== * Internal: init.4th dispatch (PERSONALITY layer) * * After a baby VM runs its identity capsule, attempt to execute block 1. * Block 1 is the PERSONALITY layer — the baby's personal init.4th. * Failure is silent: the block may not exist, which is normal. *===========================================================================*/ static void dispatch_init_forth(void *vm_ctx) { /* M9: run baby's personal init.4th from block 1 once per-VM block * storage is isolated. Until then this is a no-op to avoid executing * Mama's block 1 content on every child VM. */ (void)vm_ctx; } /*=========================================================================== * VM Kill *===========================================================================*/ int capsule_vm_kill(const char *name) { VMRegistryEntry *entry; VM *vm; uint32_t vm_id; uint32_t i; if (!name) return -1; /* Locate by name (case-insensitive) */ { vm_node_t *node = vm_registry_head; entry = (VMRegistryEntry *)0; while (node) { if (vm_name_eq_nocase(node->entry.name, name)) { entry = &node->entry; break; } node = node->next; } } if (!entry) { console_puts("KILL: "); console_puts(name); console_println(" not found"); return -1; } /* Hera cannot be killed */ if (entry->vm_id == 0) { console_println("KILL: cannot kill Hera"); return -1; } /* Already dead — idempotent */ if (entry->state == VM_STATE_DEAD) { console_puts("KILL: "); console_puts(name); console_println(" already dead"); return 0; } vm_id = entry->vm_id; vm = (VM *)entry->vm_ptr; /* Tear down and free */ if (vm) { vm_cleanup(vm); sf_free(vm); } entry->vm_ptr = (void *)0; entry->state = VM_STATE_DEAD; for (i = 0; i < VM_NAME_MAX; i++) entry->name[i] = '\0'; capsule_parity_log_kill(vm_id, name); console_puts("KILL: "); console_puts(name); console_println(" dead"); return 0; } void capsule_vm_kill_all_nonmama(void) { vm_node_t *node; VM *vm; uint32_t vm_id; node = vm_registry_head; while (node) { if (node->entry.vm_id == 0 || node->entry.state == VM_STATE_DEAD) { node = node->next; continue; } vm_id = node->entry.vm_id; vm = (VM *)node->entry.vm_ptr; if (vm) { vm->halted = 1; vm_cleanup(vm); sf_free(vm); } node->entry.vm_ptr = (void *)0; node->entry.state = VM_STATE_DEAD; capsule_parity_log_kill(vm_id, node->entry.name); node = node->next; } } /*=========================================================================== * Mama Init *===========================================================================*/ CapsuleRunResult capsule_birth_mama( void *mama_vm, const CapsuleDirHeader *dir, const CapsuleDesc *descs, const CapsuleNameEntry *names, const uint8_t *arena) { if (!mama_vm || !dir || !descs || !names || !arena) return CAPSULE_RUN_ERR_INVALID; if (!vm_exec_fn || !vm_dict_hash_fn) return CAPSULE_RUN_ERR_INVALID; const CapsuleDesc *mama_cap = capsule_find_mama_init(dir, descs); if (!mama_cap) return CAPSULE_RUN_ERR_INVALID; CapsuleValidateResult vr = capsule_validate(mama_cap, arena, dir->arena_size, 1); if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID; uint64_t pre_dict_hash = vm_dict_hash_fn(mama_vm); (void)pre_dict_hash; const uint8_t *payload = capsule_get_payload(mama_cap, arena); if (!payload) return CAPSULE_RUN_ERR_INVALID; int exec_result = vm_exec_fn(mama_vm, (const char *)payload, mama_cap->length); if (exec_result != 0) return CAPSULE_RUN_ERR_EXEC_FAIL; uint64_t post_dict_hash = vm_dict_hash_fn(mama_vm); capsule_parity_log_mama_init( mama_cap->capsule_id, mama_cap->content_hash, post_dict_hash); { VMRegistryEntry *mama_entry = vm_find_entry_ptr(0); if (mama_entry) { mama_entry->birth_capsule_id = mama_cap->capsule_id; mama_entry->birth_dict_hash = post_dict_hash; } } return CAPSULE_RUN_OK; } /*=========================================================================== * Baby Birth *===========================================================================*/ CapsuleRunResult capsule_birth_baby( const char *capsule_name, const CapsuleDirHeader *dir, const CapsuleDesc *descs, const CapsuleNameEntry *names, const uint8_t *arena, uint32_t *out_vm_id, void **out_vm_ctx) { if (!capsule_name || !dir || !descs || !names || !arena) return CAPSULE_RUN_ERR_INVALID; if (!vm_exec_fn || !vm_dict_hash_fn || !vm_alloc_fn) return CAPSULE_RUN_ERR_INVALID; /* Locate by name */ const CapsuleDesc *cap = capsule_find_by_name(dir, descs, names, capsule_name); if (!cap) return CAPSULE_RUN_ERR_INVALID; if (!CAPSULE_BIRTH_ELIGIBLE(cap->flags)) return CAPSULE_RUN_ERR_NOT_ELIGIBLE; CapsuleValidateResult vr = capsule_validate(cap, arena, dir->arena_size, 1); if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID; VMRegistryEntry *entry = vm_registry_alloc(); if (!entry) return CAPSULE_RUN_ERR_INVALID; uint32_t vm_id = next_vm_id++; entry->vm_id = vm_id; entry->state = VM_STATE_EMBRYO; entry->birth_capsule_id = cap->capsule_id; /* Allocate baby VM */ void *new_vm = vm_alloc_fn(); entry->vm_ptr = new_vm; if (!new_vm) { entry->state = VM_STATE_STILLBORN; capsule_parity_log_birth_failed(vm_id, cap->capsule_id, CAPSULE_RUN_ERR_STILLBORN, 0); return CAPSULE_RUN_ERR_STILLBORN; } const uint8_t *payload = capsule_get_payload(cap, arena); if (!payload) { entry->state = VM_STATE_STILLBORN; capsule_parity_log_birth_failed(vm_id, cap->capsule_id, CAPSULE_RUN_ERR_INVALID, 0); return CAPSULE_RUN_ERR_INVALID; } /* IDENTITY: run init capsule */ int exec_result = vm_exec_fn(new_vm, (const char *)payload, cap->length); if (exec_result != 0) { uint64_t partial_hash = vm_dict_hash_fn(new_vm); entry->state = VM_STATE_STILLBORN; entry->birth_dict_hash = partial_hash; capsule_parity_log_birth_failed(vm_id, cap->capsule_id, CAPSULE_RUN_ERR_EXEC_FAIL, partial_hash); return CAPSULE_RUN_ERR_EXEC_FAIL; } /* PERSONALITY: per-VM block storage is M9 scope; no-op until then */ dispatch_init_forth(new_vm); uint64_t dict_hash = vm_dict_hash_fn(new_vm); entry->state = VM_STATE_LIVE; entry->birth_dict_hash = dict_hash; capsule_parity_log_birth(vm_id, cap->capsule_id, cap->content_hash, dict_hash); if (out_vm_id) *out_vm_id = vm_id; if (out_vm_ctx) *out_vm_ctx = new_vm; return CAPSULE_RUN_OK; } /*=========================================================================== * Experiment Execution *===========================================================================*/ CapsuleRunResult capsule_run_experiment( void *mama_vm, const char *capsule_name, const CapsuleDirHeader *dir, const CapsuleDesc *descs, const CapsuleNameEntry *names, const uint8_t *arena, uint64_t *out_run_id) { if (!mama_vm || !capsule_name || !dir || !descs || !names || !arena) return CAPSULE_RUN_ERR_INVALID; if (!vm_exec_fn || !vm_dict_hash_fn) return CAPSULE_RUN_ERR_INVALID; const CapsuleDesc *cap = capsule_find_by_name(dir, descs, names, capsule_name); if (!cap) return CAPSULE_RUN_ERR_INVALID; if (!CAPSULE_DOE_ELIGIBLE(cap->flags)) return CAPSULE_RUN_ERR_NOT_ELIGIBLE; CapsuleValidateResult vr = capsule_validate(cap, arena, dir->arena_size, 1); if (vr != CAPSULE_VALID) return CAPSULE_RUN_ERR_INVALID; uint64_t pre_dict_hash = vm_dict_hash_fn(mama_vm); const uint8_t *payload = capsule_get_payload(cap, arena); if (!payload) return CAPSULE_RUN_ERR_INVALID; int exec_result = vm_exec_fn(mama_vm, (const char *)payload, cap->length); uint64_t post_dict_hash = vm_dict_hash_fn(mama_vm); CapsuleRunRecord record; record.run_id = 0; record.vm_id = 0; record.reserved = 0; record.capsule_id = cap->capsule_id; record.capsule_hash = cap->content_hash; record.pre_dict_hash = pre_dict_hash; record.post_dict_hash = post_dict_hash; record.started_ns = 0; record.ended_ns = 0; record.result_code = (exec_result == 0) ? CAPSULE_RUN_OK : CAPSULE_RUN_ERR_EXEC_FAIL; record.flags = cap->flags; uint64_t run_id = capsule_run_log_record(&record); capsule_parity_log_run(0, run_id, cap->capsule_id, pre_dict_hash, post_dict_hash); if (out_run_id) *out_run_id = run_id; return (exec_result == 0) ? CAPSULE_RUN_OK : CAPSULE_RUN_ERR_EXEC_FAIL; }