Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization: - Move StarForth v3 engine to v3/ (src/, include/, Makefile) - Move kernel to kernel/ (src/, include/, linker/, Makefile) - Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md) - Move FABRIC-0..4.md to docs/fabric/ - Move ONTOLOGY.md and ROADMAP.md to docs/ Board infrastructure: - Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/ - Each board has board.mk (ISA, CPU flags, boot recipe) and README.md - Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET - make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board - ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet) - scripts/mkdiskimage.sh builds disk images for all boards Docs pipeline: - docs/book/ with LaTeX master (main.tex) and Makefile - pandoc converts Markdown to LaTeX at build time - Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks) - make docs builds single PDF (754 pages, 0 missing characters) - make docs TARGET=<board> adds board appendix - build/docs/<book|board>/meta.tex stamps git commit into PDF Bug fixes: - 42 include paths that only worked by accident now use correct relative paths - clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build) - Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved - Doxyfile, .clang-tidy, README.md, Kconfig paths updated Verified: - Hosted v3 build passes 1012 tests, 0 failures - SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE) - Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes - make clean TARGET=<board> removes only that board and its ISA objects - make all builds all boards, hosted v3, and docs in one run Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# src/
|
||||
|
||||
Hosted StarForth VM implementation (compiled by the root `Makefile`).
|
||||
Bare-metal kernel sources live in `src/starkernel/`; FORTH word
|
||||
implementations in `src/word_source/`; the test harness in
|
||||
`src/test_runner/`; platform shims in `src/platform/`.
|
||||
|
||||
**Entry point / interpreter core**
|
||||
- `main.c` — CLI entry point, VM init, DoE mode dispatch.
|
||||
- `vm.c` — interpreter loop, stacks, dictionary state (the central
|
||||
runtime file).
|
||||
- `vm_api.c` — external VM API implementation.
|
||||
- `vm_bootstrap.c` — VM bootstrap initialization.
|
||||
- `vm_debug.c` — debugging utilities.
|
||||
- `vm_time.c` — time-related VM operations.
|
||||
- `vm_internal.h` — internal-only declarations shared across the `vm_*.c`
|
||||
files, not part of the public `include/vm_api.h` surface.
|
||||
- `repl.c` — REPL read-eval-print loop.
|
||||
- `cli.c` — CLI argument parsing.
|
||||
- `io.c` — I/O operations.
|
||||
- `log.c` — logging infrastructure.
|
||||
|
||||
**Memory / dictionary / blocks**
|
||||
- `memory_management.c` — dictionary allocator.
|
||||
- `dictionary_management.c` — dictionary allocation and search.
|
||||
- `dictionary_heat_optimization.c` — Loop #1 execution-heat tracking.
|
||||
- `word_registry.c` — word registration system.
|
||||
- `block_subsystem.c` — logical→physical block mapper.
|
||||
- `blkio_file.c`, `blkio_ram.c`, `blkio_factory.c` — block I/O backends
|
||||
(file-backed, RAM-backed) and the factory that selects between them.
|
||||
- `stack_management.c` — stack operations.
|
||||
|
||||
**Physics-driven adaptive runtime (7 feedback loops)**
|
||||
- `physics_runtime.c` — main physics coordinator.
|
||||
- `physics_hotwords_cache.c` — Loop #1 hot-words caching.
|
||||
- `physics_metadata.c` — per-word metadata tracking.
|
||||
- `physics_pipelining_metrics.c` — Loop #4 word-transition prediction.
|
||||
- `physics_execution_hooks.c` — execution instrumentation.
|
||||
- `rolling_window_of_truth.c` — Loop #2 circular execution-history buffer.
|
||||
- `inference_engine.c` — Loops #5/#6, statistical inference (window-width,
|
||||
decay-slope).
|
||||
- `ssm_jacquard.c` — L8 Jacquard steady-state mode selector; consumes
|
||||
`compudynamics.c` for tuning-word/config lookups.
|
||||
- `compudynamics.c` — generic compudynamics module (`cd_tuning_word()`,
|
||||
`cd_tuning_vm()`); the score/UCB/reward/weight constants for the L8
|
||||
adaptive table live here, not in `ssm_jacquard.c`.
|
||||
- `heartbeat_export.c` — heartbeat metrics export (CSV export function
|
||||
itself not yet implemented — see `docs/working/architecture/heartbeat_csv_export.md`).
|
||||
|
||||
**Math / measurement**
|
||||
- `math_portable.c` — portable math functions.
|
||||
- `profiler.c` — performance profiling.
|
||||
- `doe_metrics.c` — Design of Experiments metrics (2^7 factorial).
|
||||
|
||||
Any `.bak` file alongside a `.c` file here (`doe_metrics.c.bak`,
|
||||
`inference_engine.c.bak`, `vm.c.bak`) is a pre-edit backup left by a past
|
||||
maintenance script (see `scripts/remove_loop_conditionals.sh`), not a
|
||||
build input.
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// StarForth - Block I/O Factory (Implementation)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blkio.h"
|
||||
#include "blkio_factory.h"
|
||||
|
||||
/* Backends */
|
||||
extern size_t blkio_file_state_size(void);
|
||||
|
||||
extern const blkio_vtable_t *blkio_file_vtable(void);
|
||||
|
||||
extern int blkio_file_init_state(void *state_mem, size_t state_len,
|
||||
const char *path,
|
||||
uint32_t total_blocks,
|
||||
uint32_t fbs,
|
||||
uint8_t read_only,
|
||||
uint8_t create_if_missing,
|
||||
uint8_t truncate_to_size,
|
||||
void **out_opaque);
|
||||
|
||||
extern size_t blkio_ram_state_size(void);
|
||||
|
||||
extern const blkio_vtable_t *blkio_ram_vtable(void);
|
||||
|
||||
extern int blkio_ram_init_state(void *state_mem, size_t state_len,
|
||||
uint8_t *base, uint32_t total_blocks,
|
||||
uint32_t fbs, uint8_t read_only,
|
||||
void **out_opaque);
|
||||
|
||||
/** @brief Return 1 if string @c s is non-NULL and non-empty. */
|
||||
static int nonempty(const char *s) { return (s && *s); }
|
||||
|
||||
/**
|
||||
* @brief Open a block I/O device using the best available backend.
|
||||
*
|
||||
* Selects the file backend when @c disk_img_path is non-empty, otherwise
|
||||
* falls back to the RAM backend using @c ram_base / @c ram_blocks.
|
||||
*
|
||||
* File backend behaviour:
|
||||
* - Creates the file if it does not exist (unless @c read_only is set).
|
||||
* - Derives @c total_blocks from the file size when @c total_blocks == 0.
|
||||
* - Sets @c *out_used_file = 1 on success.
|
||||
*
|
||||
* RAM backend behaviour:
|
||||
* - @c ram_base must point to at least @c ram_blocks * @c fbs bytes of
|
||||
* caller-owned memory that outlives the device.
|
||||
*
|
||||
* @param dev Output device handle to populate
|
||||
* @param disk_img_path Path to disk image file, or NULL/empty for RAM
|
||||
* @param read_only 1 = open read-only, 0 = read-write
|
||||
* @param total_blocks Desired block count (0 = derive from file size)
|
||||
* @param fbs FORTH block size in bytes (0 = use @c BLKIO_FORTH_BLOCK_SIZE)
|
||||
* @param state_mem Caller-allocated state buffer (must be large enough)
|
||||
* @param state_len Size of @c state_mem in bytes
|
||||
* @param ram_base Base of RAM block array (used only when no disk path)
|
||||
* @param ram_blocks Number of blocks in @c ram_base
|
||||
* @param out_used_file Optional output: set to 1 if the file backend was used
|
||||
* @return @c BLKIO_OK on success, or a @c BLKIO_E* error code
|
||||
*/
|
||||
int blkio_factory_open(blkio_dev_t *dev,
|
||||
const char *disk_img_path,
|
||||
uint8_t read_only,
|
||||
uint32_t total_blocks,
|
||||
uint32_t fbs,
|
||||
void *state_mem,
|
||||
size_t state_len,
|
||||
uint8_t *ram_base,
|
||||
uint32_t ram_blocks,
|
||||
uint8_t *out_used_file) {
|
||||
if (out_used_file) *out_used_file = 0;
|
||||
if (!dev || !state_mem || state_len == 0) return BLKIO_EINVAL;
|
||||
|
||||
const uint32_t efbs = (fbs ? fbs : BLKIO_FORTH_BLOCK_SIZE);
|
||||
|
||||
if (nonempty(disk_img_path)) {
|
||||
const size_t need = blkio_file_state_size();
|
||||
if (state_len < need) return BLKIO_ENOSPACE;
|
||||
|
||||
void *opaque = NULL;
|
||||
const uint8_t create_if_missing = read_only ? 0u : 1u;
|
||||
const uint8_t truncate_to_size = (!read_only && total_blocks > 0) ? 1u : 0u;
|
||||
|
||||
int rc = blkio_file_init_state(state_mem, state_len,
|
||||
disk_img_path,
|
||||
total_blocks,
|
||||
efbs,
|
||||
read_only,
|
||||
create_if_missing,
|
||||
truncate_to_size,
|
||||
&opaque);
|
||||
if (rc != BLKIO_OK) return rc;
|
||||
|
||||
blkio_params_t p = {
|
||||
.forth_block_size = efbs,
|
||||
.total_blocks = total_blocks, /* 0 => derive in file_open */
|
||||
.opaque = opaque
|
||||
};
|
||||
|
||||
rc = blkio_open(dev, blkio_file_vtable(), &p);
|
||||
if (rc == BLKIO_OK && out_used_file) *out_used_file = 1u;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!ram_base || ram_blocks == 0) return BLKIO_EINVAL;
|
||||
|
||||
const size_t need_ram = blkio_ram_state_size();
|
||||
if (state_len < need_ram) return BLKIO_ENOSPACE;
|
||||
|
||||
void *opaque = NULL;
|
||||
int rc = blkio_ram_init_state(state_mem, state_len,
|
||||
ram_base, ram_blocks,
|
||||
efbs, read_only,
|
||||
&opaque);
|
||||
if (rc != BLKIO_OK) return rc;
|
||||
|
||||
const blkio_params_t p = {
|
||||
.forth_block_size = efbs,
|
||||
.total_blocks = ram_blocks,
|
||||
.opaque = opaque
|
||||
};
|
||||
|
||||
return blkio_open(dev, blkio_ram_vtable(), &p);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// StarForth - RAM Block I/O backend
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#include "blkio.h"
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t *base;
|
||||
uint32_t total_blocks;
|
||||
uint32_t fbs;
|
||||
uint8_t read_only;
|
||||
} blkio_ram_state_t;
|
||||
|
||||
/**
|
||||
* @brief Return the byte size required for a @c blkio_ram_state_t.
|
||||
* @return sizeof(blkio_ram_state_t)
|
||||
*/
|
||||
size_t blkio_ram_state_size(void) { return sizeof(blkio_ram_state_t); }
|
||||
|
||||
/**
|
||||
* @brief Initialise a @c blkio_ram_state_t in caller-provided memory.
|
||||
*
|
||||
* Stores @c base, @c total_blocks, @c fbs (defaulting to
|
||||
* @c BLKIO_FORTH_BLOCK_SIZE if zero), and @c read_only into the state struct
|
||||
* at @c state_mem. Sets @c *out_opaque to point to the state. The caller
|
||||
* owns the @c base memory and must ensure it remains valid for the device lifetime.
|
||||
*
|
||||
* @param state_mem Caller-allocated buffer (must be >= blkio_ram_state_size())
|
||||
* @param state_len Size of @c state_mem in bytes
|
||||
* @param base Pointer to the RAM block array
|
||||
* @param total_blocks Number of FORTH blocks in @c base
|
||||
* @param fbs FORTH block size in bytes (0 = use default)
|
||||
* @param read_only 1 = deny writes, 0 = allow reads and writes
|
||||
* @param out_opaque Output: set to @c state_mem on success
|
||||
* @return @c BLKIO_OK on success, @c BLKIO_EINVAL on bad arguments
|
||||
*/
|
||||
int blkio_ram_init_state(void *state_mem, size_t state_len,
|
||||
uint8_t *base, uint32_t total_blocks,
|
||||
uint32_t fbs, uint8_t read_only,
|
||||
void **out_opaque) {
|
||||
if (!state_mem || state_len < sizeof(blkio_ram_state_t) || !base || !out_opaque)
|
||||
return BLKIO_EINVAL;
|
||||
|
||||
blkio_ram_state_t *st = (blkio_ram_state_t *) state_mem;
|
||||
st->base = base;
|
||||
st->total_blocks = total_blocks;
|
||||
st->fbs = (fbs ? fbs : BLKIO_FORTH_BLOCK_SIZE);
|
||||
st->read_only = read_only ? 1u : 0u;
|
||||
*out_opaque = (void *) st;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
/** @brief Attach state to the device handle and validate parameters. */
|
||||
static int ram_open(blkio_dev_t *dev, const blkio_params_t *p);
|
||||
|
||||
/** @brief Close the RAM device (no-op; caller owns the memory). */
|
||||
static int ram_close(blkio_dev_t * dev);
|
||||
|
||||
/** @brief Read one FORTH block from the RAM array into @c dst. */
|
||||
static int ram_read(blkio_dev_t *dev, uint32_t fblock, void *dst);
|
||||
|
||||
/** @brief Write one FORTH block from @c src into the RAM array. */
|
||||
static int ram_write(blkio_dev_t *dev, uint32_t fblock, const void *src);
|
||||
|
||||
/** @brief Flush the RAM device — always a no-op since data is in memory. */
|
||||
static int ram_flush(blkio_dev_t * dev);
|
||||
|
||||
/** @brief Return device info (block size, count, physical size, read-only flag). */
|
||||
static int ram_info(blkio_dev_t * dev, blkio_info_t * out);
|
||||
|
||||
static const blkio_vtable_t BLKIO_RAM_VT = {
|
||||
.open = ram_open,
|
||||
.close = ram_close,
|
||||
.read = ram_read,
|
||||
.write = ram_write,
|
||||
.flush = ram_flush,
|
||||
.info = ram_info
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return the vtable for the RAM block I/O backend.
|
||||
* @return Pointer to the static @c blkio_vtable_t for RAM devices
|
||||
*/
|
||||
const blkio_vtable_t *blkio_ram_vtable(void) { return &BLKIO_RAM_VT; }
|
||||
|
||||
/**
|
||||
* @brief Test whether @c fb is a valid block number for the RAM device.
|
||||
* @param st RAM state (may be NULL)
|
||||
* @param fb Block number to test
|
||||
* @return 1 if in bounds, 0 otherwise
|
||||
*/
|
||||
static inline int in_bounds(const blkio_ram_state_t *st, uint32_t fb) {
|
||||
return st && (fb < st->total_blocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach the RAM state to the device handle.
|
||||
*
|
||||
* Validates that @c base and @c fbs are non-zero, then sets
|
||||
* @c dev->state, @c dev->forth_block_size, and @c dev->total_blocks.
|
||||
*
|
||||
* @param dev Uninitialised device handle
|
||||
* @param p Parameters with pre-initialised opaque RAM state pointer
|
||||
* @return @c BLKIO_OK on success, @c BLKIO_EINVAL on bad arguments
|
||||
*/
|
||||
static int ram_open(blkio_dev_t *dev, const blkio_params_t *p) {
|
||||
if (!dev || !p || !p->opaque) return BLKIO_EINVAL;
|
||||
blkio_ram_state_t *st = (blkio_ram_state_t *) p->opaque;
|
||||
if (!st->base || st->fbs == 0) return BLKIO_EINVAL;
|
||||
|
||||
dev->state = st;
|
||||
dev->forth_block_size = st->fbs;
|
||||
dev->total_blocks = st->total_blocks;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the RAM device — caller retains ownership of the RAM buffer.
|
||||
*
|
||||
* @param dev Device handle
|
||||
* @return @c BLKIO_OK always (no resources to release)
|
||||
*/
|
||||
static int ram_close(blkio_dev_t *dev) {
|
||||
if (!dev || !dev->state) return BLKIO_EINVAL;
|
||||
return BLKIO_OK; /* caller owns memory */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy one FORTH block from the RAM array to @c dst.
|
||||
*
|
||||
* @param dev Open RAM device
|
||||
* @param fblock Logical block number (must be < @c total_blocks)
|
||||
* @param dst Destination buffer (must be >= @c fbs bytes)
|
||||
* @return @c BLKIO_OK on success, @c BLKIO_EINVAL if out of range or NULL pointer
|
||||
*/
|
||||
static int ram_read(blkio_dev_t *dev, uint32_t fblock, void *dst) {
|
||||
if (!dev || !dst) return BLKIO_EINVAL;
|
||||
blkio_ram_state_t *st = (blkio_ram_state_t *) dev->state;
|
||||
if (!in_bounds(st, fblock)) return BLKIO_EINVAL;
|
||||
const uint8_t *src = st->base + ((size_t) fblock * st->fbs);
|
||||
memcpy(dst, src, st->fbs);
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy one FORTH block from @c src into the RAM array.
|
||||
*
|
||||
* @param dev Open RAM device (must not be read-only)
|
||||
* @param fblock Logical block number (must be < @c total_blocks)
|
||||
* @param src Source buffer (must be >= @c fbs bytes)
|
||||
* @return @c BLKIO_OK on success, @c BLKIO_ENOSUP if read-only,
|
||||
* @c BLKIO_EINVAL if out of range or NULL pointer
|
||||
*/
|
||||
static int ram_write(blkio_dev_t *dev, uint32_t fblock, const void *src) {
|
||||
if (!dev || !src) return BLKIO_EINVAL;
|
||||
blkio_ram_state_t *st = (blkio_ram_state_t *) dev->state;
|
||||
if (st->read_only) return BLKIO_ENOSUP;
|
||||
if (!in_bounds(st, fblock)) return BLKIO_EINVAL;
|
||||
uint8_t *dst = st->base + ((size_t) fblock * st->fbs);
|
||||
memcpy(dst, src, st->fbs);
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flush the RAM device — no-op since data is always in memory.
|
||||
*
|
||||
* @param dev Device handle (unused)
|
||||
* @return @c BLKIO_OK always
|
||||
*/
|
||||
static int ram_flush(blkio_dev_t *dev) {
|
||||
(void) dev;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return device info for the RAM backend.
|
||||
*
|
||||
* Physical sector size equals @c fbs (RAM has no physical sector constraint).
|
||||
* Physical size in bytes = @c fbs * @c total_blocks.
|
||||
*
|
||||
* @param dev Open RAM device
|
||||
* @param out Output struct to populate
|
||||
* @return @c BLKIO_OK on success, @c BLKIO_EINVAL if @c dev or @c out is NULL
|
||||
*/
|
||||
static int ram_info(blkio_dev_t *dev, blkio_info_t *out) {
|
||||
if (!dev || !out) return BLKIO_EINVAL;
|
||||
blkio_ram_state_t *st = (blkio_ram_state_t *) dev->state;
|
||||
out->forth_block_size = st->fbs;
|
||||
out->total_blocks = st->total_blocks;
|
||||
out->phys_sector_size = st->fbs;
|
||||
out->phys_size_bytes = (uint64_t) st->fbs * (uint64_t) st->total_blocks;
|
||||
out->read_only = st->read_only;
|
||||
return BLKIO_OK;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+238
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "cli.h"
|
||||
#include "version.h"
|
||||
|
||||
/* Parse helper for --ram-disk */
|
||||
static int parse_u32(const char* s, uint32_t* out)
|
||||
{
|
||||
if (!s || !*s) return -1;
|
||||
uint64_t v = 0;
|
||||
for (const char* p = s; *p; ++p)
|
||||
{
|
||||
if (*p < '0' || *p > '9') return -1;
|
||||
v = v * 10u + (uint32_t)(*p - '0');
|
||||
if (v > 0xffffffffULL) return -1;
|
||||
}
|
||||
*out = (uint32_t)v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print usage/help information
|
||||
*/
|
||||
void cli_print_usage(const char* program_name)
|
||||
{
|
||||
printf("Usage: %s [OPTIONS]\n", program_name);
|
||||
printf("StarForth - A lightweight Forth virtual machine\n\n");
|
||||
printf("Options:\n");
|
||||
printf(" -s Script/silent mode - suppress prompts and 'ok' output (for piped input)\n");
|
||||
printf(" --break-me 🔥 ULTRA-COMPREHENSIVE diagnostic mode - tests EVERYTHING\n");
|
||||
printf(" --benchmark [N] Run performance benchmarks (default: 1000 iterations)\n");
|
||||
printf(" --log-error Set logging level to ERROR (only errors)\n");
|
||||
printf(" --log-warn Set logging level to WARN (warnings and errors)\n");
|
||||
printf(" --log-info Set logging level to INFO (default)\n");
|
||||
printf(" --log-test Set logging level to TEST (test results_run_01_2025_12_08 only)\n");
|
||||
printf(" --log-debug Set logging level to DEBUG (all messages)\n");
|
||||
printf(" --log-none Disable all logging (maximum performance)\n");
|
||||
printf(" --fail-fast Stop test suite immediately on first failure\n");
|
||||
printf(" --doe Run DoE experiment: output CSV metrics to stdout and exit\n");
|
||||
printf(" --heartbeat-log=<mode> Heartbeat logging mode (off|summary|full)\n");
|
||||
printf(" off: No heartbeat output (default)\n");
|
||||
printf(" summary: Bucket aggregates in main CSV\n");
|
||||
printf(" full: Per-tick CSV to hb/run-<id>.csv\n");
|
||||
printf(" --disk-img=<path> Use raw disk image file at <path>\n");
|
||||
printf(" --ram-disk=<MB> RAM fallback size if no --disk-img (default: 1 MB)\n");
|
||||
printf(" --version, -v Show version information and exit\n");
|
||||
printf(" --help, -h Show this help message\n\n");
|
||||
printf("Examples:\n");
|
||||
printf(" %s # Start REPL with INFO logging\n", program_name);
|
||||
printf(" %s -s < script.forth # Run script from stdin (no prompts)\n", program_name);
|
||||
printf(" %s --benchmark # Run benchmarks with 1000 iterations\n", program_name);
|
||||
printf(" %s --disk-img=./disks/starship.img # Use disk image\n", program_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse command-line arguments into CLIConfig
|
||||
*/
|
||||
CLIConfig cli_parse(int argc, char* argv[])
|
||||
{
|
||||
CLIConfig config = {
|
||||
/* Test modes - all default to 0 */
|
||||
.run_tests = 0,
|
||||
.break_me = 0,
|
||||
.fail_fast = 0,
|
||||
.run_tests_flag_observed = 0,
|
||||
|
||||
/* Benchmark */
|
||||
.benchmark = 0,
|
||||
.benchmark_iterations = 1000,
|
||||
|
||||
/* Design of Experiments */
|
||||
.doe_experiment = 0,
|
||||
.heartbeat_log_mode = HEARTBEAT_LOG_OFF,
|
||||
|
||||
/* REPL mode */
|
||||
.script_mode = 0,
|
||||
|
||||
/* Logging */
|
||||
.log_level = LOG_INFO,
|
||||
.log_level_explicitly_set = 0,
|
||||
|
||||
/* Block I/O */
|
||||
.disk_img_path = NULL,
|
||||
.ram_disk_mb = 1,
|
||||
};
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "--run-tests") == 0)
|
||||
{
|
||||
config.run_tests_flag_observed = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--break-me") == 0)
|
||||
{
|
||||
config.break_me = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--benchmark") == 0)
|
||||
{
|
||||
config.benchmark = 1;
|
||||
if (i + 1 < argc && atoi(argv[i + 1]) > 0)
|
||||
{
|
||||
config.benchmark_iterations = atoi(argv[i + 1]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-error") == 0)
|
||||
{
|
||||
config.log_level = LOG_ERROR;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-warn") == 0)
|
||||
{
|
||||
config.log_level = LOG_WARN;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-info") == 0)
|
||||
{
|
||||
config.log_level = LOG_INFO;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-test") == 0)
|
||||
{
|
||||
config.log_level = LOG_TEST;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-debug") == 0)
|
||||
{
|
||||
config.log_level = LOG_DEBUG;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--log-none") == 0)
|
||||
{
|
||||
config.log_level = LOG_NONE;
|
||||
config.log_level_explicitly_set = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--fail-fast") == 0)
|
||||
{
|
||||
config.fail_fast = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--doe-experiment") == 0 || strcmp(argv[i], "--doe") == 0)
|
||||
{
|
||||
config.doe_experiment = 1;
|
||||
}
|
||||
else if (strncmp(argv[i], "--heartbeat-log=", 16) == 0)
|
||||
{
|
||||
const char* mode_str = argv[i] + 16;
|
||||
if (strcmp(mode_str, "off") == 0) {
|
||||
config.heartbeat_log_mode = HEARTBEAT_LOG_OFF;
|
||||
} else if (strcmp(mode_str, "summary") == 0) {
|
||||
config.heartbeat_log_mode = HEARTBEAT_LOG_SUMMARY;
|
||||
} else if (strcmp(mode_str, "full") == 0) {
|
||||
config.heartbeat_log_mode = HEARTBEAT_LOG_FULL;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid --heartbeat-log value: %s (expected off|summary|full)\n", mode_str);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (strcmp(argv[i], "-s") == 0)
|
||||
{
|
||||
config.script_mode = 1;
|
||||
}
|
||||
else if (strncmp(argv[i], "--disk-img=", 11) == 0)
|
||||
{
|
||||
config.disk_img_path = argv[i] + 11;
|
||||
}
|
||||
else if (strncmp(argv[i], "--ram-disk=", 11) == 0)
|
||||
{
|
||||
uint32_t tmp = 0;
|
||||
if (parse_u32(argv[i] + 11, &tmp) != 0 || tmp == 0)
|
||||
{
|
||||
fprintf(stderr, "Invalid --ram-disk value: %s (positive MB expected)\n", argv[i] + 11);
|
||||
exit(1);
|
||||
}
|
||||
config.ram_disk_mb = tmp;
|
||||
}
|
||||
else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0)
|
||||
{
|
||||
printf("%s\n", STARFORTH_VERSION_FULL);
|
||||
exit(0);
|
||||
}
|
||||
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
|
||||
{
|
||||
cli_print_usage(argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Unknown option: %s\n", argv[i]);
|
||||
cli_print_usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include "vm.h"
|
||||
#include "log.h"
|
||||
#include "platform_time.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* @brief Capture current VM metrics into a heartbeat tick snapshot
|
||||
*
|
||||
* Collects 11 metrics per tick for multivariate dynamics analysis.
|
||||
* Called every heartbeat tick to record VM state.
|
||||
*
|
||||
* @param vm Pointer to the VM instance
|
||||
* @param snapshot Pointer to snapshot structure to populate
|
||||
*/
|
||||
void heartbeat_capture_tick_snapshot(VM* vm, HeartbeatTickSnapshot* snapshot)
|
||||
{
|
||||
if (!vm || !snapshot)
|
||||
return;
|
||||
|
||||
HeartbeatState* hb = &vm->heartbeat;
|
||||
|
||||
/* Monotonic tick counter — use the real heartbeat counter, not tick_count_total */
|
||||
snapshot->tick_number = (uint32_t)hb->tick_count;
|
||||
|
||||
/* Timing: heartbeats ARE the clock ("we count heartbeats not timer ticks").
|
||||
* sf_monotonic_ns() is unreliable in freestanding kernel (returns 0 on amd64,
|
||||
* raw counter on riscv64). Use nominal tick period instead. */
|
||||
snapshot->elapsed_ns = hb->tick_count * (uint64_t)HEARTBEAT_TICK_NS;
|
||||
|
||||
static uint64_t last_tick_count = 0;
|
||||
uint64_t delta_ticks = hb->tick_count - last_tick_count;
|
||||
snapshot->tick_interval_ns = delta_ticks * (uint64_t)HEARTBEAT_TICK_NS;
|
||||
last_tick_count = hb->tick_count;
|
||||
|
||||
/* Delta metrics — computed from per-VM counters using static last-value tracking.
|
||||
* On VM reset (current < last), treat current value as the full delta. */
|
||||
|
||||
/* Cache metrics from hotwords cache */
|
||||
snapshot->cache_hits_delta = 0; /* TODO: wire hotwords cache hit counter */
|
||||
snapshot->bucket_hits_delta = 0; /* TODO: wire bucket hit counter */
|
||||
|
||||
/* Word execution delta — words executed since the previous heartbeat tick.
|
||||
* vm->heartbeat.words_executed is incremented by vm_core.c on every dispatch. */
|
||||
{
|
||||
static uint64_t last_words_executed = 0;
|
||||
uint64_t cur = hb->words_executed;
|
||||
snapshot->word_executions_delta = (uint32_t)(cur >= last_words_executed
|
||||
? cur - last_words_executed
|
||||
: cur);
|
||||
last_words_executed = cur;
|
||||
}
|
||||
|
||||
/* Hot word count - walk dictionary and count words above threshold */
|
||||
snapshot->hot_word_count = 0;
|
||||
uint64_t total_heat = 0;
|
||||
uint32_t word_count = 0;
|
||||
|
||||
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
|
||||
{
|
||||
if (entry->execution_heat > 0)
|
||||
{
|
||||
total_heat += entry->execution_heat;
|
||||
word_count++;
|
||||
|
||||
/* Count as "hot" if above cache promotion threshold */
|
||||
if (entry->execution_heat >= 10) /* HEAT_CACHE_DEMOTION_THRESHOLD */
|
||||
{
|
||||
snapshot->hot_word_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Average word heat (convert Q48.16 to double) */
|
||||
if (word_count > 0)
|
||||
{
|
||||
snapshot->avg_word_heat = (double)total_heat / (double)word_count / 65536.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
snapshot->avg_word_heat = 0.0;
|
||||
}
|
||||
|
||||
/* Rolling window size.
|
||||
* window_width = L8's inferred effective size (the target).
|
||||
* actual_window_size = min(total_executions, effective_window_size):
|
||||
* how many entries are ACTUALLY being analysed right now — grows toward
|
||||
* window_width as data accumulates, then tracks it as L8 narrows/widens. */
|
||||
snapshot->window_width = vm->rolling_window.effective_window_size;
|
||||
{
|
||||
uint64_t fill = vm->rolling_window.total_executions;
|
||||
uint32_t eff = vm->rolling_window.effective_window_size;
|
||||
snapshot->actual_window_size = (uint32_t)(fill < (uint64_t)eff ? fill : eff);
|
||||
}
|
||||
|
||||
/* predicted_label_hits: ANOVA early-exit confirmations per tick.
|
||||
* Each early exit means the inference engine validated the current L8 config
|
||||
* as stable — the selector's choice correlated with subsequent execution patterns.
|
||||
* High rate = selector chose well and the system settled.
|
||||
* Low rate = selector is still searching. */
|
||||
{
|
||||
static uint64_t last_early_exit_count = 0;
|
||||
uint64_t cur = hb->early_exit_count;
|
||||
snapshot->predicted_label_hits = (uint32_t)(cur >= last_early_exit_count
|
||||
? cur - last_early_exit_count
|
||||
: cur);
|
||||
last_early_exit_count = cur;
|
||||
}
|
||||
|
||||
/* Jitter: tick-based timing has no wall-clock deviation to measure.
|
||||
* Encode delta_ticks as a deviation: 0 means exactly on schedule (1 tick/call). */
|
||||
snapshot->estimated_jitter_ns = (delta_ticks > 1)
|
||||
? (double)((delta_ticks - 1) * (uint64_t)HEARTBEAT_TICK_NS)
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emit a heartbeat tick snapshot as CSV row to stderr
|
||||
*
|
||||
* Outputs 11 comma-separated values (no header) for real-time streaming.
|
||||
* Called every heartbeat tick to emit metrics immediately.
|
||||
*
|
||||
* Format: tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,
|
||||
* bucket_hits_delta,word_executions_delta,hot_word_count,
|
||||
* avg_word_heat,window_width,actual_window_size,predicted_label_hits,estimated_jitter_ns
|
||||
*
|
||||
* @param vm Pointer to the VM instance (unused, for API consistency)
|
||||
* @param snapshot Pointer to the snapshot to emit
|
||||
*/
|
||||
void heartbeat_emit_tick_row(VM* vm, HeartbeatTickSnapshot* snapshot)
|
||||
{
|
||||
(void)vm; /* Unused parameter */
|
||||
|
||||
if (!snapshot)
|
||||
return;
|
||||
|
||||
/* Emit CSV row to stderr - no header, just data */
|
||||
fprintf(stderr, "%u,%lu,%lu,%u,%u,%u,%lu,%.6f,%u,%u,%u,%.2f\n",
|
||||
snapshot->tick_number,
|
||||
snapshot->elapsed_ns,
|
||||
snapshot->tick_interval_ns,
|
||||
snapshot->cache_hits_delta,
|
||||
snapshot->bucket_hits_delta,
|
||||
snapshot->word_executions_delta,
|
||||
snapshot->hot_word_count,
|
||||
snapshot->avg_word_heat,
|
||||
snapshot->window_width,
|
||||
snapshot->actual_window_size,
|
||||
snapshot->predicted_label_hits,
|
||||
snapshot->estimated_jitter_ns);
|
||||
|
||||
/* Ensure immediate output to stderr (unbuffered) */
|
||||
fflush(stderr);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include "io.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Initialize the IO subsystem
|
||||
* @param vm Pointer to VM instance
|
||||
*
|
||||
* Initializes the block device view over the unified VM memory.
|
||||
* Zeros out the entire VM memory space.
|
||||
*/
|
||||
void io_init(VM *vm) {
|
||||
if (!vm || !vm->memory) {
|
||||
log_message(LOG_ERROR, "io_init: VM or VM memory not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Zero the entire VM address space so blocks start clean. */
|
||||
memset(vm->memory, 0, VM_MEMORY_SIZE);
|
||||
|
||||
log_message(LOG_INFO,
|
||||
"IO subsystem initialized with %d blocks of %d bytes",
|
||||
(int) MAX_BLOCKS, (int) BLOCK_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Write a block to VM memory
|
||||
* @param vm Pointer to VM instance
|
||||
* @param block_num Block number to write (0 to MAX_BLOCKS-1)
|
||||
* @param buffer Source buffer containing block data
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
/**
|
||||
* @brief Write a block to VM memory
|
||||
* @param vm Pointer to VM instance
|
||||
* @param block_num Block number to write (0 to MAX_BLOCKS-1)
|
||||
* @param buffer Source buffer containing block data
|
||||
* @return 0 on success, -1 on error
|
||||
*
|
||||
* Writes a BLOCK_SIZE chunk of data from the provided buffer into the VM's memory
|
||||
* at the specified block number offset. The block number must be within valid range.
|
||||
* The entire block is written atomically.
|
||||
*
|
||||
* @note The buffer must contain at least BLOCK_SIZE bytes of valid data
|
||||
*/
|
||||
int io_write_block(VM *vm, int block_num, const unsigned char *buffer) {
|
||||
if (!vm || !vm->memory || !buffer) {
|
||||
log_message(LOG_ERROR, "io_write_block: bad args");
|
||||
return -1;
|
||||
}
|
||||
if (block_num < 0 || block_num >= (int) MAX_BLOCKS) {
|
||||
log_message(LOG_ERROR, "io_write_block: invalid block number %d", block_num);
|
||||
return -1;
|
||||
}
|
||||
size_t off = (size_t) block_num * (size_t) BLOCK_SIZE;
|
||||
memcpy(&vm->memory[off], buffer, BLOCK_SIZE);
|
||||
log_message(LOG_DEBUG, "io_write_block: wrote block %d", block_num);
|
||||
return 0;
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include "../include/log.h"
|
||||
#include "../include/platform_time.h"
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Current log level (default to LOG_INFO) */
|
||||
static LogLevel current_level = LOG_INFO;
|
||||
|
||||
/* Log level names */
|
||||
static const char *level_names[] = {
|
||||
"ERROR",
|
||||
"WARN",
|
||||
"INFO",
|
||||
"TEST",
|
||||
"DEBUG"
|
||||
};
|
||||
|
||||
/* ANSI color codes for each log level - only used outside minimal mode;
|
||||
* MINIMAL builds never read these (both use sites are #ifndef-guarded
|
||||
* below), so no empty-string fallback definitions are needed. */
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
static const char *level_colors[] = {
|
||||
"\x1b[31m", // ERROR - Red
|
||||
"\x1b[33m", // WARN - Yellow
|
||||
"\x1b[32m", // INFO - Green
|
||||
"\x1b[35m", // TEST - Magenta
|
||||
"\x1b[34m" // DEBUG - Blue
|
||||
};
|
||||
|
||||
/* Test result colors */
|
||||
static const char *test_pass_color = "\x1b[32m"; // Green
|
||||
static const char *test_fail_color = "\x1b[31m"; // Red
|
||||
static const char *test_skip_color = "\x1b[33m"; // Yellow
|
||||
|
||||
/* Reset color */
|
||||
static const char *color_reset = "\x1b[0m";
|
||||
#endif
|
||||
|
||||
/* Set global log level */
|
||||
/*
|
||||
* @brief Sets the global logging level
|
||||
*
|
||||
* @param level The LogLevel to set as current logging level
|
||||
*/
|
||||
void log_set_level(LogLevel level) {
|
||||
current_level = level;
|
||||
}
|
||||
|
||||
/* Get current log level */
|
||||
/**
|
||||
* @brief Gets the current global logging level
|
||||
*
|
||||
* @return LogLevel Current logging level
|
||||
*/
|
||||
LogLevel log_get_level(void) {
|
||||
return current_level;
|
||||
}
|
||||
|
||||
/* Timestamp helper */
|
||||
/**
|
||||
* @brief Helper function to generate timestamp string
|
||||
*
|
||||
* @param buffer Output buffer for the timestamp string
|
||||
* @param size Size of the output buffer
|
||||
*/
|
||||
static void get_timestamp(char *buffer, size_t size) {
|
||||
/* Use platform abstraction for timestamps */
|
||||
sf_time_ns_t now = sf_realtime_ns();
|
||||
sf_format_timestamp(now, buffer, 1); /* 24-hour format */
|
||||
}
|
||||
|
||||
/* Generic logger */
|
||||
/*
|
||||
* @brief Logs a message with the specified level and format
|
||||
*
|
||||
* @param level The LogLevel for this message
|
||||
* @param fmt Printf-style format string
|
||||
* @param ... Variable arguments for format string
|
||||
*/
|
||||
void log_message(LogLevel level, const char *fmt, ...) {
|
||||
/* LOG_NONE (-1) disables ALL logging */
|
||||
if (current_level == LOG_NONE || level > current_level)
|
||||
return;
|
||||
|
||||
char timestamp[16];
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
fprintf(stderr, "%s[%s] %s: %s", level_colors[level], timestamp, level_names[level], color_reset);
|
||||
#else
|
||||
fprintf(stderr, "[%s] %s: ", timestamp, level_names[level]);
|
||||
#endif
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* Dedicated test result logger (only shown for LOG_TEST or higher) */
|
||||
/*
|
||||
* @brief Logs test results_run_01_2025_12_08 with appropriate formatting and colors
|
||||
*
|
||||
* @param word_name Name of the word being tested
|
||||
* @param result Test result (PASS/FAIL/SKIP)
|
||||
*/
|
||||
void log_test_result(const char *word_name, TestResult result) {
|
||||
if (current_level == LOG_NONE || current_level < LOG_TEST)
|
||||
return;
|
||||
|
||||
char timestamp[16];
|
||||
get_timestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
const char *color;
|
||||
#endif
|
||||
const char *status;
|
||||
|
||||
switch (result) {
|
||||
case TEST_PASS:
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
color = test_pass_color;
|
||||
#endif
|
||||
status = "PASS";
|
||||
break;
|
||||
case TEST_FAIL:
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
color = test_fail_color;
|
||||
#endif
|
||||
status = "FAIL";
|
||||
break;
|
||||
case TEST_SKIP:
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
color = test_skip_color;
|
||||
#endif
|
||||
status = "SKIP";
|
||||
break;
|
||||
default:
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
color = color_reset;
|
||||
#endif
|
||||
status = "????";
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef STARFORTH_MINIMAL
|
||||
fprintf(stderr, "\x1b[35m[%s] TEST: %sTesting %-12s ... %s%s%s\n",
|
||||
timestamp, color_reset, word_name, color, status, color_reset);
|
||||
#else
|
||||
fprintf(stderr, "[%s] TEST: Testing %-12s ... %s\n",
|
||||
timestamp, word_name, status);
|
||||
#endif
|
||||
}
|
||||
+563
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* math_portable.c
|
||||
*
|
||||
* Pure 64-bit fixed-point Q48.16 math functions.
|
||||
* Suitable for embedded environments with no libm or floating-point
|
||||
* (HISTORICAL: also L4Re, through mid-2026; removed as an active target).
|
||||
* All arithmetic is integer-only for maximum production performance.
|
||||
*
|
||||
* Q48.16 Format:
|
||||
* - 48-bit signed integer + 16-bit fractional part
|
||||
* - 1.0 = 65536 (2^16)
|
||||
* - Precision: 2^-16 ≈ 0.0000153
|
||||
* - Range: ±140 trillion
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "math_portable.h"
|
||||
|
||||
/**
|
||||
* @brief Compute exp(@p x) via Taylor series in Q48.16 fixed-point.
|
||||
*
|
||||
* Evaluates the classic Taylor expansion:
|
||||
* @code
|
||||
* exp(x) = 1 + x + x²/2! + x³/3! + … (up to 40 terms)
|
||||
* @endcode
|
||||
* entirely in 64-bit integer arithmetic using the Q48.16 representation
|
||||
* (1.0 = 65536 = 2^16).
|
||||
*
|
||||
* The function is intentionally scoped to the range the physics engine
|
||||
* actually uses — the argument to @c erf_q48() is @c -x² times a ratio
|
||||
* near 1, yielding values in roughly the @c [-20, 0] interval. Outside
|
||||
* this range the implementation provides coarse-grained clamping:
|
||||
* - @p x == 0 → returns @c Q48 (1.0 exactly)
|
||||
* - @p x > 20.0 → saturates at @c Q48 << 8 (approx e^20, avoids overflow)
|
||||
* - @p x < -100.0 → underflows to 0 (result is negligible)
|
||||
*
|
||||
* The term loop exits early on the first term smaller than 1 ULP (the
|
||||
* series has converged) and guards against intermediate overflow by
|
||||
* clamping @c result at @c INT64_MAX-1.
|
||||
*
|
||||
* @note This is a file-local (@c static) helper; callers are
|
||||
* @c erf_q48() only.
|
||||
*
|
||||
* @param x Input value in Q48.16 format (1.0 = 65536). Intended range:
|
||||
* approximately [-100×65536, +20×65536].
|
||||
* @return exp(@p x) in Q48.16 format; 0 on underflow; saturated at
|
||||
* @c INT64_MAX-1 on overflow.
|
||||
*/
|
||||
static int64_t exp_q48(int64_t x) {
|
||||
const int64_t Q48 = 65536LL; /* 2^16 */
|
||||
|
||||
/* Handle extremes */
|
||||
if (x == 0) return Q48;
|
||||
if (x > (20LL << 16)) return Q48 << 8; /* Approximate e^20 */
|
||||
if (x < -(100LL << 16)) return 0; /* Underflow to zero */
|
||||
|
||||
/* Taylor series: exp(x) = Σ x^n / n! (in fixed-point) */
|
||||
int64_t result = Q48; /* Start with 1 */
|
||||
int64_t term = Q48;
|
||||
int64_t x_power = x; /* x^1 */
|
||||
|
||||
for (int i = 1; i < 40; i++) {
|
||||
/* term = x^i / i! */
|
||||
term = (x_power / (int64_t)i) / Q48; /* Scale down to avoid overflow */
|
||||
if (term > -1 && term < 1) break; /* Negligible */
|
||||
|
||||
result += term;
|
||||
x_power = (x_power * x) / Q48; /* Keep x^n scaled */
|
||||
|
||||
/* Guard against overflow */
|
||||
if (result < 0 && result < (1LL << 62)) break;
|
||||
if (result > 0 && result > (1LL << 62)) {
|
||||
result = 9223372036854775806LL; /* Saturate at INT64_MAX-1 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute sqrt(@p x) via Newton-Raphson in Q48.16 fixed-point.
|
||||
*
|
||||
* Applies the Newton-Raphson recurrence:
|
||||
* @code
|
||||
* r_{n+1} = (r_n² + x) / (2 × r_n)
|
||||
* @endcode
|
||||
* for up to 20 iterations, with all arithmetic scaled to the Q48.16
|
||||
* representation (1.0 = 65536). Intermediate products use 8-bit right
|
||||
* shifts on both operands before squaring to prevent 64-bit overflow:
|
||||
* @code
|
||||
* guess_squared = (guess >> 8) * (guess >> 8)
|
||||
* @endcode
|
||||
* This sacrifices 16 bits of precision in the squared term, which is
|
||||
* acceptable because the division by @c 2*guess restores the correct
|
||||
* order of magnitude. Convergence is declared when successive iterates
|
||||
* differ by at most 1 in Q48.16 units (sub-nanosecond for timing uses).
|
||||
*
|
||||
* Special cases handled without iteration:
|
||||
* - @p x ≤ 0 → returns 0 (no imaginary results)
|
||||
* - @p x == 65536 (1.0) → returns 65536 exactly
|
||||
*
|
||||
* Initial guess: @c x/2 for @p x > 1.0, else @c 1.0. Values above 1.0
|
||||
* converge from above; values in (0, 1.0) converge from the 1.0 initial
|
||||
* guess.
|
||||
*
|
||||
* @param x Non-negative value in Q48.16 format (1.0 = 65536).
|
||||
* Negative values are treated as zero.
|
||||
* @return sqrt(@p x) in Q48.16 format; 0 for @p x ≤ 0.
|
||||
*/
|
||||
int64_t sqrt_q48(int64_t x) {
|
||||
const int64_t Q48 = 65536LL;
|
||||
|
||||
/* Handle special cases */
|
||||
if (x < 0) return 0;
|
||||
if (x == 0) return 0;
|
||||
if (x == Q48) return Q48; /* sqrt(1.0) = 1.0 */
|
||||
|
||||
/* Initial guess: average of x and 1 */
|
||||
int64_t guess = (x > Q48) ? (x >> 1) : Q48;
|
||||
|
||||
/* Newton-Raphson iterations */
|
||||
for (int i = 0; i < 20; i++) {
|
||||
/* next = (guess^2 + x) / (2 * guess)
|
||||
* In Q48.16: (guess^2) >> 16 + x, then divide by 2*guess
|
||||
*/
|
||||
int64_t guess_squared = (guess >> 8) * (guess >> 8); /* Avoid overflow */
|
||||
int64_t next = (guess_squared + (x >> 16)) / (2 * guess);
|
||||
|
||||
/* Check convergence */
|
||||
int64_t diff = next - guess;
|
||||
if (diff > -2 && diff < 2) return next;
|
||||
|
||||
guess = next;
|
||||
}
|
||||
|
||||
return guess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute erf(@p x) via Abramowitz & Stegun 7.1.26 in Q48.16 fixed-point.
|
||||
*
|
||||
* Implements the rational approximation from Abramowitz & Stegun, formula 7.1.26:
|
||||
* @code
|
||||
* erf(x) ≈ sign(x) × sqrt(1 − exp(−x² × (4/π + a·x²) / (1 + a·x²)))
|
||||
* @endcode
|
||||
* where @c a ≈ 0.147 (in Q48.16: 9633). The maximum error of the true
|
||||
* mathematical approximation is @c |ε| < 1.5×10⁻⁷; the fixed-point
|
||||
* encoding introduces additional rounding of approximately 1 ULP in Q48.16.
|
||||
*
|
||||
* All arithmetic is performed in 64-bit integer Q48.16 format — no
|
||||
* floating-point operations. The computation sequence:
|
||||
* 1. Extract sign; work with @c |x|.
|
||||
* 2. Compute @c x² with 8-bit prescaling to prevent overflow:
|
||||
* @c x_sq = (x >> 8) * (x >> 8).
|
||||
* 3. Evaluate the A&S numerator @c (4/π + a·x²) and denominator @c (1 + a·x²).
|
||||
* 4. Compute @c ratio = numerator / denominator (shift to preserve Q48.16 scale).
|
||||
* 5. Compute @c exp_arg = −x² × ratio and evaluate @c exp_q48(exp_arg).
|
||||
* 6. Compute @c base = 1 − exp_val, clamp to [0, 1].
|
||||
* 7. Return @c sign × sqrt_q48(base).
|
||||
*
|
||||
* Q48.16 constants used:
|
||||
* - @c a_q48 = 9633 (0.147 × 65536)
|
||||
* - @c four_over_pi_q48 = 83328 (4/π × 65536 ≈ 1.2732 × 65536)
|
||||
*
|
||||
* Consumed by @c hotwords_speedup_estimate() to compute P(speedup > 1.1×)
|
||||
* and P(speedup > 2.0×) via the normal CDF identity:
|
||||
* @code
|
||||
* P(Z ≤ z) = 0.5 × (1 + erf(z / √2))
|
||||
* @endcode
|
||||
* where @c √2 in Q48.16 = 92681.
|
||||
*
|
||||
* @param x Input value in Q48.16 format (dimensionless Z-score;
|
||||
* 1.0 = 65536). May be negative; sign determines the sign
|
||||
* of the result.
|
||||
* @return erf(@p x) in Q48.16 format, in the range [−65536, +65536]
|
||||
* corresponding to (−1.0, +1.0).
|
||||
*/
|
||||
int64_t erf_q48(int64_t x) {
|
||||
const int64_t Q48 = 65536LL;
|
||||
|
||||
if (x == 0) return 0;
|
||||
|
||||
/* Compute sign and work with absolute value */
|
||||
int sign = (x < 0) ? -1 : 1;
|
||||
if (x < 0) x = -x;
|
||||
|
||||
/* Abramowitz & Stegun coefficient a ≈ 0.147 (in Q48.16) */
|
||||
const int64_t a_q48 = 9633LL; /* 0.147 * 65536 */
|
||||
const int64_t four_over_pi_q48 = 83328LL; /* 4/π * 65536 */
|
||||
|
||||
/* Compute x^2 in Q48.16 */
|
||||
int64_t x_sq = (x >> 8) * (x >> 8); /* Scaled to avoid overflow */
|
||||
|
||||
/* Compute denominator: 1 + a*x^2 */
|
||||
int64_t denom = Q48 + ((a_q48 * x_sq) >> 16);
|
||||
if (denom == 0) denom = 1; /* Guard divide-by-zero */
|
||||
|
||||
/* Compute numerator: 4/π + a*x^2 */
|
||||
int64_t numer = four_over_pi_q48 + ((a_q48 * x_sq) >> 16);
|
||||
|
||||
/* Compute ratio = numerator / denominator */
|
||||
int64_t ratio = (numer << 16) / denom;
|
||||
|
||||
/* Compute exp argument: -x^2 * ratio */
|
||||
int64_t exp_arg = -(x_sq * ratio / Q48);
|
||||
|
||||
/* Compute exp(-x^2 * ratio) */
|
||||
int64_t exp_val = exp_q48(exp_arg);
|
||||
|
||||
/* Compute base: 1 - exp(exp_arg) */
|
||||
int64_t base = Q48 - exp_val;
|
||||
if (base < 0) base = 0;
|
||||
if (base > Q48) base = Q48;
|
||||
|
||||
/* Compute sqrt(base) */
|
||||
int64_t sqrt_base = sqrt_q48(base);
|
||||
|
||||
/* Apply sign and return */
|
||||
return sign * sqrt_base;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include "../include/vm.h"
|
||||
#include "../include/log.h"
|
||||
|
||||
/**
|
||||
* @brief Allocates memory from the VM's dictionary space
|
||||
* @param vm Pointer to the VM instance
|
||||
* @param bytes Number of bytes to allocate
|
||||
* @return Pointer to the allocated memory or NULL if allocation fails
|
||||
* @note The allocation is done from the VM's dictionary space which is limited to DICTIONARY_MEMORY_SIZE
|
||||
*/
|
||||
void *vm_allot(VM *vm, size_t bytes) {
|
||||
if (!vm || !vm->memory) {
|
||||
log_message(LOG_ERROR, "vm_allot: VM or memory is NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Ensure we don't allocate beyond dictionary space (first DICTIONARY_BLOCKS blocks = DICTIONARY_MEMORY_SIZE bytes) */
|
||||
log_message(LOG_DEBUG, "vm_allot: Requesting %zu bytes, current here=%zu, limit=%d",
|
||||
bytes, vm->here, DICTIONARY_MEMORY_SIZE);
|
||||
|
||||
if (vm->here + bytes >= DICTIONARY_MEMORY_SIZE) {
|
||||
log_message(LOG_ERROR, "Dictionary space full (here=%zu, bytes=%zu, dict_limit=%d)",
|
||||
vm->here, bytes, DICTIONARY_MEMORY_SIZE);
|
||||
vm->error = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *ptr = vm->memory + vm->here;
|
||||
vm->here += bytes;
|
||||
|
||||
/* Log which block we're using */
|
||||
int current_block = vm->here / BLOCK_SIZE;
|
||||
log_message(LOG_DEBUG, "ALLOT: Allocated %zu bytes at offset %zu (block %d)",
|
||||
bytes, vm->here - bytes, current_block);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Aligns the VM's dictionary pointer (here) to the cell boundary
|
||||
* @param vm Pointer to the VM instance
|
||||
* @note Adds padding bytes if necessary to ensure proper alignment for cell_t type
|
||||
*/
|
||||
void vm_align(VM *vm) {
|
||||
size_t align = sizeof(cell_t);
|
||||
size_t misalignment = vm->here % align;
|
||||
|
||||
if (misalignment != 0) {
|
||||
size_t padding = align - misalignment;
|
||||
void *pad = vm_allot(vm, padding);
|
||||
if (pad == NULL) {
|
||||
log_message(LOG_ERROR, "vm_align: Out of memory while aligning");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Optional: zero out the padding */
|
||||
unsigned char *bytes = (unsigned char *) pad;
|
||||
for (size_t i = 0; i < padding; ++i) {
|
||||
bytes[i] = 0;
|
||||
}
|
||||
|
||||
log_message(LOG_DEBUG, "vm_align: Added %zu byte(s) of padding", padding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the memory address for a specified block number
|
||||
* @param vm Pointer to the VM instance
|
||||
* @param block_num Block number to get the address for
|
||||
* @return Pointer to the start of the block or NULL if block number is invalid
|
||||
* @note Blocks are fixed-size memory regions of BLOCK_SIZE bytes
|
||||
*/
|
||||
void *vm_get_block_addr(VM *vm, int block_num) {
|
||||
if (block_num < 0 || block_num >= MAX_BLOCKS) {
|
||||
log_message(LOG_ERROR, "vm_get_block_addr: Invalid block number %d", block_num);
|
||||
return NULL;
|
||||
}
|
||||
return vm->memory + (block_num * BLOCK_SIZE);
|
||||
}
|
||||
|
||||
/* Convert address to block number */
|
||||
/**
|
||||
* @brief Converts a memory address to its corresponding block number
|
||||
* @param vm Pointer to the VM instance
|
||||
* @param addr Memory address to convert
|
||||
* @return Block number (0 to MAX_BLOCKS-1) or -1 if address is outside VM memory
|
||||
* @note Used for determining which block a memory address belongs to
|
||||
*/
|
||||
int vm_addr_to_block(VM *vm, void *addr) {
|
||||
if (addr < (void *) vm->memory ||
|
||||
addr >= (void *) (vm->memory + VM_MEMORY_SIZE)) {
|
||||
return -1; /* Outside VM memory */
|
||||
}
|
||||
|
||||
uintptr_t offset = (uintptr_t) addr - (uintptr_t) vm->memory;
|
||||
return (int) (offset / BLOCK_SIZE);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* physics_execution_hooks.c - All physics machinery in one place
|
||||
*
|
||||
* This file absorbs ALL the physics-related code that was scattered
|
||||
* throughout execute_colon_word and vm_interpret_word.
|
||||
*
|
||||
* The VM execution loop is now clean; this file is where physics lives.
|
||||
*/
|
||||
|
||||
#include "../include/physics_execution_hooks.h"
|
||||
#include "../include/platform_time.h"
|
||||
#include "../include/profiler.h"
|
||||
#include "../include/physics_metadata.h"
|
||||
#include "../include/physics_hotwords_cache.h"
|
||||
#include "../include/physics_pipelining_metrics.h"
|
||||
#include "../include/rolling_window_of_truth.h"
|
||||
#include "../include/ssm_jacquard.h"
|
||||
|
||||
/**
|
||||
* @brief Pre-execution physics hook — called before every word dispatch.
|
||||
*
|
||||
* Consolidates all physics work that must happen before a word runs:
|
||||
* - Loop #3: applies linear decay based on elapsed time since last execution
|
||||
* - Loop #1: increments @c execution_heat for the word
|
||||
* - Loop #2: records the word-id into the rolling window of truth
|
||||
* - Loop #4: detects a pipelining prefetch hit (if @c prev's speculation
|
||||
* matched @c word), records the transition from @c prev to @c word,
|
||||
* updates the probability cache, and issues a speculative hotwords
|
||||
* cache promotion for the most likely next word
|
||||
*
|
||||
* @param vm Pointer to the VM structure
|
||||
* @param word DictEntry being executed
|
||||
* @param prev DictEntry executed immediately before @c word (may be NULL)
|
||||
*/
|
||||
void physics_pre_execute(VM* vm, DictEntry* word, DictEntry* prev)
|
||||
{
|
||||
if (!vm || !word) return;
|
||||
|
||||
uint64_t now_ns = sf_monotonic_ns();
|
||||
|
||||
/* Loop #3: Apply linear decay before accumulating new heat.
|
||||
* Tick-based, not wall-clock -- see physics_metadata_apply_linear_decay(). */
|
||||
uint64_t elapsed_ticks = vm->heartbeat.tick_count - word->physics.last_decay_tick;
|
||||
physics_metadata_apply_linear_decay(word, elapsed_ticks, vm);
|
||||
word->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||||
word->physics.last_active_ns = now_ns;
|
||||
word->physics.last_decay_ns = now_ns;
|
||||
|
||||
/* Loop #1: Heat accumulation */
|
||||
physics_execution_heat_increment(word);
|
||||
|
||||
uint32_t word_id = word->word_id;
|
||||
if (word_id >= DICTIONARY_SIZE) return;
|
||||
|
||||
/* Loop #2: Rolling Window of Truth */
|
||||
rolling_window_record_execution(&vm->rolling_window, word_id);
|
||||
|
||||
/* Loop #4: Pipelining Transition Metrics. ENABLE_PIPELINING is the
|
||||
* compile-time ceiling; ssm_config's L4_pipelining is L8's live
|
||||
* runtime toggle within that ceiling (see ssm_jacquard.h). */
|
||||
{
|
||||
ssm_config_t *ssm_cfg = (ssm_config_t*)vm->ssm_config;
|
||||
if (!prev || !prev->transition_metrics || !ENABLE_PIPELINING ||
|
||||
!ssm_cfg || !ssm_cfg->L4_pipelining) return;
|
||||
}
|
||||
|
||||
/* Check if current word was speculatively promoted by previous word */
|
||||
uint32_t prev_speculation_target = prev->transition_metrics->most_likely_next_word_id;
|
||||
if (prev_speculation_target == word_id && prev->transition_metrics->prefetch_attempts > 0)
|
||||
{
|
||||
/* PREFETCH HIT: Current word matches previous word's speculation! */
|
||||
transition_metrics_record_prefetch_hit(prev->transition_metrics, 0);
|
||||
vm->pipeline_metrics.prefetch_hits++;
|
||||
}
|
||||
|
||||
/* Record transition from previous word to current word */
|
||||
transition_metrics_record(prev->transition_metrics, word_id, DICTIONARY_SIZE);
|
||||
|
||||
/* Update probability cache to find most likely next word */
|
||||
transition_metrics_update_cache(prev->transition_metrics, DICTIONARY_SIZE);
|
||||
|
||||
uint32_t speculated_word_id = prev->transition_metrics->most_likely_next_word_id;
|
||||
|
||||
/* Speculative prefetch of most likely next word */
|
||||
if (speculated_word_id < DICTIONARY_SIZE &&
|
||||
transition_metrics_should_speculate(prev->transition_metrics, speculated_word_id))
|
||||
{
|
||||
DictEntry* spec_entry = vm_dictionary_lookup_by_word_id(vm, speculated_word_id);
|
||||
|
||||
/* ENABLE_HOTWORDS_CACHE is the compile-time ceiling; cache->enabled
|
||||
* is L8's live runtime toggle within that ceiling, kept in sync
|
||||
* with ssm_config's L1_heat_tracking wherever L8 applies a config
|
||||
* (see vm_time.c / vm_runtime.c). */
|
||||
if (spec_entry && vm->hotwords_cache && ENABLE_HOTWORDS_CACHE &&
|
||||
vm->hotwords_cache->enabled)
|
||||
{
|
||||
spec_entry->execution_heat = HOTWORDS_EXECUTION_HEAT_THRESHOLD + 1;
|
||||
hotwords_cache_promote(vm->hotwords_cache, spec_entry);
|
||||
|
||||
prev->transition_metrics->prefetch_attempts++;
|
||||
vm->pipeline_metrics.prefetch_attempts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post-execution physics hook — called after every word dispatch.
|
||||
*
|
||||
* Consolidates all physics work that must happen after a word completes:
|
||||
* - Updates physics metadata (@c temperature_q8, @c last_active_ns) via
|
||||
* @c physics_metadata_touch()
|
||||
* - Notifies the profiler (@c profiler_word_exit())
|
||||
* - Increments @c vm->heartbeat.words_executed
|
||||
* - When running without a background heartbeat thread, checks whether
|
||||
* enough words have been executed to trigger a heartbeat cycle
|
||||
* (@c vm_heartbeat_run_cycle())
|
||||
*
|
||||
* @param vm Pointer to the VM structure
|
||||
* @param word DictEntry that just finished executing
|
||||
*/
|
||||
void physics_post_execute(VM* vm, DictEntry* word)
|
||||
{
|
||||
if (!vm || !word) return;
|
||||
|
||||
physics_metadata_touch(word, word->execution_heat, sf_monotonic_ns());
|
||||
profiler_word_exit(word);
|
||||
vm->heartbeat.words_executed++;
|
||||
|
||||
/* Heartbeat: Periodic time-driven tuning (Loop #3 & #5) */
|
||||
if (!vm->heartbeat.worker && ++vm->heartbeat.check_counter >= HEARTBEAT_CHECK_FREQUENCY)
|
||||
{
|
||||
extern void vm_heartbeat_run_cycle(VM* vm);
|
||||
vm_heartbeat_run_cycle(vm);
|
||||
vm->heartbeat.check_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Physics hook for the outer interpreter word-lookup path.
|
||||
*
|
||||
* Called when the outer interpreter successfully finds a word by name,
|
||||
* before executing it. Applies linear decay and increments heat on both
|
||||
* the found entry and the canonical entry (if different), and updates
|
||||
* @c last_active_ns / @c last_decay_ns on both. Acquires @c vm->dict_lock
|
||||
* for the entire operation to ensure thread safety.
|
||||
*
|
||||
* @param vm Pointer to the VM structure
|
||||
* @param entry DictEntry found by name search
|
||||
* @param canon Canonical DictEntry (may equal @c entry or be the smudge target)
|
||||
*/
|
||||
void physics_on_lookup(VM* vm, DictEntry* entry, DictEntry* canon)
|
||||
{
|
||||
if (!vm || !entry) return;
|
||||
|
||||
uint64_t lookup_ns = sf_monotonic_ns();
|
||||
|
||||
sf_mutex_lock(&vm->dict_lock);
|
||||
|
||||
/* Apply decay and heat to found entry. Tick-based, not wall-clock --
|
||||
* see physics_metadata_apply_linear_decay(). */
|
||||
uint64_t elapsed_entry_ticks = vm->heartbeat.tick_count - entry->physics.last_decay_tick;
|
||||
physics_metadata_apply_linear_decay(entry, elapsed_entry_ticks, vm);
|
||||
entry->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||||
entry->physics.last_active_ns = lookup_ns;
|
||||
entry->physics.last_decay_ns = lookup_ns;
|
||||
physics_execution_heat_increment(entry);
|
||||
|
||||
/* Also handle canonical entry if different */
|
||||
if (canon && canon != entry)
|
||||
{
|
||||
uint64_t elapsed_canon_ticks = vm->heartbeat.tick_count - canon->physics.last_decay_tick;
|
||||
physics_metadata_apply_linear_decay(canon, elapsed_canon_ticks, vm);
|
||||
canon->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||||
canon->physics.last_active_ns = lookup_ns;
|
||||
canon->physics.last_decay_ns = lookup_ns;
|
||||
physics_execution_heat_increment(canon);
|
||||
physics_metadata_touch(canon, canon->execution_heat, lookup_ns);
|
||||
}
|
||||
|
||||
sf_mutex_unlock(&vm->dict_lock);
|
||||
}
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
# src/platform/
|
||||
|
||||
Platform abstraction layer for the hosted VM build.
|
||||
|
||||
- `alloc_host.c` — hosted (`malloc`-backed) allocator implementation of
|
||||
`include/platform_alloc.h`.
|
||||
- `alloc_kernel.c` — a kernel-side allocator implementation of the same
|
||||
interface. Note: `src/starkernel/vm/alloc_kernel.c` is the copy actually
|
||||
referenced by `Makefile.starkernel`'s kernel build; this one under
|
||||
`src/platform/` is not wired into any build target — check before relying
|
||||
on it.
|
||||
- `platform_init.c` — platform-specific initialization dispatch.
|
||||
- `threading.c` — threading primitives backing the heartbeat background
|
||||
thread (`HEARTBEAT_THREAD_ENABLED=1`).
|
||||
|
||||
Subdirectories:
|
||||
- `linux/time.c` — POSIX (`clock_gettime`) timing backend.
|
||||
- `l4re/time.c` — HISTORICAL: L4Re/Fiasco.OC timing backend. L4Re was a
|
||||
supported platform target through mid-2026; it has been removed as an
|
||||
active target and this file is no longer wired into any build.
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* platform/alloc_host.c - Host (Linux/POSIX) memory allocator
|
||||
*
|
||||
* Simple wrapper around standard malloc/free with statistics tracking.
|
||||
*/
|
||||
|
||||
#include "../../include/platform_alloc.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Statistics tracking */
|
||||
static sf_alloc_stats_t g_stats = {0};
|
||||
|
||||
/**
|
||||
* @brief Initialise the host allocator statistics.
|
||||
*
|
||||
* Resets the @c g_stats struct to zero. Safe to call multiple times;
|
||||
* subsequent calls simply reset the counters. No heap memory is allocated
|
||||
* by this function — the underlying allocator is the system @c malloc.
|
||||
*
|
||||
* @return 0 always (cannot fail on the hosted path)
|
||||
*/
|
||||
int sf_alloc_init(void)
|
||||
{
|
||||
memset(&g_stats, 0, sizeof(g_stats));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate @c size bytes from the system heap.
|
||||
*
|
||||
* Delegates to @c malloc(). On success, increments @c g_stats.used_bytes
|
||||
* and @c g_stats.alloc_count, and updates @c g_stats.peak_bytes if the
|
||||
* new used total exceeds the recorded peak.
|
||||
*
|
||||
* @param size Number of bytes to allocate; 0 returns NULL
|
||||
* @return Pointer to allocated memory, or NULL on allocation failure or zero size
|
||||
*/
|
||||
void* sf_malloc(size_t size)
|
||||
{
|
||||
if (size == 0) return NULL;
|
||||
|
||||
void* ptr = malloc(size);
|
||||
if (ptr)
|
||||
{
|
||||
g_stats.used_bytes += size;
|
||||
g_stats.alloc_count++;
|
||||
if (g_stats.used_bytes > g_stats.peak_bytes)
|
||||
{
|
||||
g_stats.peak_bytes = g_stats.used_bytes;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate a zero-initialised array of @c count elements of @c size bytes each.
|
||||
*
|
||||
* Delegates to @c calloc(). Updates statistics the same way as @c sf_malloc():
|
||||
* adds @c count*size to @c used_bytes, increments @c alloc_count, and updates
|
||||
* @c peak_bytes.
|
||||
*
|
||||
* @param count Number of elements; 0 returns NULL
|
||||
* @param size Size of each element in bytes; 0 returns NULL
|
||||
* @return Pointer to zero-filled memory, or NULL on allocation failure or zero arguments
|
||||
*/
|
||||
void* sf_calloc(size_t count, size_t size)
|
||||
{
|
||||
if (count == 0 || size == 0) return NULL;
|
||||
|
||||
void* ptr = calloc(count, size);
|
||||
if (ptr)
|
||||
{
|
||||
size_t total = count * size;
|
||||
g_stats.used_bytes += total;
|
||||
g_stats.alloc_count++;
|
||||
if (g_stats.used_bytes > g_stats.peak_bytes)
|
||||
{
|
||||
g_stats.peak_bytes = g_stats.used_bytes;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resize a previously allocated block to @c new_size bytes.
|
||||
*
|
||||
* Delegates to @c realloc(). On success, increments @c g_stats.alloc_count.
|
||||
* The exact byte delta is not tracked because the original allocation size is
|
||||
* not stored alongside the pointer — @c used_bytes is therefore approximate
|
||||
* after any @c sf_realloc() call. For accurate accounting, wrap allocations
|
||||
* with size-tracking headers.
|
||||
*
|
||||
* @param ptr Pointer to an existing allocation, or NULL (behaves like @c sf_malloc)
|
||||
* @param new_size Desired new size in bytes; 0 may free the block (platform-defined)
|
||||
* @return Pointer to resized memory, or NULL on failure (original block unchanged)
|
||||
*/
|
||||
void* sf_realloc(void* ptr, size_t new_size)
|
||||
{
|
||||
void* new_ptr = realloc(ptr, new_size);
|
||||
if (new_ptr)
|
||||
{
|
||||
/* Note: Can't track exact delta without size tracking */
|
||||
g_stats.alloc_count++;
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release a previously allocated block back to the system heap.
|
||||
*
|
||||
* Delegates to @c free(). Increments @c g_stats.free_count on non-NULL @c ptr.
|
||||
* The freed byte count is not subtracted from @c used_bytes because the
|
||||
* original size is not tracked — see @c sf_realloc() note.
|
||||
*
|
||||
* @param ptr Pointer to free; NULL is a safe no-op
|
||||
*/
|
||||
void sf_free(void* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
|
||||
/* Note: We can't track exact bytes freed without size tracking.
|
||||
* For accurate stats, would need to wrap allocations with headers.
|
||||
* For now, just count frees. */
|
||||
g_stats.free_count++;
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy the current allocator statistics into @c stats.
|
||||
*
|
||||
* Fills @c *stats with a snapshot of @c g_stats. Note that @c used_bytes
|
||||
* overstates the live total when blocks have been freed or reallocated,
|
||||
* because freed sizes are not tracked. @c free_count is accurate.
|
||||
*
|
||||
* @param stats Output buffer to populate; no-op if NULL
|
||||
*/
|
||||
void sf_alloc_get_stats(sf_alloc_stats_t* stats)
|
||||
{
|
||||
if (!stats) return;
|
||||
*stats = g_stats;
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* platform/alloc_kernel.c - Kernel (bare-metal) memory allocator
|
||||
*
|
||||
* Static arena with bump allocation. Free is a no-op.
|
||||
* This is appropriate for kernel use where:
|
||||
* - VM is long-lived (no restart)
|
||||
* - Allocations happen at init time
|
||||
* - Runtime allocations are rare
|
||||
*
|
||||
* Arena size: 4MB by default (configurable via SF_ARENA_SIZE)
|
||||
*/
|
||||
|
||||
#include "../../include/platform_alloc.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef SF_ARENA_SIZE
|
||||
#define SF_ARENA_SIZE (4 * 1024 * 1024) /* 4MB default */
|
||||
#endif
|
||||
|
||||
/* Alignment for all allocations (8 bytes for 64-bit safety) */
|
||||
#define SF_ALIGN 8
|
||||
#define SF_ALIGN_UP(x) (((x) + (SF_ALIGN - 1)) & ~(SF_ALIGN - 1))
|
||||
|
||||
/* Static arena */
|
||||
static uint8_t g_arena[SF_ARENA_SIZE] __attribute__((aligned(SF_ALIGN)));
|
||||
static size_t g_arena_offset = 0;
|
||||
static int g_initialized = 0;
|
||||
|
||||
/* Statistics */
|
||||
static sf_alloc_stats_t g_stats = {0};
|
||||
|
||||
/**
|
||||
* @brief Initialise the kernel bump allocator.
|
||||
*
|
||||
* Resets @c g_arena_offset to zero and clears all @c g_stats fields.
|
||||
* Sets @c g_initialized so that @c sf_malloc() skips the lazy-init guard.
|
||||
* Safe to call at any point; all prior allocations become invalid after a
|
||||
* reset, so this must only be called before any VM allocation takes place.
|
||||
*
|
||||
* @return 0 always (cannot fail)
|
||||
*/
|
||||
int sf_alloc_init(void)
|
||||
{
|
||||
g_arena_offset = 0;
|
||||
g_initialized = 1;
|
||||
|
||||
g_stats.total_bytes = SF_ARENA_SIZE;
|
||||
g_stats.used_bytes = 0;
|
||||
g_stats.peak_bytes = 0;
|
||||
g_stats.alloc_count = 0;
|
||||
g_stats.free_count = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Allocate @c size bytes from the static kernel arena.
|
||||
*
|
||||
* Advances @c g_arena_offset by @c SF_ALIGN_UP(size). Returns a pointer
|
||||
* directly into @c g_arena; the caller must not @c free() the result.
|
||||
* Returns NULL (cast 0) when the arena is exhausted or @c size is zero.
|
||||
* Lazy-calls @c sf_alloc_init() if the arena has not yet been initialised.
|
||||
*
|
||||
* @param size Number of bytes to allocate; 0 returns NULL
|
||||
* @return Pointer into the static arena, or NULL on exhaustion or zero size
|
||||
*/
|
||||
void* sf_malloc(size_t size)
|
||||
{
|
||||
if (!g_initialized) sf_alloc_init();
|
||||
if (size == 0) return (void*)0;
|
||||
|
||||
size_t aligned_size = SF_ALIGN_UP(size);
|
||||
size_t new_offset = g_arena_offset + aligned_size;
|
||||
|
||||
if (new_offset > SF_ARENA_SIZE)
|
||||
{
|
||||
/* Out of memory */
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
void* ptr = &g_arena[g_arena_offset];
|
||||
g_arena_offset = new_offset;
|
||||
|
||||
g_stats.used_bytes = g_arena_offset;
|
||||
g_stats.alloc_count++;
|
||||
if (g_stats.used_bytes > g_stats.peak_bytes)
|
||||
{
|
||||
g_stats.peak_bytes = g_stats.used_bytes;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Allocate a zero-initialised block of @c count * @c size bytes from the arena.
|
||||
*
|
||||
* Delegates to @c sf_malloc() then manually zeroes the result with a byte loop
|
||||
* (no @c memset dependency — avoids linking the C runtime in freestanding builds).
|
||||
* Guards against @c count*size overflow by verifying @c total/size == count.
|
||||
*
|
||||
* @param count Number of elements; 0 returns NULL
|
||||
* @param size Size of each element in bytes; 0 returns NULL
|
||||
* @return Pointer to zero-filled arena memory, or NULL on overflow/exhaustion/zero args
|
||||
*/
|
||||
void* sf_calloc(size_t count, size_t size)
|
||||
{
|
||||
if (count == 0 || size == 0) return (void*)0;
|
||||
|
||||
size_t total = count * size;
|
||||
|
||||
/* Check for overflow */
|
||||
if (size != 0 && total / size != count)
|
||||
{
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
void* ptr = sf_malloc(total);
|
||||
if (ptr)
|
||||
{
|
||||
/* Zero-initialize */
|
||||
uint8_t* p = (uint8_t*)ptr;
|
||||
for (size_t i = 0; i < total; i++)
|
||||
{
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Resize an allocation — bump-allocator approximation.
|
||||
*
|
||||
* Because the arena does not store block sizes, the old block cannot be
|
||||
* reclaimed. This function allocates a fresh block of @c new_size bytes and
|
||||
* returns it; the old block is orphaned in the arena. The caller is responsible
|
||||
* for copying any content from @c ptr to the returned pointer before discarding
|
||||
* @c ptr. If @c ptr is NULL, behaves identically to @c sf_malloc(new_size).
|
||||
*
|
||||
* @param ptr Existing allocation (orphaned after this call); NULL is safe
|
||||
* @param new_size Desired size in bytes; 0 returns NULL
|
||||
* @return Pointer to a new arena block of at least @c new_size bytes, or NULL
|
||||
*/
|
||||
void* sf_realloc(void* ptr, size_t new_size)
|
||||
{
|
||||
/*
|
||||
* Bump allocator limitation: we don't track allocation sizes.
|
||||
* For kernel use, realloc is rarely needed.
|
||||
*
|
||||
* Strategy: allocate new block, caller must copy if needed.
|
||||
* This wastes memory but works for rare realloc cases.
|
||||
*
|
||||
* If ptr is NULL, this is equivalent to malloc.
|
||||
*/
|
||||
if (!ptr) return sf_malloc(new_size);
|
||||
if (new_size == 0) return (void*)0;
|
||||
|
||||
/* Allocate new block - old block is orphaned (bump allocator limitation) */
|
||||
return sf_malloc(new_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Release a block — no-op for the bump allocator.
|
||||
*
|
||||
* The kernel arena has no reclaim mechanism; memory is held until the
|
||||
* next @c sf_alloc_init() call (i.e. the next power cycle). Increments
|
||||
* @c g_stats.free_count so the caller can observe that @c sf_free() was
|
||||
* invoked, but no bytes are returned to the arena.
|
||||
*
|
||||
* @param ptr Block to "free"; NULL is a safe no-op
|
||||
*/
|
||||
void sf_free(void* ptr)
|
||||
{
|
||||
/* Bump allocator: free is a no-op.
|
||||
*
|
||||
* This is acceptable because:
|
||||
* 1. VM allocations happen at init time
|
||||
* 2. VM runs until power-off
|
||||
* 3. No fragmentation issues in practice
|
||||
*
|
||||
* If needed, could implement a simple free list here.
|
||||
*/
|
||||
if (ptr)
|
||||
{
|
||||
g_stats.free_count++;
|
||||
}
|
||||
(void)ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Copy the current arena statistics into @c stats.
|
||||
*
|
||||
* Fills @c *stats with a snapshot of @c g_stats. @c total_bytes is fixed at
|
||||
* @c SF_ARENA_SIZE; @c used_bytes equals the current arena offset (exact,
|
||||
* since no reclaim occurs); @c free_count tracks @c sf_free() call count
|
||||
* even though no memory is actually released.
|
||||
*
|
||||
* @param stats Output buffer to populate; no-op if NULL
|
||||
*/
|
||||
void sf_alloc_get_stats(sf_alloc_stats_t* stats)
|
||||
{
|
||||
if (!stats) return;
|
||||
*stats = g_stats;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# src/platform/l4re/
|
||||
|
||||
- `time.c` — HISTORICAL: L4Re/Fiasco.OC timing backend implementing
|
||||
`include/platform_time.h`. L4Re was a supported platform target through
|
||||
mid-2026; it has been removed as an active target, and this file is no
|
||||
longer wired into any build (hosted or kernel).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
# src/platform/linux/
|
||||
|
||||
- `time.c` — POSIX timing backend (`clock_gettime`) for the hosted Linux
|
||||
build; implements `include/platform_time.h`.
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 Platform IO - Linux/POSIX Backend
|
||||
*
|
||||
* Implements platform_io.h's terminal-readiness check using select() on
|
||||
* fd 0, the standard POSIX non-blocking-stdin-peek idiom. Zero timeout:
|
||||
* a single poll, never blocks.
|
||||
*
|
||||
* License: See LICENSE file
|
||||
*/
|
||||
|
||||
#include "../../../include/platform_io.h"
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int sf_terminal_ready(void) {
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
int r = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
|
||||
return (r > 0 && FD_ISSET(STDIN_FILENO, &readfds)) ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* StarForth Platform Time - Linux/POSIX Backend
|
||||
*
|
||||
* Implements platform time abstraction using POSIX clock_gettime().
|
||||
*
|
||||
* License: See LICENSE file
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#include "../../../include/platform_time.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
/* POSIX implementation */
|
||||
|
||||
/**
|
||||
* @brief Return the current monotonic time in nanoseconds.
|
||||
*
|
||||
* Reads @c CLOCK_MONOTONIC via @c clock_gettime(). The clock advances
|
||||
* continuously and is unaffected by wall-clock adjustments — suitable for
|
||||
* measuring elapsed durations such as execution latency and decay intervals.
|
||||
* Returns 0 on @c clock_gettime() failure (should not occur on any modern
|
||||
* POSIX kernel).
|
||||
*
|
||||
* @return Nanoseconds since an arbitrary but fixed epoch, or 0 on error
|
||||
*/
|
||||
static sf_time_ns_t posix_get_monotonic_ns(void) {
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
|
||||
return 0;
|
||||
return (sf_time_ns_t) ts.tv_sec * 1000000000ULL + (sf_time_ns_t) ts.tv_nsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current wall-clock time in nanoseconds since the Unix epoch.
|
||||
*
|
||||
* Reads @c CLOCK_REALTIME via @c clock_gettime(). Subject to NTP or @c settimeofday()
|
||||
* adjustments — use @c posix_get_monotonic_ns() for interval timing.
|
||||
* Returns 0 on @c clock_gettime() failure.
|
||||
*
|
||||
* @return Nanoseconds since 1970-01-01 00:00:00 UTC, or 0 on error
|
||||
*/
|
||||
static sf_time_ns_t posix_get_realtime_ns(void) {
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
|
||||
return 0;
|
||||
return (sf_time_ns_t) ts.tv_sec * 1000000000ULL + (sf_time_ns_t) ts.tv_nsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the system real-time clock to @c ns_since_epoch.
|
||||
*
|
||||
* Converts @c ns_since_epoch to a @c struct timespec and calls
|
||||
* @c clock_settime(CLOCK_REALTIME). Requires @c CAP_SYS_TIME or equivalent
|
||||
* privilege; fails with @c EPERM if the caller lacks permission.
|
||||
*
|
||||
* @param ns_since_epoch Desired new wall-clock time in nanoseconds since Unix epoch
|
||||
* @return 0 on success, non-zero errno-encoded error on failure
|
||||
*/
|
||||
static int posix_set_realtime_ns(sf_time_ns_t ns_since_epoch) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t)(ns_since_epoch / 1000000000ULL);
|
||||
ts.tv_nsec = (long) (ns_since_epoch % 1000000000ULL);
|
||||
return clock_settime(CLOCK_REALTIME, &ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Format a nanosecond-precision wall-clock time as a human-readable string.
|
||||
*
|
||||
* Converts @c ns_since_epoch to local time, formats hours/minutes/seconds via
|
||||
* @c strftime(), then appends a three-digit millisecond suffix. The result is
|
||||
* written into @c buf (must be at least @c SF_TIME_STAMP_SIZE bytes). On
|
||||
* @c localtime() or @c strftime() failure, writes @c "??:??:??.???" into @c buf
|
||||
* and returns -1.
|
||||
*
|
||||
* @param ns_since_epoch Wall-clock time in nanoseconds since Unix epoch
|
||||
* @param buf Output buffer (at least @c SF_TIME_STAMP_SIZE bytes); must not be NULL
|
||||
* @param format_24h Non-zero for 24-hour format (@c %H:%M:%S), zero for 12-hour (@c %I:%M:%S %p)
|
||||
* @return 0 on success, -1 if @c buf is NULL or time formatting fails
|
||||
*/
|
||||
static int posix_format_timestamp(sf_time_ns_t ns_since_epoch, char *buf, int format_24h) {
|
||||
if (!buf)
|
||||
return -1;
|
||||
|
||||
time_t seconds = (time_t)(ns_since_epoch / 1000000000ULL);
|
||||
unsigned int ms = (unsigned int)((ns_since_epoch % 1000000000ULL) / 1000000ULL);
|
||||
struct tm *tm_info = localtime(&seconds);
|
||||
if (!tm_info) {
|
||||
strncpy(buf, "??:??:??.???", SF_TIME_STAMP_SIZE - 1);
|
||||
buf[SF_TIME_STAMP_SIZE - 1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
char hms[16];
|
||||
const char *format = format_24h ? "%H:%M:%S" : "%I:%M:%S %p";
|
||||
if (strftime(hms, sizeof(hms), format, tm_info) == 0) {
|
||||
strncpy(buf, "??:??:??.???", SF_TIME_STAMP_SIZE - 1);
|
||||
buf[SF_TIME_STAMP_SIZE - 1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(buf, SF_TIME_STAMP_SIZE, "%s.%03u", hms, ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Probe whether a real-time clock is available.
|
||||
*
|
||||
* Attempts a @c clock_gettime(CLOCK_REALTIME) probe. POSIX systems invariably
|
||||
* support @c CLOCK_REALTIME so this will return 1 in practice; the check
|
||||
* mirrors the bare-metal backend's interface where RTC presence is genuinely
|
||||
* uncertain.
|
||||
*
|
||||
* @return 1 if @c CLOCK_REALTIME is readable, 0 otherwise
|
||||
*/
|
||||
static int posix_has_rtc(void) {
|
||||
/* POSIX systems typically have working CLOCK_REALTIME */
|
||||
struct timespec ts;
|
||||
return (clock_gettime(CLOCK_REALTIME, &ts) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* POSIX backend vtable */
|
||||
const sf_time_backend_t sf_time_backend_posix = {
|
||||
.get_monotonic_ns = posix_get_monotonic_ns,
|
||||
.get_realtime_ns = posix_get_realtime_ns,
|
||||
.set_realtime_ns = posix_set_realtime_ns,
|
||||
.format_timestamp = posix_format_timestamp,
|
||||
.has_rtc = posix_has_rtc,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user