word_source: repair DECAY-RATE@ overflow guard and remove dead shadowed registrations
DECAY-RATE@ (physics_freeze_words.c) pushed to the data stack with no capacity check and no prior pop to make room, unlike its neighbors in the same file -- the one live, unconditional missing-guard bug the Isabelle sweep's ~15 candidate findings reduced to once checked against vm_push()'s real internal bounds check (see proof/FINDINGS.md SS2). Removed dictionary_manipulation_words.c's [ ] STATE and defining_words.c's DEFER IS DEFER@ (plus the now-orphaned defining_runtime_defer helper) -- all confirmed permanently shadowed by later dictionary registrations (defining_words.c and defer_words.c respectively), per FORTH's newest-first lookup. No behavior change: the removed code was already unreachable. Verified: hosted `make` builds clean under -Wall -Werror; the hosted self-test suite passes 965/965 implemented tests with no regressions. Three-architecture QEMU acceptance boot, all clean to ok> with an identical dict_hash=0x24b4279f0670aa3a across amd64/aarch64/riscv64 and identical 1003/965/0/0 test totals -- logs attached. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3426d6a4a7
commit
dfdabcc2d7
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,13 @@ begin
|
||||
`STATE` (Module 17, line 126). FORTH dictionary lookup finds the MOST
|
||||
RECENT definition of a name first, so defining_words.c's versions are
|
||||
the only ones ever reachable -- dictionary_manipulation_words.c's `[`/
|
||||
`]`/`STATE`/`INTERPRET` are permanently shadowed, dead code from the
|
||||
moment boot registration completes.
|
||||
`]`/`STATE` are permanently shadowed, dead code from the moment boot
|
||||
registration completes. CORRECTED 2026-08-14: `INTERPRET` is NOT part of
|
||||
this shadow -- defining_words.c never registers a word named INTERPRET
|
||||
at all (confirmed by grep), so dictionary_manipulation_words.c's
|
||||
INTERPRET is the only registration that exists and is live, not dead.
|
||||
The earlier version of this note (and proof/FINDINGS.md) incorrectly
|
||||
folded it into the shadowed group.
|
||||
|
||||
This also CORRECTS that file's finding, not just supersedes it: its
|
||||
`[`/`]`/`STATE` write/read a dead file-scope `static cell_t
|
||||
|
||||
@@ -37,16 +37,28 @@ begin
|
||||
(include/vm.h:431, the real per-VM STATE field actively used by
|
||||
vm.c/vm_core.c/vm_bootstrap.c/capsule_loader.c on both hosted and
|
||||
kernel builds). This static is a single process-wide variable, not
|
||||
even per-VM: in the Tripod multi-VM fleet, EVERY VM's `[`/`]`/
|
||||
`INTERPRET` mutates the SAME shared cell, and `STATE` pushes ITS
|
||||
address (a bare host pointer, not a VM address) -- identical across
|
||||
every VM instance. `vm->state_var` itself is correctly toggled
|
||||
elsewhere in the real interpreter loop, so the dictionary-space
|
||||
compile/interpret bookkeeping isn't broken, but these four FORTH
|
||||
words are effectively wired to a dead, cross-VM-shared shadow
|
||||
variable instead of the VM's own state. Modelled below using only
|
||||
`vm_mode` (which `[`/`]` DO correctly set) -- the dead static write
|
||||
has no vm_state counterpart and is simply omitted, not "fixed".
|
||||
even per-VM. CORRECTED 2026-08-14 -- these four words split into two
|
||||
different reachability classes, not one:
|
||||
- `[`, `]`, `STATE` are permanently SHADOWED (see StarForth_Defining_
|
||||
Words.thy's duplicate-registration finding) -- dead code, this
|
||||
static's misuse never executes for any live VM.
|
||||
- `INTERPRET` is NOT shadowed (defining_words.c registers no word by
|
||||
that name at all) -- it IS live and reachable, and its body really
|
||||
does execute `state_variable = 0;` on every call
|
||||
(dictionary_manipulation_words.c:392) alongside the correct
|
||||
`vm->mode = MODE_INTERPRET`. This write is functionally inert
|
||||
(nothing on any live path reads `state_variable`, since the only
|
||||
word that ever read it -- this file's own `STATE` -- is itself one
|
||||
of the shadowed three above), but it is live, executing code in a
|
||||
registered, tested word, not dead code to remove casually per
|
||||
.claude/CLAUDE.md's "never modify a registered, tested word"
|
||||
guidance. Reported, not touched.
|
||||
`vm->state_var` itself is correctly toggled elsewhere in the real
|
||||
interpreter loop regardless, so the dictionary-space compile/
|
||||
interpret bookkeeping isn't broken by any of this. Modelled below
|
||||
using only `vm_mode` (which `[`/`]` DO correctly set) -- the dead
|
||||
static write has no vm_state counterpart and is simply omitted, not
|
||||
"fixed".
|
||||
2. `dictionary_m_word_hidden`'s `#else` fallback branch (taken only if
|
||||
`WORD_HIDDEN` is undefined -- it is always defined per include/vm.h:
|
||||
181, so this branch is dead in every build configuration seen) calls
|
||||
|
||||
@@ -22,13 +22,14 @@ begin
|
||||
accessors with no vm_state counterpart, the same class of gap as every
|
||||
other hardware-boundary word in this console-fabric group.
|
||||
|
||||
── Finding: FB-WIDTH/FB-HEIGHT have no overflow guard ──────────────────
|
||||
Neither checks `ds_full` (or any capacity condition) before pushing --
|
||||
same hazard class as physics_freeze_words.c's DECAY-RATE@, the first
|
||||
instance of this sweep finding a missing overflow guard. Second
|
||||
instance now, both in diagnostic/hardware-boundary words that read as
|
||||
"just returns a number" and evidently didn't get the same underflow-
|
||||
guard-writing discipline as stack-manipulation words.
|
||||
── CORRECTED finding: FB-WIDTH/FB-HEIGHT are NOT missing a guard ───────
|
||||
Both push via C's `vm_push()` (src/stack_management.c:75), which bounds-
|
||||
checks internally (`if (vm->dsp >= STACK_SIZE - 1) ...`) before every
|
||||
write -- this file's earlier claim that neither checks capacity was a
|
||||
gap in this theory's abstract push model, not a real defect in the C.
|
||||
Re-verified 2026-08-14 during the sweep's repair pass; see
|
||||
proof/FINDINGS.md §2 for the full correction across all files this
|
||||
pattern was raised against.
|
||||
======================================================================== *)
|
||||
|
||||
(* ── PLOT ( x y color -- ) : hosted build ─────────────────────────────── *)
|
||||
@@ -67,7 +68,10 @@ lemma plot_kernel_not_modelled: True
|
||||
by simp
|
||||
|
||||
(* ── FB-WIDTH / FB-HEIGHT ( -- n ) : hosted build ─────────────────────── *)
|
||||
(* C: hosted branch pushes 0 unconditionally, no capacity guard at all. *)
|
||||
(* C: hosted branch pushes 0 via vm_push(), which bounds-checks internally
|
||||
(see corrected header finding above) -- the abstract push below still
|
||||
models an unconditional push since this theory's stack has no depth
|
||||
bound to violate, but the real C is guarded. *)
|
||||
|
||||
definition forth_fb_width_hosted :: "vm_state \<Rightarrow> vm_state" where
|
||||
"forth_fb_width_hosted vm = vm\<lparr>data_stack := 0 # data_stack vm\<rparr>"
|
||||
|
||||
@@ -144,8 +144,12 @@ lemma infer_early_exit_fetch_some_false:
|
||||
using assms by (simp add: forth_infer_early_exit_fetch_def)
|
||||
|
||||
lemma infer_fetch_words_no_overflow_guard: True
|
||||
\<comment> \<open>All five push unconditionally, no ds_full check -- another instance
|
||||
of the recurring missing-overflow-guard pattern.\<close>
|
||||
\<comment> \<open>CORRECTED 2026-08-14: all five push via VM_PUSH (include/vm.h:706-719),
|
||||
which resolves to the bounds-checked vm_push() in every build except one
|
||||
defining STARFORTH_PERFORMANCE -- confirmed by repo-wide grep to never be
|
||||
defined by any Makefile/Kconfig target here. Real but dormant hazard, not
|
||||
a live per-word bug; same finding as StarForth_Q48_Words.thy's header.
|
||||
See proof/FINDINGS.md \<section>2.\<close>
|
||||
by simp
|
||||
|
||||
(* ── Q.VARIANCE / INFER-DECAY-SLOPE / INFER-WINDOW-WIDTH
|
||||
@@ -233,15 +237,15 @@ lemma l8_table_force_target_not_modelled: True
|
||||
|
||||
(* ── Everything else -- NOT MODELLED ──────────────────────────────────── *)
|
||||
|
||||
lemma window_diversity_not_modelled: True \<comment> \<open>WINDOW-DIVERSITY: rolling_window_measure_diversity computes a fresh value from rw_history (a modelled field, but the diversity ALGORITHM over it is not) -- distinct from the already-stored rw_last_diversity. No overflow guard either.\<close>
|
||||
lemma window_diversity_not_modelled: True \<comment> \<open>WINDOW-DIVERSITY: rolling_window_measure_diversity computes a fresh value from rw_history (a modelled field, but the diversity ALGORITHM over it is not) -- distinct from the already-stored rw_last_diversity. Push is via VM_PUSH -- see corrected header note near infer_fetch_words_no_overflow_guard.\<close>
|
||||
by simp
|
||||
lemma infer_run_not_modelled: True \<comment> \<open>INFER-RUN: allocates last_inference_outputs on first call, walks the dictionary read-only for heat stats, then runs inference_engine_run -- a whole-subsystem algorithm. This is the ONLY word that writes `last_inference`, so no fetch-after-INFER-RUN lemma can be stated.\<close>
|
||||
by simp
|
||||
lemma l8_mode_not_modelled: True \<comment> \<open>L8-MODE: reads vm->ssm_l8_state's legacy 16-mode int -- see file header's representation-mismatch finding. No overflow guard.\<close>
|
||||
lemma l8_mode_not_modelled: True \<comment> \<open>L8-MODE: reads vm->ssm_l8_state's legacy 16-mode int -- see file header's representation-mismatch finding. Push is via VM_PUSH -- see corrected header note near infer_fetch_words_no_overflow_guard.\<close>
|
||||
by simp
|
||||
lemma l8_apply_not_modelled: True \<comment> \<open>L8-APPLY: no-op if !l8||!cfg, else ssm_apply_mode -- targets the same unmodelled legacy L8 state as L8-UPDATE.\<close>
|
||||
by simp
|
||||
lemma bayes_cache_mean_not_modelled: True \<comment> \<open>BAYES-CACHE-MEAN: hotwords_posterior_cache_hits over the unmodelled hot-words cache subsystem (StarForth_Physics_Benchmark_Words.thy). No overflow guard.\<close>
|
||||
lemma bayes_cache_mean_not_modelled: True \<comment> \<open>BAYES-CACHE-MEAN: hotwords_posterior_cache_hits over the unmodelled hot-words cache subsystem (StarForth_Physics_Benchmark_Words.thy). Push is via VM_PUSH -- see corrected header note near infer_fetch_words_no_overflow_guard.\<close>
|
||||
by simp
|
||||
lemma bayes_cache_lower_not_modelled: True by simp
|
||||
lemma bayes_cache_upper_not_modelled: True by simp
|
||||
|
||||
@@ -18,11 +18,11 @@ begin
|
||||
── Scope ─────────────────────────────────────────────────────────────
|
||||
The fallback (non-kernel-or-wrong-arch) behaviour of all six words is
|
||||
fully modelled: every one reduces to a fixed constant push (or, for
|
||||
ALT+TAB, a true no-op) with NO capacity guard before pushing -- the
|
||||
third and fourth instances of the missing-overflow-guard finding first
|
||||
raised in physics_freeze_words.c's DECAY-RATE@ (KBD-SCAN/VKBD-EVENT/
|
||||
VKBD-DEBUG/KEY-EVENT push 1-2 cells unconditionally; KBD-DEBUG pushes
|
||||
2). The real (matching kernel+arch) hardware-polling bodies are NOT
|
||||
ALT+TAB, a true no-op). CORRECTED 2026-08-14: all six push via C's
|
||||
`vm_push()` (src/stack_management.c:75), which bounds-checks internally
|
||||
-- the earlier "no capacity guard" claim here was a gap in this theory's
|
||||
abstract push model, not a real defect in the C; see proof/FINDINGS.md
|
||||
§2. The real (matching kernel+arch) hardware-polling bodies are NOT
|
||||
modelled: i8042_pop_scancode/virtio_input_pop_event/
|
||||
console_fb_toggle_graphics are all raw hardware/interrupt-state reads
|
||||
with no vm_state counterpart, the same class of gap as every other
|
||||
|
||||
@@ -29,13 +29,13 @@ begin
|
||||
on vm_find_word + vm_compile_word + vm_allot -- several already-flagged
|
||||
gaps compounded in one helper).
|
||||
|
||||
── Finding: three more push-only words with no overflow guard ─────────
|
||||
LOG-ERROR/WARN/INFO/TEST/DEBUG and LOG-LEVEL@ push unconditionally with
|
||||
no `ds_full` check -- the pattern first found at DECAY-RATE@
|
||||
(physics_freeze_words.c) and repeated across framebuffer_words.c/
|
||||
keyboard_words.c keeps recurring specifically in "just returns a
|
||||
constant/global" words; worth citing log_words.c as further evidence
|
||||
when this goes to Bob as an aggregated pattern rather than one-offs.
|
||||
── CORRECTED finding: LOG-* words are NOT missing a guard ─────────────
|
||||
LOG-ERROR/WARN/INFO/TEST/DEBUG and LOG-LEVEL@ all push via C's
|
||||
`vm_push()` (src/stack_management.c:75), which bounds-checks internally
|
||||
-- this file's earlier claim of a missing `ds_full` check was a gap in
|
||||
this theory's abstract push model, not a real defect in the C.
|
||||
Re-verified 2026-08-14; see proof/FINDINGS.md §2 for the full
|
||||
correction across every file this pattern was raised against.
|
||||
======================================================================== *)
|
||||
|
||||
definition LOG_ERROR_LEVEL :: cell where "LOG_ERROR_LEVEL = 0"
|
||||
@@ -120,7 +120,8 @@ lemma log_level_store_target_not_modelled: True
|
||||
|
||||
lemma log_level_fetch_not_modelled: True
|
||||
\<comment> \<open>Pushes log_get_level(), reading the same unmodelled global
|
||||
LOG-LEVEL! writes to. No overflow guard either -- see file header.\<close>
|
||||
LOG-LEVEL! writes to. Push is via vm_push(), which bounds-checks --
|
||||
see corrected file header.\<close>
|
||||
by simp
|
||||
|
||||
(* ── (do-log-N) runtime words -- NOT MODELLED ─────────────────────────── *)
|
||||
|
||||
@@ -28,7 +28,12 @@ begin
|
||||
`STARFORTH_PERFORMANCE`) path, consistent with this suite's general
|
||||
assumption that stack words check bounds; the fast-path variant is
|
||||
flagged, not modelled, since "no check at all" has no useful lemma to
|
||||
state beyond "anything can happen."
|
||||
state beyond "anything can happen." CONFIRMED 2026-08-14: repo-wide grep
|
||||
shows `STARFORTH_PERFORMANCE` is never defined by any Makefile or
|
||||
Kconfig target in this repo -- only referenced inside vm.h itself and
|
||||
stack_words.c. So under every configuration this repo currently builds,
|
||||
this file's words are on the safe path; the hazard is real but dormant,
|
||||
contingent on a build flag nothing sets today.
|
||||
|
||||
── Q48 operations reused from StarForth_Q48_16.thy ─────────────────────
|
||||
q48_add/q48_sub/q48_mul/q48_div/q48_from_u64/q48_to_u64 were already
|
||||
|
||||
@@ -711,100 +711,14 @@ static void defining_word_immediate(VM *vm) {
|
||||
}
|
||||
|
||||
/* ───────────────────────────── DEFER / IS ─────────────────────────── */
|
||||
|
||||
/* DEFER runtime: execute the XT stored in this word's data field */
|
||||
static void defining_runtime_defer(VM *vm) {
|
||||
if (!vm) return;
|
||||
DictEntry *self = vm->current_executing_entry;
|
||||
if (!self) { vm->error = 1; return; }
|
||||
|
||||
cell_t *df = vm_dictionary_get_data_field(self);
|
||||
if (!df) { vm->error = 1; return; }
|
||||
|
||||
DictEntry *target = (DictEntry *)(uintptr_t)(*df);
|
||||
if (!target || !target->func) {
|
||||
log_message(LOG_ERROR, "DEFER %.*s: uninitialized — use IS to set",
|
||||
(int)self->name_len, self->name);
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
vm->current_executing_entry = target;
|
||||
target->func(vm);
|
||||
}
|
||||
|
||||
/* DEFER ( "name" -- ) create a deferred word with an empty XT slot */
|
||||
static void defining_word_defer(VM *vm) {
|
||||
if (!vm) return;
|
||||
|
||||
char namebuf[WORD_NAME_MAX + 1];
|
||||
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
|
||||
if (nlen <= 0) { vm->error = 1; return; }
|
||||
|
||||
DictEntry *entry = vm_create_word(vm, namebuf, (size_t)nlen, defining_runtime_defer);
|
||||
if (!entry) { vm->error = 1; return; }
|
||||
|
||||
cell_t *df = vm_dictionary_get_data_field(entry);
|
||||
if (!df) { vm->error = 1; return; }
|
||||
*df = 0;
|
||||
|
||||
log_message(LOG_DEBUG, "DEFER: '%.*s'", nlen, namebuf);
|
||||
}
|
||||
|
||||
/* IS ( xt "name" -- ) store xt into the deferred word's slot */
|
||||
static void defining_word_is(VM *vm) {
|
||||
if (!vm) return;
|
||||
if (vm->dsp < 0) {
|
||||
log_message(LOG_ERROR, "IS: stack underflow");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
cell_t xt = vm_pop(vm);
|
||||
|
||||
char namebuf[WORD_NAME_MAX + 1];
|
||||
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
|
||||
if (nlen <= 0) { vm->error = 1; return; }
|
||||
|
||||
DictEntry *entry = vm_find_word(vm, namebuf, (size_t)nlen);
|
||||
if (!entry) {
|
||||
log_message(LOG_ERROR, "IS: '%.*s' not found", nlen, namebuf);
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
if (entry->func != defining_runtime_defer) {
|
||||
log_message(LOG_ERROR, "IS: '%.*s' is not a DEFER word", nlen, namebuf);
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t *df = vm_dictionary_get_data_field(entry);
|
||||
if (!df) { vm->error = 1; return; }
|
||||
*df = xt;
|
||||
|
||||
log_message(LOG_DEBUG, "IS: '%.*s' <- XT %p", nlen, namebuf, (void *)(uintptr_t)xt);
|
||||
}
|
||||
|
||||
/* DEFER@ ( "name" -- xt ) fetch the XT stored in a deferred word */
|
||||
static void defining_word_defer_fetch(VM *vm) {
|
||||
if (!vm) return;
|
||||
|
||||
char namebuf[WORD_NAME_MAX + 1];
|
||||
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
|
||||
if (nlen <= 0) { vm->error = 1; return; }
|
||||
|
||||
DictEntry *entry = vm_find_word(vm, namebuf, (size_t)nlen);
|
||||
if (!entry) {
|
||||
log_message(LOG_ERROR, "DEFER@: '%.*s' not found", nlen, namebuf);
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t *df = vm_dictionary_get_data_field(entry);
|
||||
if (!df) { vm->error = 1; return; }
|
||||
vm_push(vm, *df);
|
||||
|
||||
log_message(LOG_DEBUG, "DEFER@: '%.*s' -> XT %p", nlen, namebuf, (void *)(uintptr_t)(*df));
|
||||
}
|
||||
/* DEFER/IS/DEFER@ are NOT defined here: defer_words.c registers the same
|
||||
* three names later in boot order (word_registry.c Module 27 vs this
|
||||
* file's Module 17), and it has no __STARKERNEL__ guard, so it shadows
|
||||
* this file's versions in both hosted and kernel builds. FORTH's
|
||||
* newest-first dictionary lookup means defer_words.c's versions (a
|
||||
* self-contained implementation, unrelated to this file's former
|
||||
* defining_runtime_defer) are the only ones ever reachable. See
|
||||
* proof/StarForth_Defer_Words.thy and proof/FINDINGS.md §3. */
|
||||
|
||||
/* ───────────────────────────── Registration ───────────────────────── */
|
||||
|
||||
@@ -858,8 +772,4 @@ void register_defining_words(VM *vm) {
|
||||
register_word(vm, "DOES>", defining_word_does);
|
||||
vm_make_immediate(vm);
|
||||
|
||||
/* DEFER / IS / DEFER@ */
|
||||
register_word(vm, "DEFER", defining_word_defer);
|
||||
register_word(vm, "IS", defining_word_is);
|
||||
register_word(vm, "DEFER@", defining_word_defer_fetch);
|
||||
}
|
||||
|
||||
@@ -111,35 +111,6 @@ static char *traverse_name_field(char *name_addr, int direction) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FORTH word [ - Enter interpretation mode
|
||||
* @param vm Pointer to VM instance
|
||||
* @stack ( -- )
|
||||
*/
|
||||
void dictionary_m_word_left_bracket(VM *vm) {
|
||||
vm->mode = MODE_INTERPRET;
|
||||
state_variable = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FORTH word ] - Enter compilation mode
|
||||
* @param vm Pointer to VM instance
|
||||
* @stack ( -- )
|
||||
*/
|
||||
void dictionary_m_word_right_bracket(VM *vm) {
|
||||
vm->mode = MODE_COMPILE;
|
||||
state_variable = -1; /* FORTH-79 uses -1 for true */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FORTH word STATE - Get compilation state variable address
|
||||
* @param vm Pointer to VM instance
|
||||
* @stack ( -- addr )
|
||||
*/
|
||||
void dictionary_m_word_state(VM *vm) {
|
||||
vm_push(vm, (cell_t)(uintptr_t) & state_variable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FORTH word SMUDGE - Toggle smudge bit of latest word
|
||||
* @param vm Pointer to VM instance
|
||||
@@ -473,9 +444,13 @@ static void dictionary_m_word_hidden(VM *vm) {
|
||||
void register_dictionary_manipulation_words(VM *vm) {
|
||||
|
||||
/* Register all dictionary manipulation words */
|
||||
register_word(vm, "[", dictionary_m_word_left_bracket);
|
||||
register_word(vm, "]", dictionary_m_word_right_bracket);
|
||||
register_word(vm, "STATE", dictionary_m_word_state);
|
||||
/* [, ], STATE are NOT registered here: defining_words.c registers the
|
||||
* same three names later in boot order (word_registry.c Module 17 vs
|
||||
* this file's Module 13), and FORTH's newest-first dictionary lookup
|
||||
* means defining_words.c's versions are the only ones ever reachable.
|
||||
* The versions formerly here also wrote a dead file-scope static
|
||||
* instead of vm->state_addr -- see proof/StarForth_Defining_Words.thy
|
||||
* and proof/FINDINGS.md §1. */
|
||||
register_word(vm, "SMUDGE", dictionary_m_word_smudge);
|
||||
register_word(vm, "HIDDEN", dictionary_m_word_hidden);
|
||||
register_word(vm, ">BODY", dictionary_m_word_to_body);
|
||||
|
||||
@@ -370,11 +370,16 @@ void forth_ALL_HEATS(VM *vm) {
|
||||
*
|
||||
* Stack effect: ( -- rate )
|
||||
*
|
||||
* @param vm Active VM; no-op if @c vm->error is set
|
||||
* @param vm Active VM; sets @c vm->error = 1 on stack overflow
|
||||
*/
|
||||
void forth_DECAY_RATE_FETCH(VM *vm) {
|
||||
if (vm->error) return;
|
||||
|
||||
if (vm->dsp >= STACK_SIZE) {
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Stack: ( -- rate ) */
|
||||
vm->data_stack[vm->dsp++] = (cell_t)DECAY_RATE_PER_US_Q16;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user