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>
469 lines
13 KiB
C
469 lines
13 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.
|
||
|
||
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.
|
||
|
||
*/
|
||
|
||
/* dictionary_manipulation_words.c - FORTH-79 Dictionary Manipulation Words */
|
||
#include "include/dictionary_manipulation_words.h"
|
||
#include "../../include/word_registry.h"
|
||
#include "../../include/log.h"
|
||
|
||
/* Global state for dictionary manipulation */
|
||
static cell_t state_variable = 0; /* STATE - compilation state */
|
||
|
||
/* Helper function to calculate aligned address */
|
||
static uintptr_t align_address(uintptr_t addr) {
|
||
size_t alignment = sizeof(cell_t);
|
||
return (addr + alignment - 1) & ~(alignment - 1);
|
||
}
|
||
|
||
/* Helper function to get name field from execution token */
|
||
static char *get_name_field(DictEntry *entry) {
|
||
if (entry == NULL) {
|
||
return NULL;
|
||
}
|
||
return (char *) entry->name;
|
||
}
|
||
|
||
/* Helper function to get body address from execution token */
|
||
static void *get_body_address(DictEntry *entry) {
|
||
if (entry == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
/* Body follows the header structure */
|
||
uintptr_t body_addr = (uintptr_t) entry + sizeof(DictEntry) + entry->name_len;
|
||
return (void *) align_address(body_addr);
|
||
}
|
||
|
||
/* Helper function to find dictionary entry from name field */
|
||
static DictEntry *find_entry_from_name(VM *vm, char *name_field) {
|
||
DictEntry *entry;
|
||
|
||
entry = vm->latest;
|
||
while (entry != NULL) {
|
||
if ((char *) entry->name == name_field) {
|
||
return entry;
|
||
}
|
||
entry = entry->link;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* Helper function to traverse name field */
|
||
static char *traverse_name_field(char *name_addr, int direction) {
|
||
uint8_t *ptr;
|
||
uint8_t name_len;
|
||
|
||
if (name_addr == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
/* FORTH-79 names start with length byte */
|
||
ptr = (uint8_t *) name_addr;
|
||
|
||
if (direction > 0) {
|
||
/* Forward traversal - skip over name */
|
||
name_len = *ptr & 0x1F; /* Mask off flags */
|
||
return (char *) (ptr + name_len + 1);
|
||
} else {
|
||
/* Backward traversal - find start of name */
|
||
/* This is more complex - we need to scan backwards */
|
||
/* For simplicity, assume we're at the start already */
|
||
return name_addr;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief FORTH word SMUDGE - Toggle smudge bit of latest word
|
||
* @param vm Pointer to VM instance
|
||
* @stack ( -- )
|
||
* @note Compile-only word
|
||
*/
|
||
static void dictionary_m_word_smudge(VM *vm) {
|
||
// compile-only: error if used while interpreting
|
||
if (vm->mode != MODE_COMPILE) {
|
||
// correct
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
// existing body that toggles/sets the smudge/hidden bit on the *latest* word
|
||
// (leave your current logic exactly as-is)
|
||
}
|
||
|
||
/* >BODY ( xt -- addr ) Convert execution token to body */
|
||
void dictionary_m_word_to_body(VM *vm) {
|
||
if (vm->dsp < 0) {
|
||
log_message(LOG_ERROR, ">BODY: Stack underflow");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
cell_t xt = vm_pop(vm);
|
||
log_message(LOG_DEBUG, ">BODY: POP: %ld (dsp=%d)", (long) xt, vm->dsp);
|
||
|
||
DictEntry *entry = (DictEntry *) (intptr_t) xt;
|
||
if (!entry) {
|
||
log_message(LOG_ERROR, ">BODY: Invalid execution token (NULL)");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
// Use the VM's dictionary function to get the data field
|
||
cell_t *data_field = vm_dictionary_get_data_field(entry);
|
||
if (!data_field) {
|
||
log_message(LOG_ERROR, ">BODY: Unable to get data field address for entry %p", (void *) entry);
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
cell_t body_addr = (cell_t)(intptr_t)data_field;
|
||
log_message(LOG_DEBUG, ">BODY: xt=%p -> body=%p", (void *) entry, (void *) data_field);
|
||
vm_push(vm, body_addr);
|
||
log_message(LOG_DEBUG, ">BODY: PUSH: %ld (dsp=%d)", (long) body_addr, vm->dsp);
|
||
}
|
||
|
||
/* >NAME ( xt -- addr ) Convert execution token to name */
|
||
void dictionary_m_word_to_name(VM *vm) {
|
||
cell_t xt;
|
||
DictEntry *entry;
|
||
char *name_field;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
xt = vm_pop(vm);
|
||
entry = (DictEntry *) (uintptr_t) xt;
|
||
|
||
name_field = get_name_field(entry);
|
||
if (name_field == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, (cell_t)(uintptr_t)name_field);
|
||
}
|
||
|
||
/* NAME> ( addr -- xt ) Convert name to execution token */
|
||
void dictionary_m_word_name_to(VM *vm) {
|
||
cell_t addr;
|
||
char *name_field;
|
||
DictEntry *entry;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
name_field = (char *) (uintptr_t) addr;
|
||
|
||
entry = find_entry_from_name(vm, name_field);
|
||
if (entry == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, (cell_t)(uintptr_t)entry);
|
||
}
|
||
|
||
/* >LINK ( addr -- addr ) Get link field address */
|
||
void dictionary_m_word_to_link(VM *vm) {
|
||
cell_t addr;
|
||
DictEntry *entry;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
entry = (DictEntry *) (uintptr_t) addr;
|
||
|
||
if (entry == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
/* Link field is part of the DictEntry structure */
|
||
vm_push(vm, (cell_t)(uintptr_t) & entry->link);
|
||
}
|
||
|
||
/* LINK> ( addr -- addr ) Get next word from link */
|
||
void dictionary_m_word_link_from(VM *vm) {
|
||
cell_t addr;
|
||
DictEntry **link_field;
|
||
DictEntry *next_entry;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
link_field = (DictEntry **) (uintptr_t) addr;
|
||
|
||
if (link_field == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
next_entry = *link_field;
|
||
vm_push(vm, (cell_t)(uintptr_t)next_entry);
|
||
}
|
||
|
||
/* CFA ( addr -- xt ) Get code field address */
|
||
void dictionary_m_word_cfa(VM *vm) {
|
||
cell_t addr;
|
||
DictEntry *entry;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
entry = (DictEntry *) (uintptr_t) addr;
|
||
|
||
if (entry == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
/* In our implementation, the execution token is the entry itself */
|
||
vm_push(vm, (cell_t)(uintptr_t)entry);
|
||
}
|
||
|
||
/* LFA ( addr -- addr ) Get link field address */
|
||
void dictionary_m_word_lfa(VM *vm) {
|
||
dictionary_m_word_to_link(vm); /* Same as >LINK */
|
||
}
|
||
|
||
/* NFA ( addr -- addr ) Get name field address */
|
||
void dictionary_m_word_nfa(VM *vm) {
|
||
cell_t addr;
|
||
DictEntry *entry;
|
||
char *name_field;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
entry = (DictEntry *) (uintptr_t) addr;
|
||
|
||
name_field = get_name_field(entry);
|
||
if (name_field == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, (cell_t)(uintptr_t)name_field);
|
||
}
|
||
|
||
/* PFA ( addr -- addr ) Get parameter field address */
|
||
void dictionary_m_word_pfa(VM *vm) {
|
||
cell_t addr;
|
||
DictEntry *entry;
|
||
void *body_addr;
|
||
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
addr = vm_pop(vm);
|
||
entry = (DictEntry *) (uintptr_t) addr;
|
||
|
||
body_addr = get_body_address(entry);
|
||
if (body_addr == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, (cell_t)(uintptr_t)body_addr);
|
||
}
|
||
|
||
/* TRAVERSE ( addr n -- addr ) Move through name field */
|
||
void dictionary_m_word_traverse(VM *vm) {
|
||
cell_t n, addr;
|
||
char *name_addr;
|
||
char *result_addr;
|
||
|
||
if (vm->dsp < 1) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
n = vm_pop(vm);
|
||
addr = vm_pop(vm);
|
||
|
||
name_addr = (char *) (uintptr_t) addr;
|
||
result_addr = traverse_name_field(name_addr, (int) n);
|
||
|
||
if (result_addr == NULL) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, (cell_t)(uintptr_t)result_addr);
|
||
}
|
||
|
||
/* INTERPRET ( -- ) Text interpreter */
|
||
void dictionary_m_word_interpret(VM *vm) {
|
||
/* Simply call the VM's built-in interpreter - it handles everything */
|
||
/* The VM already has proper input management, word parsing, and execution */
|
||
|
||
/* In FORTH-79, INTERPRET processes the current input stream */
|
||
/* Since vm_interpret handles input parsing and execution, we just ensure */
|
||
/* we're in the right mode and let the VM do the work */
|
||
|
||
/* Set interpretation mode */
|
||
vm->mode = MODE_INTERPRET;
|
||
state_variable = 0;
|
||
|
||
/* The actual interpretation happens through vm_interpret() calls */
|
||
/* This word exists mainly for completeness and mode setting */
|
||
}
|
||
|
||
static void dictionary_m_word_find(VM *vm) {
|
||
char namebuf[128];
|
||
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
|
||
if (nlen <= 0) {
|
||
vm->error = 1; // real input underflow
|
||
return;
|
||
}
|
||
|
||
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
|
||
if (e) {
|
||
vm_push(vm, (cell_t)(uintptr_t)e); // compilation address; swap to CFA if you prefer later
|
||
} else {
|
||
vm_push(vm, 0); // miss is NOT an error
|
||
}
|
||
}
|
||
|
||
/* ' ( -- xt ) FORTH-79 tick: parse next word, push its execution token.
|
||
* Non-immediate — in compile mode the interpreter compiles a call to ',
|
||
* so this function only ever runs in interpret mode. */
|
||
static void dictionary_m_word_tick(VM *vm) {
|
||
char namebuf[128];
|
||
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
|
||
if (nlen <= 0) {
|
||
log_message(LOG_ERROR, "': unable to parse word");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
|
||
if (!e) {
|
||
log_message(LOG_ERROR, "': word '%.*s' not found", nlen, namebuf);
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
log_message(LOG_DEBUG, "': found '%.*s' xt=%p", nlen, namebuf, (void *) e);
|
||
vm_push(vm, (cell_t)(uintptr_t)e);
|
||
}
|
||
|
||
// in src/word_source/dictionary_manipulation_words.c
|
||
static void dictionary_m_word_hidden(VM *vm) {
|
||
// compile-only guard (matches your other compile-only words)
|
||
if (vm->mode != MODE_COMPILE) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
// get the latest entry (use whatever you use elsewhere; many places access vm->latest)
|
||
DictEntry *e = vm->latest;
|
||
if (!e) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
// Set the hidden/smudge flag. Prefer a named flag if you have it.
|
||
// If your codebase uses WORD_HIDDEN, use that.
|
||
// If it uses the same bit as SMUDGE, use that (often called WORD_SMUDGED).
|
||
#ifdef WORD_HIDDEN
|
||
e->flags |= WORD_HIDDEN;
|
||
#else
|
||
// Fallback: if you only have a toggling SMUDGE path, set conditionally using it.
|
||
if (!(e->flags & WORD_SMUDGED)) {
|
||
// call your existing SMUDGE implementation to flip it on once
|
||
dictionary_word_smudge(vm);
|
||
// ensure we didn't accidentally set an error
|
||
if (vm->error) return;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
* @brief Register all dictionary manipulation words with the VM
|
||
* @param vm Pointer to VM instance
|
||
* @details Registers standard FORTH-79 dictionary manipulation words
|
||
*/
|
||
void register_dictionary_manipulation_words(VM *vm) {
|
||
|
||
/* Register all dictionary manipulation words */
|
||
/* [, ], 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);
|
||
register_word(vm, ">NAME", dictionary_m_word_to_name);
|
||
register_word(vm, "NAME>", dictionary_m_word_name_to);
|
||
register_word(vm, ">LINK", dictionary_m_word_to_link);
|
||
register_word(vm, "LINK>", dictionary_m_word_link_from);
|
||
register_word(vm, "CFA", dictionary_m_word_cfa);
|
||
register_word(vm, "LFA", dictionary_m_word_lfa);
|
||
register_word(vm, "NFA", dictionary_m_word_nfa);
|
||
register_word(vm, "PFA", dictionary_m_word_pfa);
|
||
register_word(vm, "TRAVERSE", dictionary_m_word_traverse);
|
||
register_word(vm, "INTERPRET", dictionary_m_word_interpret);
|
||
register_word(vm, "FIND", dictionary_m_word_find);
|
||
register_word(vm, "'", dictionary_m_word_tick);
|
||
} |