theory StarForth_Lifecycle_Words_Hosted imports StarForth_Base begin (* ========================================================================= Mirrors: src/word_source/lifecycle_words_hosted.c Registers (hosted build only -- see below): BIRTH KILL PAUSE RESUME USE ── Build-variant split, opposite of the console-fabric files ────────── The ENTIRE file is `#ifndef __STARKERNEL__` -- opposite of framebuffer_ words.c/keyboard_words.c/scroll_words.c/ttf_words.c, which were kernel- only. Per the file's own header comment and CLAUDE.md's Hard Rules ("BIRTH, RUN, USE are primitives registered in C exactly like DUP... Never reach for FIND"), the KERNEL build's BIRTH/KILL/PAUSE/RESUME/USE live in `src/starkernel/capsule/lifecycle_words.c` -- a different file entirely, in `src/starkernel/` rather than `src/word_source/`, and per CLAUDE.md's scope note that tree is real/load-bearing kernel code, not this sweep's target (this sweep covers `src/word_source/*.c`, the vendored/shared word set). So this theory covers ONLY the hosted stand-ins; the real kernel capsule-birth-protocol words are out of scope for this file (and for this sweep generally). ── All five words are the SAME vm_state transition ───────────────────── Each body is: pop u, pop caddr (both via bare `vm_pop`, no upfront `dsp` guard -- deferring to `vm_pop`'s own internal underflow check, same convention scroll_words.c uses and explains), copy at most 63 bytes from `vm->memory[caddr..caddr+u)` into a local buffer (bounds-checked against `VM_MEMORY_SIZE`, silently truncated to empty if `caddr` is out of range), then `log_message` the extracted name. The five bodies differ ONLY in the literal string passed to `log_message` ("BIRTH %s (hosted)" vs "KILL %s (hosted)" etc.) -- everything with a vm_state footprint is byte-for-byte identical across all five. Modelled ONCE as `forth_lifecycle_pop2`, with each word's definition stated as literally equal to it. The C does NOT check `vm->error` between the two pops, so on a single-element stack the first pop succeeds (consuming it) before the second pop discovers the now-empty stack and sets the error -- the same partial-pop-then-error shape already seen in physics_pipelining_ diagnostic_words.c, but here fully mechanised since (unlike that file) nothing downstream of the pops needs an unmodelled subsystem: the name extraction is a pure, bounds-checked memory READ with no vm_state write, and log_message is pure I/O. So this file's entire vm_state footprint is exactly captured by the two pops -- no deferred remainder at all, the only file in this sweep so far where that's true for every registered word. *) definition forth_lifecycle_pop2 :: "vm_state \ vm_state" where "forth_lifecycle_pop2 vm = (case data_stack vm of [] \ set_error vm | [x] \ set_error (vm\data_stack := []\) | u # caddr # xs \ vm\data_stack := xs\)" lemma lifecycle_pop2_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_lifecycle_pop2 vm)" and "data_stack (forth_lifecycle_pop2 vm) = []" by (simp_all add: forth_lifecycle_pop2_def set_error_def assms) lemma lifecycle_pop2_underflow_one: \ \Partial pop: the single element IS consumed (it was popped successfully as `u`) before the second pop fails.\ assumes "data_stack vm = [x]" shows "vm_error (forth_lifecycle_pop2 vm)" and "data_stack (forth_lifecycle_pop2 vm) = []" by (simp_all add: forth_lifecycle_pop2_def set_error_def assms) lemma lifecycle_pop2_normal: assumes "data_stack vm = u # caddr # xs" shows "data_stack (forth_lifecycle_pop2 vm) = xs" and "vm_error (forth_lifecycle_pop2 vm) = vm_error vm" by (simp_all add: forth_lifecycle_pop2_def assms) (* ── The five registered words, each literally equal to forth_lifecycle_pop2 ── *) definition forth_birth :: "vm_state \ vm_state" where "forth_birth = forth_lifecycle_pop2" definition forth_kill :: "vm_state \ vm_state" where "forth_kill = forth_lifecycle_pop2" definition forth_pause :: "vm_state \ vm_state" where "forth_pause = forth_lifecycle_pop2" definition forth_resume :: "vm_state \ vm_state" where "forth_resume = forth_lifecycle_pop2" definition forth_use :: "vm_state \ vm_state" where "forth_use = forth_lifecycle_pop2" lemma birth_is_pop2: "forth_birth vm = forth_lifecycle_pop2 vm" by (simp add: forth_birth_def) lemma kill_is_pop2: "forth_kill vm = forth_lifecycle_pop2 vm" by (simp add: forth_kill_def) lemma pause_is_pop2: "forth_pause vm = forth_lifecycle_pop2 vm" by (simp add: forth_pause_def) lemma resume_is_pop2: "forth_resume vm = forth_lifecycle_pop2 vm" by (simp add: forth_resume_def) lemma use_is_pop2: "forth_use vm = forth_lifecycle_pop2 vm" by (simp add: forth_use_def) lemma lifecycle_words_preserve_dictionary: "dictionary (forth_lifecycle_pop2 vm) = dictionary vm" by (auto simp: forth_lifecycle_pop2_def set_error_def split: list.split) lemma lifecycle_words_preserve_memory: "memory (forth_lifecycle_pop2 vm) = memory vm" by (auto simp: forth_lifecycle_pop2_def set_error_def split: list.split) lemma lifecycle_name_extraction_and_log_not_modelled: True \ \extract_name's memcpy-from-vm-memory is a pure read (no vm_state write) and log_message is pure I/O -- neither has any vm_state effect to characterise beyond what forth_lifecycle_pop2 already captures.\ by simp end