622 lines
12 KiB
Plaintext
622 lines
12 KiB
Plaintext
// Moved from docs/src/performance-profiling/ASM_OPTIMIZATIONS.adoc to docs/working/scratch/src/performance-profiling/ASM_OPTIMIZATIONS.adoc on 2026-06-16 (docs reorg Phase 2)
|
||
== StarForth Assembly Optimizations
|
||
:toc: left
|
||
:toc-title: Contents
|
||
:toclevels: 3
|
||
xref:../README.adoc[← Back to Documentation Index]
|
||
|
||
|
||
|
||
=== Overview
|
||
|
||
This document describes the x86_64 assembly optimizations available for
|
||
StarForth, suitable for both standard Linux environments and L4Re
|
||
microkernel deployments (StarshipOS).
|
||
|
||
=== Files
|
||
|
||
* `+include/vm_asm_opt.h+` - Basic stack and arithmetic optimizations
|
||
* `+include/vm_inner_interp_asm.h+` - Advanced direct-threaded inner
|
||
interpreter
|
||
|
||
=== Performance Impact
|
||
|
||
[width="100%",cols="28%,12%,12%,48%",options="header",]
|
||
|===
|
||
|Optimization |Impact |Speedup |Use Case
|
||
|Stack operations |EXTREME |2-3x |Every word execution
|
||
|Inner interpreter |EXTREME |3-5x |Threaded code (colon definitions)
|
||
|Arithmetic ops |HIGH |1.5-2x |Math-heavy code
|
||
|Dictionary lookup |HIGH |2-3x |Word compilation/interpretation
|
||
|String operations |MEDIUM |1.5-2x |Text processing
|
||
|===
|
||
|
||
=== Quick Start
|
||
|
||
==== 1. Enable Optimizations
|
||
|
||
Edit your `+Makefile+` or build script:
|
||
|
||
[source,makefile]
|
||
----
|
||
# Add to CFLAGS
|
||
CFLAGS += -DUSE_ASM_OPT=1 -DUSE_DIRECT_THREADING=1 -O3 -march=native
|
||
|
||
# For L4Re/StarshipOS
|
||
CFLAGS += -DUSE_ASM_OPT=1 -march=x86-64-v2 -O3
|
||
----
|
||
|
||
==== 2. Include Headers
|
||
|
||
In your `+src/stack_management.c+`:
|
||
|
||
[source,c]
|
||
----
|
||
#include "../include/vm.h"
|
||
#include "../include/vm_asm_opt.h"
|
||
|
||
void vm_push(VM *vm, cell_t value) {
|
||
#if USE_ASM_OPT
|
||
vm_push_asm(vm, value);
|
||
#else
|
||
// Original C implementation
|
||
if (vm->dsp >= STACK_SIZE - 1) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
vm->data_stack[++vm->dsp] = value;
|
||
#endif
|
||
}
|
||
|
||
cell_t vm_pop(VM *vm) {
|
||
#if USE_ASM_OPT
|
||
return vm_pop_asm(vm);
|
||
#else
|
||
// Original C implementation
|
||
if (vm->dsp < 0) {
|
||
vm->error = 1;
|
||
return 0;
|
||
}
|
||
return vm->data_stack[vm->dsp--];
|
||
#endif
|
||
}
|
||
----
|
||
|
||
==== 3. Optimize Arithmetic Words
|
||
|
||
In `+src/word_source/arithmetic_words.c+`:
|
||
|
||
[source,c]
|
||
----
|
||
#include "../../include/vm_asm_opt.h"
|
||
|
||
void arithmetic_word_plus(VM *vm) {
|
||
if (vm->dsp < 1) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
#if USE_ASM_OPT
|
||
cell_t n2 = vm_pop_asm(vm);
|
||
cell_t n1 = vm_pop_asm(vm);
|
||
cell_t result;
|
||
|
||
if (vm_add_check_overflow(n1, n2, &result)) {
|
||
// Handle overflow
|
||
log_message(LOG_ERROR, "+: Overflow detected");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
vm_push_asm(vm, result);
|
||
#else
|
||
// Original implementation
|
||
cell_t n2 = vm_pop(vm);
|
||
cell_t n1 = vm_pop(vm);
|
||
vm_push(vm, n1 + n2);
|
||
#endif
|
||
}
|
||
|
||
void arithmetic_word_star_slash(VM *vm) {
|
||
// n1 n2 n3 -- n4 (n1*n2/n3)
|
||
if (vm->dsp < 2) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
#if USE_ASM_OPT
|
||
cell_t n3 = vm_pop_asm(vm);
|
||
cell_t n2 = vm_pop_asm(vm);
|
||
cell_t n1 = vm_pop_asm(vm);
|
||
|
||
if (n3 == 0) {
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
// Use double-width multiplication
|
||
cell_t hi, lo;
|
||
vm_mul_double(n1, n2, &hi, &lo);
|
||
|
||
// Now divide the 128-bit result by n3
|
||
// (Note: Full 128-bit division requires more complex code)
|
||
// For now, assume result fits in 64 bits:
|
||
cell_t result = lo / n3; // Simplified
|
||
|
||
vm_push_asm(vm, result);
|
||
#else
|
||
// Original implementation
|
||
cell_t n3 = vm_pop(vm);
|
||
cell_t n2 = vm_pop(vm);
|
||
cell_t n1 = vm_pop(vm);
|
||
vm_push(vm, (n1 * n2) / n3);
|
||
#endif
|
||
}
|
||
----
|
||
|
||
==== 4. Optimize Dictionary Lookup
|
||
|
||
In `+src/dictionary_management.c+`:
|
||
|
||
[source,c]
|
||
----
|
||
#include "../include/vm_asm_opt.h"
|
||
|
||
DictEntry *vm_find_word(VM *vm, const char *name, size_t len) {
|
||
for (DictEntry *e = vm->latest; e; e = e->link) {
|
||
if (e->name_len == len) {
|
||
#if USE_ASM_OPT
|
||
if (vm_strcmp_asm(e->name, name, len) == 0) {
|
||
return e;
|
||
}
|
||
#else
|
||
if (memcmp(e->name, name, len) == 0) {
|
||
return e;
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
----
|
||
|
||
=== Advanced: Direct-Threaded Inner Interpreter
|
||
|
||
This is the most complex but highest-impact optimization.
|
||
|
||
==== Understanding Direct Threading
|
||
|
||
Traditional C implementation:
|
||
|
||
[source,c]
|
||
----
|
||
void execute_colon_word(VM *vm) {
|
||
cell_t *ip = vm->ip;
|
||
while (*ip) {
|
||
DictEntry *word = (DictEntry*)*ip++;
|
||
word->func(vm); // Function call overhead!
|
||
if (vm->exit_colon) break;
|
||
}
|
||
}
|
||
----
|
||
|
||
Problems:
|
||
|
||
* Function call/return for EVERY word (expensive)
|
||
* Poor CPU branch prediction
|
||
* Register spilling
|
||
|
||
Direct-threaded solution:
|
||
|
||
[source,c]
|
||
----
|
||
void execute_colon_word_fast(VM *vm) {
|
||
vm_setup_registers(vm);
|
||
|
||
// Now IP, stacks are in registers
|
||
// Each word does its work and jumps to NEXT
|
||
// No function call overhead!
|
||
|
||
vm_save_registers(vm);
|
||
}
|
||
----
|
||
|
||
==== Implementation Example
|
||
|
||
Create a new file `+src/vm_primitives_asm.c+`:
|
||
|
||
[source,c]
|
||
----
|
||
#include "../include/vm.h"
|
||
#include "../include/vm_inner_interp_asm.h"
|
||
|
||
#if USE_DIRECT_THREADING
|
||
|
||
// Each primitive word uses the PRIM_* macros and NEXT_ASM()
|
||
|
||
void forth_dup_fast(void) {
|
||
PRIM_DUP();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_drop_fast(void) {
|
||
PRIM_DROP();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_swap_fast(void) {
|
||
PRIM_SWAP();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_plus_fast(void) {
|
||
PRIM_PLUS();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_minus_fast(void) {
|
||
PRIM_MINUS();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_star_fast(void) {
|
||
PRIM_STAR();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_fetch_fast(void) {
|
||
PRIM_FETCH();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_store_fast(void) {
|
||
PRIM_STORE();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_to_r_fast(void) {
|
||
PRIM_TO_R();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_r_from_fast(void) {
|
||
PRIM_R_FROM();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_branch_fast(void) {
|
||
PRIM_BRANCH();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
void forth_zbranch_fast(void) {
|
||
PRIM_ZBRANCH();
|
||
NEXT_ASM();
|
||
}
|
||
|
||
#endif // USE_DIRECT_THREADING
|
||
----
|
||
|
||
=== Benchmarking
|
||
|
||
==== 1. Create Test Suite
|
||
|
||
Create `+benchmarks/stack_bench.fth+`:
|
||
|
||
[source,forth]
|
||
----
|
||
: BENCH-DUP ( n -- )
|
||
0 DO 123 DUP DROP LOOP ;
|
||
|
||
: BENCH-MATH ( n -- )
|
||
0 DO 10 20 + 5 * 100 / DROP LOOP ;
|
||
|
||
: BENCH-STACK ( n -- )
|
||
0 DO 1 2 3 4 5 DROP DROP DROP DROP DROP LOOP ;
|
||
|
||
\ Run benchmarks
|
||
." Testing DUP/DROP: "
|
||
1000000 BENCH-DUP
|
||
." Done" CR
|
||
|
||
." Testing arithmetic: "
|
||
1000000 BENCH-MATH
|
||
." Done" CR
|
||
|
||
." Testing stack ops: "
|
||
1000000 BENCH-STACK
|
||
." Done" CR
|
||
----
|
||
|
||
==== 2. Measure Performance
|
||
|
||
[source,bash]
|
||
----
|
||
# Baseline (no optimizations)
|
||
make clean
|
||
make CFLAGS="-std=c99 -O2 -Iinclude"
|
||
time ./build/starforth benchmarks/stack_bench.fth
|
||
|
||
# With assembly optimizations
|
||
make clean
|
||
make CFLAGS="-std=c99 -O3 -march=native -DUSE_ASM_OPT=1 -Iinclude"
|
||
time ./build/starforth benchmarks/stack_bench.fth
|
||
|
||
# With direct threading
|
||
make clean
|
||
make CFLAGS="-std=c99 -O3 -march=native -DUSE_ASM_OPT=1 -DUSE_DIRECT_THREADING=1 -Iinclude"
|
||
time ./build/starforth benchmarks/stack_bench.fth
|
||
----
|
||
|
||
==== 3. Use perf for Detailed Analysis
|
||
|
||
[source,bash]
|
||
----
|
||
# Profile baseline
|
||
perf record -g ./build/starforth benchmarks/stack_bench.fth
|
||
perf report
|
||
|
||
# Profile optimized
|
||
perf record -g ./build/starforth_asm benchmarks/stack_bench.fth
|
||
perf report
|
||
|
||
# Compare:
|
||
perf stat -r 10 ./build/starforth benchmarks/stack_bench.fth
|
||
perf stat -r 10 ./build/starforth_asm benchmarks/stack_bench.fth
|
||
----
|
||
|
||
=== L4Re / StarshipOS Integration
|
||
|
||
==== Build for L4Re
|
||
|
||
[source,makefile]
|
||
----
|
||
# In your L4Re package Makefile
|
||
CFLAGS += -DUSE_ASM_OPT=1 -O3 -march=x86-64-v2
|
||
CFLAGS += -fno-stack-protector # If in kernel context
|
||
CFLAGS += -mno-red-zone # Required for kernel code
|
||
|
||
# Link with L4Re libraries
|
||
LIBS += -ll4re-util -ll4sys
|
||
----
|
||
|
||
==== Memory Management
|
||
|
||
For L4Re, use L4Re’s allocators:
|
||
|
||
[source,c]
|
||
----
|
||
#include <l4/re/c/mem_alloc.h>
|
||
#include <l4/re/c/dataspace.h>
|
||
|
||
void vm_init_l4re(VM *vm) {
|
||
// Allocate VM memory using L4Re dataspace
|
||
l4re_ds_t ds;
|
||
l4_cap_idx_t ds_cap = l4re_util_cap_alloc();
|
||
|
||
long ret = l4re_ma_alloc(VM_MEMORY_SIZE, ds_cap, 0);
|
||
if (ret) {
|
||
// Handle error
|
||
return;
|
||
}
|
||
|
||
// Map dataspace
|
||
ret = l4re_rm_attach(&vm->memory, VM_MEMORY_SIZE,
|
||
L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_RW,
|
||
ds_cap, 0, L4_PAGESHIFT);
|
||
if (ret) {
|
||
// Handle error
|
||
return;
|
||
}
|
||
|
||
// Continue with normal initialization
|
||
vm->dsp = -1;
|
||
vm->rsp = -1;
|
||
// ...
|
||
}
|
||
----
|
||
|
||
==== IPC Integration
|
||
|
||
For inter-VM communication:
|
||
|
||
[source,c]
|
||
----
|
||
#include <l4/sys/ipc.h>
|
||
|
||
// Send Forth data to another L4Re task
|
||
void vm_ipc_send(VM *vm, l4_cap_idx_t dest) {
|
||
l4_msg_regs_t *mr = l4_utcb_mr();
|
||
|
||
// Pack stack data into message registers
|
||
mr->mr[0] = vm->dsp;
|
||
for (int i = 0; i <= vm->dsp && i < L4_UTCB_GENERIC_DATA_SIZE; i++) {
|
||
mr->mr[i+1] = vm->data_stack[i];
|
||
}
|
||
|
||
l4_msgtag_t tag = l4_msgtag(0, vm->dsp + 2, 0, 0);
|
||
tag = l4_ipc_send(dest, l4_utcb(), tag, L4_IPC_NEVER);
|
||
|
||
if (l4_ipc_error(tag, l4_utcb())) {
|
||
vm->error = 1;
|
||
}
|
||
}
|
||
----
|
||
|
||
=== Debugging
|
||
|
||
==== Disable Optimizations for Debugging
|
||
|
||
[source,makefile]
|
||
----
|
||
debug:
|
||
$(MAKE) CFLAGS="$(BASE_CFLAGS) -O0 -g -DUSE_ASM_OPT=0" all
|
||
----
|
||
|
||
==== GDB Tips
|
||
|
||
[source,bash]
|
||
----
|
||
# Debug assembly-optimized code
|
||
gdb ./build/starforth
|
||
|
||
(gdb) break vm_push_asm
|
||
(gdb) layout asm
|
||
(gdb) stepi
|
||
(gdb) info registers r12 r13 r14 r15
|
||
----
|
||
|
||
==== Verify Correctness
|
||
|
||
Create test suite that runs in both modes:
|
||
|
||
[source,c]
|
||
----
|
||
void test_stack_ops(void) {
|
||
VM vm_c, vm_asm;
|
||
|
||
vm_init(&vm_c);
|
||
vm_init(&vm_asm);
|
||
|
||
// Test with C version
|
||
#undef USE_ASM_OPT
|
||
vm_push(&vm_c, 42);
|
||
vm_push(&vm_c, 17);
|
||
cell_t result_c = vm_pop(&vm_c);
|
||
|
||
// Test with ASM version
|
||
#define USE_ASM_OPT 1
|
||
vm_push_asm(&vm_asm, 42);
|
||
vm_push_asm(&vm_asm, 17);
|
||
cell_t result_asm = vm_pop_asm(&vm_asm);
|
||
|
||
assert(result_c == result_asm);
|
||
assert(vm_c.dsp == vm_asm.dsp);
|
||
}
|
||
----
|
||
|
||
=== Platform Compatibility
|
||
|
||
[width="100%",cols="22%,15%,17%,25%,21%",options="header",]
|
||
|===
|
||
|Platform |Stack Ops |Arithmetic |Direct Threading |Notes
|
||
|Linux x86_64 |✓ |✓ |✓ |Full support
|
||
|L4Re x86_64 |✓ |✓ |✓ |Full support
|
||
|StarshipOS |✓ |✓ |✓ |Kernel & user
|
||
|ARM64 |✗ |✗ |✗ |Future work
|
||
|RISC-V |✗ |✗ |✗ |Future work
|
||
|===
|
||
|
||
=== Safety Considerations
|
||
|
||
==== Stack Overflow Protection
|
||
|
||
The assembly code checks for overflow, but for extra safety:
|
||
|
||
[source,c]
|
||
----
|
||
#if USE_ASM_OPT
|
||
// Add guard pages
|
||
mprotect(vm->data_stack + STACK_SIZE, PAGE_SIZE, PROT_NONE);
|
||
mprotect(vm->return_stack + STACK_SIZE, PAGE_SIZE, PROT_NONE);
|
||
#endif
|
||
----
|
||
|
||
==== Error Handling
|
||
|
||
Assembly code sets `+vm->error+` on overflow/underflow, but doesn’t log
|
||
(for performance). Add logging in debug builds:
|
||
|
||
[source,c]
|
||
----
|
||
#ifdef DEBUG
|
||
#define VM_ERROR_LOG(msg) log_message(LOG_ERROR, msg)
|
||
#else
|
||
#define VM_ERROR_LOG(msg) do {} while(0)
|
||
#endif
|
||
----
|
||
|
||
=== Performance Tips
|
||
|
||
[arabic]
|
||
. *Use PGO (Profile-Guided Optimization)*
|
||
+
|
||
[source,bash]
|
||
----
|
||
# Generate profile
|
||
make CFLAGS="-O3 -fprofile-generate"
|
||
./build/starforth benchmarks/typical_workload.fth
|
||
|
||
# Use profile
|
||
make clean
|
||
make CFLAGS="-O3 -fprofile-use"
|
||
----
|
||
. *Align hot structures*
|
||
+
|
||
[source,c]
|
||
----
|
||
typedef struct VM {
|
||
// ... fields ...
|
||
} __attribute__((aligned(64))) VM; // Cache line aligned
|
||
----
|
||
. *Prefetch dictionary entries*
|
||
+
|
||
[source,c]
|
||
----
|
||
__builtin_prefetch(vm->latest);
|
||
__builtin_prefetch(vm->latest->link);
|
||
----
|
||
. *Use huge pages for VM memory*
|
||
+
|
||
[source,c]
|
||
----
|
||
vm->memory = mmap(NULL, VM_MEMORY_SIZE,
|
||
PROT_READ | PROT_WRITE,
|
||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
|
||
-1, 0);
|
||
----
|
||
|
||
=== Troubleshooting
|
||
|
||
==== "`Illegal instruction`" error
|
||
|
||
* Check CPU features: `+cat /proc/cpuinfo+`
|
||
* Reduce `+-march=native+` to `+-march=x86-64-v2+`
|
||
|
||
==== Crashes in assembly code
|
||
|
||
* Check stack alignment (must be 16-byte aligned)
|
||
* Verify register clobbers are correct
|
||
* Use `+-fsanitize=address+` to catch errors
|
||
|
||
==== Performance not improving
|
||
|
||
* Profile with `+perf+` to find actual bottleneck
|
||
* Check if compiler is inlining the wrappers
|
||
* Verify optimizations are actually enabled
|
||
|
||
=== Contributing
|
||
|
||
When adding new assembly optimizations:
|
||
|
||
[arabic]
|
||
. Benchmark before and after
|
||
. Add both C and ASM versions
|
||
. Test on multiple CPUs
|
||
. Document register usage
|
||
. Add to this README
|
||
|
||
=== References
|
||
|
||
* https://www.complang.tuwien.ac.at/forth/threaded-code.html[Threaded
|
||
Code]
|
||
* https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html[GCC Inline
|
||
Assembly]
|
||
* https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf[x86_64
|
||
ABI]
|
||
* https://l4re.org/doc/[L4Re Documentation]
|
||
|
||
=== License
|
||
|
||
Public domain / CC0. No warranty. Use at your own risk.
|