540 lines
16 KiB
C
540 lines
16 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
This file is part of the StarForth project.
|
||
|
||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
|
||
You may obtain a copy of the License at:
|
||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
|
||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
express or implied, including but not limited to the warranties of
|
||
merchantability, fitness for a particular purpose, and noninfringement.
|
||
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
|
||
*/
|
||
|
||
#ifndef VM_INNER_INTERP_RISCV64_H
|
||
#define VM_INNER_INTERP_RISCV64_H
|
||
|
||
#include "vm.h"
|
||
|
||
/*
|
||
* RISC-V 64 DIRECT-THREADED INNER INTERPRETER
|
||
* ============================================
|
||
*
|
||
* Register Allocation (callee-saved s2–s6):
|
||
* - s2 (x18): VM pointer
|
||
* - s3 (x19): Instruction Pointer (IP)
|
||
* - s4 (x20): Data Stack Pointer (DSP) — pointer to TOS cell
|
||
* - s5 (x21): Return Stack Pointer (RSP) — pointer to top return entry
|
||
* - s6 (x22): Top-Of-Stack cache (TOS) — avoids memory round-trip for TOS
|
||
*
|
||
* Using callee-saved registers means the compiler will save/restore them
|
||
* around any C calls made while the inner interpreter is running.
|
||
*
|
||
* Stack convention (matches x86_64 and ARM64 implementations):
|
||
* - Stack grows upward; DSP points to the current TOS cell.
|
||
* - TOS is additionally cached in s6 to avoid one load per operation.
|
||
* - RSP points to the top return-address cell (not past it).
|
||
*
|
||
* NEXT macro (direct-threaded dispatch):
|
||
* ld a0, 0(s3) # a0 = *IP
|
||
* addi s3, s3, 8 # IP += 8
|
||
* jr a0 # jump to code field (jalr x0, a0, 0)
|
||
*
|
||
* Expected performance: 1.5–2.5x vs interpreter loop (fewer branch
|
||
* mispredictions, tighter dispatch path). Less than ARM64 TOS-cache gain
|
||
* because RISC-V lacks post-increment addressing for combined load+advance.
|
||
*/
|
||
|
||
#ifndef USE_DIRECT_THREADING
|
||
#define USE_DIRECT_THREADING 0
|
||
#endif
|
||
|
||
#if USE_DIRECT_THREADING
|
||
|
||
/*
|
||
* NEXT — fetch and dispatch the next word
|
||
*
|
||
* 'jr a0' is a pseudoinstruction for 'jalr x0, a0, 0' which discards
|
||
* the return address (x0 is hardwired zero).
|
||
*/
|
||
#define NEXT_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s3)\n\t" /* a0 = *IP */ \
|
||
"addi s3, s3, 8\n\t" /* IP++ */ \
|
||
"jr a0\n\t" /* jump to code field */ \
|
||
::: "a0", "memory" \
|
||
)
|
||
|
||
/*
|
||
* DOCOL — enter a colon definition
|
||
*/
|
||
static inline void vm_docol_rv64(VM *vm) {
|
||
__asm__ __volatile__(
|
||
/* Push IP onto return stack */
|
||
"addi s5, s5, 8\n\t"
|
||
"sd s3, 0(s5)\n\t"
|
||
|
||
/* Load new IP from current_executing_entry->code_field */
|
||
"ld a0, %[entry]\n\t"
|
||
"ld s3, 0(a0)\n\t"
|
||
|
||
/* NEXT */
|
||
"ld a0, 0(s3)\n\t"
|
||
"addi s3, s3, 8\n\t"
|
||
"jr a0\n\t"
|
||
:
|
||
: [entry]"m"(vm->current_executing_entry)
|
||
: "a0", "s3", "s5", "memory"
|
||
);
|
||
}
|
||
|
||
/*
|
||
* EXIT — return from a colon definition
|
||
*/
|
||
static inline void vm_exit_rv64(void) {
|
||
__asm__ __volatile__(
|
||
/* Pop IP from return stack */
|
||
"ld s3, 0(s5)\n\t"
|
||
"addi s5, s5, -8\n\t"
|
||
|
||
/* NEXT */
|
||
"ld a0, 0(s3)\n\t"
|
||
"addi s3, s3, 8\n\t"
|
||
"jr a0\n\t"
|
||
::: "a0", "s3", "s5", "memory"
|
||
);
|
||
}
|
||
|
||
/*
|
||
* Setup registers before entering the inner interpreter loop.
|
||
* Saves all callee-saved registers we're about to clobber, then
|
||
* loads VM state into s2–s6.
|
||
*/
|
||
static inline void vm_setup_registers_rv64(VM *vm) {
|
||
__asm__ __volatile__(
|
||
/* Save callee-saved regs we're about to use */
|
||
"addi sp, sp, -80\n\t"
|
||
"sd s2, 0(sp)\n\t"
|
||
"sd s3, 8(sp)\n\t"
|
||
"sd s4, 16(sp)\n\t"
|
||
"sd s5, 24(sp)\n\t"
|
||
"sd s6, 32(sp)\n\t"
|
||
"sd s7, 40(sp)\n\t"
|
||
"sd s8, 48(sp)\n\t"
|
||
"sd s9, 56(sp)\n\t"
|
||
"sd s10,64(sp)\n\t"
|
||
"sd s11,72(sp)\n\t"
|
||
|
||
/* s2 = VM pointer */
|
||
"mv s2, %[vm]\n\t"
|
||
|
||
/* s3 = IP */
|
||
"ld s3, %[ip]\n\t"
|
||
|
||
/* s4 = &data_stack[dsp] */
|
||
"lw a0, %[dsp]\n\t"
|
||
"ld a1, %[dstack]\n\t"
|
||
"slli a0, a0, 3\n\t"
|
||
"add s4, a1, a0\n\t"
|
||
|
||
/* s5 = &return_stack[rsp] */
|
||
"lw a0, %[rsp]\n\t"
|
||
"ld a1, %[rstack]\n\t"
|
||
"slli a0, a0, 3\n\t"
|
||
"add s5, a1, a0\n\t"
|
||
|
||
/* s6 = TOS (load if stack non-empty) */
|
||
"lw a0, %[dsp]\n\t"
|
||
"bltz a0, 1f\n\t"
|
||
"ld s6, 0(s4)\n\t"
|
||
"1:\n\t"
|
||
:
|
||
: [vm]"r"(vm),
|
||
[ip]"m"(vm->ip),
|
||
[dsp]"m"(vm->dsp),
|
||
[rsp]"m"(vm->rsp),
|
||
[dstack]"m"(vm->data_stack),
|
||
[rstack]"m"(vm->return_stack)
|
||
: "a0", "a1",
|
||
"s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
|
||
"memory"
|
||
);
|
||
}
|
||
|
||
/*
|
||
* Save registers back to VM structure and restore callee-saved regs.
|
||
*/
|
||
static inline void vm_save_registers_rv64(VM *vm) {
|
||
__asm__ __volatile__(
|
||
/* Flush TOS back to stack */
|
||
"sd s6, 0(s4)\n\t"
|
||
|
||
/* Save IP */
|
||
"sd s3, %[ip]\n\t"
|
||
|
||
/* dsp = (s4 - data_stack_base) / 8 */
|
||
"ld a0, %[dstack]\n\t"
|
||
"sub a1, s4, a0\n\t"
|
||
"srli a1, a1, 3\n\t"
|
||
"sw a1, %[dsp]\n\t"
|
||
|
||
/* rsp = (s5 - return_stack_base) / 8 */
|
||
"ld a0, %[rstack]\n\t"
|
||
"sub a1, s5, a0\n\t"
|
||
"srli a1, a1, 3\n\t"
|
||
"sw a1, %[rsp]\n\t"
|
||
|
||
/* Restore callee-saved regs */
|
||
"ld s2, 0(sp)\n\t"
|
||
"ld s3, 8(sp)\n\t"
|
||
"ld s4, 16(sp)\n\t"
|
||
"ld s5, 24(sp)\n\t"
|
||
"ld s6, 32(sp)\n\t"
|
||
"ld s7, 40(sp)\n\t"
|
||
"ld s8, 48(sp)\n\t"
|
||
"ld s9, 56(sp)\n\t"
|
||
"ld s10,64(sp)\n\t"
|
||
"ld s11,72(sp)\n\t"
|
||
"addi sp, sp, 80\n\t"
|
||
: [ip]"=m"(vm->ip),
|
||
[dsp]"=m"(vm->dsp),
|
||
[rsp]"=m"(vm->rsp)
|
||
: [dstack]"m"(vm->data_stack),
|
||
[rstack]"m"(vm->return_stack)
|
||
: "a0", "a1", "memory"
|
||
);
|
||
}
|
||
|
||
/* ============================================================================
|
||
* Primitive Word Macros
|
||
*
|
||
* TOS is cached in s6. s4 = DSP (pointer to TOS cell). s5 = RSP.
|
||
* s2 = VM pointer (used for memory-relative ops).
|
||
* a0/a1 are scratch (caller-saved, safe to clobber).
|
||
* ============================================================================
|
||
*/
|
||
|
||
/* DUP — duplicate TOS */
|
||
#define PRIM_DUP_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"addi s4, s4, 8\n\t" /* DSP++ */ \
|
||
"sd s6, 0(s4)\n\t" /* push TOS copy */ \
|
||
::: "s4", "memory" \
|
||
)
|
||
|
||
/* DROP — discard TOS */
|
||
#define PRIM_DROP_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld s6, -8(s4)\n\t" /* new TOS = NOS */ \
|
||
"addi s4, s4, -8\n\t" /* DSP-- */ \
|
||
::: "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* SWAP — swap TOS and NOS */
|
||
#define PRIM_SWAP_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
|
||
"sd s6, 0(s4)\n\t" /* NOS = old TOS */ \
|
||
"mv s6, a0\n\t" /* TOS = old NOS */ \
|
||
::: "a0", "s6", "memory" \
|
||
)
|
||
|
||
/* OVER — copy NOS to TOS */
|
||
#define PRIM_OVER_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
|
||
"addi s4, s4, 8\n\t" /* DSP++ */ \
|
||
"sd s6, 0(s4)\n\t" /* push old TOS */ \
|
||
"mv s6, a0\n\t" /* TOS = NOS */ \
|
||
::: "a0", "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* ROT — rotate top three: ( a b c — b c a ) */
|
||
#define PRIM_ROT_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = item2 (NOS) */ \
|
||
"ld a1, -8(s4)\n\t" /* a1 = item3 */ \
|
||
"sd a1, 0(s4)\n\t" /* item3 -> NOS position */ \
|
||
"sd s6, -8(s4)\n\t" /* TOS -> item3 position */ \
|
||
"mv s6, a0\n\t" /* item2 -> TOS */ \
|
||
::: "a0", "a1", "s6", "memory" \
|
||
)
|
||
|
||
/* + */
|
||
#define PRIM_PLUS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"add s6, s6, a0\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* - */
|
||
#define PRIM_MINUS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"sub s6, a0, s6\n\t" /* TOS = NOS - TOS */ \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* * */
|
||
#define PRIM_STAR_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"mul s6, s6, a0\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* / — signed divide NOS/TOS */
|
||
#define PRIM_SLASH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS (dividend) */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"div s6, a0, s6\n\t" /* TOS = NOS / TOS */ \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* MOD */
|
||
#define PRIM_MOD_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"rem s6, a0, s6\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* AND */
|
||
#define PRIM_AND_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"and s6, s6, a0\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* OR */
|
||
#define PRIM_OR_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"or s6, s6, a0\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* XOR */
|
||
#define PRIM_XOR_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"xor s6, s6, a0\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* INVERT — bitwise NOT */
|
||
#define PRIM_INVERT_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"not s6, s6\n\t" \
|
||
::: "s6" \
|
||
)
|
||
|
||
/* NEGATE — two's complement */
|
||
#define PRIM_NEGATE_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"neg s6, s6\n\t" \
|
||
::: "s6" \
|
||
)
|
||
|
||
/* @ — fetch cell from VM memory */
|
||
#define PRIM_FETCH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s2)\n\t" /* a0 = vm->memory */ \
|
||
"add a0, a0, s6\n\t" /* a0 = &memory[TOS] */ \
|
||
"ld s6, 0(a0)\n\t" /* TOS = memory[TOS] */ \
|
||
::: "a0", "s6", "memory" \
|
||
)
|
||
|
||
/* ! — store cell to VM memory */
|
||
#define PRIM_STORE_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = addr (NOS) */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"ld a1, 0(s2)\n\t" /* a1 = vm->memory */ \
|
||
"add a1, a1, a0\n\t" \
|
||
"sd s6, 0(a1)\n\t" /* memory[addr] = TOS */ \
|
||
"ld s6, 0(s4)\n\t" /* load new TOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
::: "a0", "a1", "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* C@ — fetch byte */
|
||
#define PRIM_C_FETCH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s2)\n\t" \
|
||
"add a0, a0, s6\n\t" \
|
||
"lbu s6, 0(a0)\n\t" /* zero-extend byte */ \
|
||
::: "a0", "s6", "memory" \
|
||
)
|
||
|
||
/* C! — store byte */
|
||
#define PRIM_C_STORE_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = addr */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"ld a1, 0(s2)\n\t" \
|
||
"add a1, a1, a0\n\t" \
|
||
"sb s6, 0(a1)\n\t" \
|
||
"ld s6, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
::: "a0", "a1", "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* >R — push TOS to return stack */
|
||
#define PRIM_TO_R_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"addi s5, s5, 8\n\t" \
|
||
"sd s6, 0(s5)\n\t" /* RS[top] = TOS */ \
|
||
"ld s6, 0(s4)\n\t" /* new TOS = NOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
::: "s4", "s5", "s6", "memory" \
|
||
)
|
||
|
||
/* R> — pop return stack to data stack */
|
||
#define PRIM_R_FROM_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"addi s4, s4, 8\n\t" \
|
||
"sd s6, 0(s4)\n\t" /* push old TOS */ \
|
||
"ld s6, 0(s5)\n\t" /* TOS = RS top */ \
|
||
"addi s5, s5, -8\n\t" \
|
||
::: "s4", "s5", "s6", "memory" \
|
||
)
|
||
|
||
/* R@ — copy top of return stack (no pop) */
|
||
#define PRIM_R_FETCH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"addi s4, s4, 8\n\t" \
|
||
"sd s6, 0(s4)\n\t" \
|
||
"ld s6, 0(s5)\n\t" /* TOS = RS top (no pop) */ \
|
||
::: "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* 2DUP — duplicate top two */
|
||
#define PRIM_2DUP_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
|
||
"addi s4, s4, 8\n\t" \
|
||
"sd a0, 0(s4)\n\t" /* push NOS */ \
|
||
"addi s4, s4, 8\n\t" \
|
||
"sd s6, 0(s4)\n\t" /* push TOS copy */ \
|
||
::: "a0", "s4", "memory" \
|
||
)
|
||
|
||
/* 2DROP — drop top two */
|
||
#define PRIM_2DROP_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld s6, -8(s4)\n\t" /* new TOS = item3 */ \
|
||
"addi s4, s4, -16\n\t" \
|
||
::: "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* 0= */
|
||
#define PRIM_ZERO_EQUALS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"seqz s6, s6\n\t" /* 1 if zero, else 0 */ \
|
||
"neg s6, s6\n\t" /* convert to -1 (true) */ \
|
||
::: "s6" \
|
||
)
|
||
|
||
/* 0< */
|
||
#define PRIM_ZERO_LESS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"srai s6, s6, 63\n\t" /* -1 if negative, 0 if >= 0 */ \
|
||
::: "s6" \
|
||
)
|
||
|
||
/* = */
|
||
#define PRIM_EQUALS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"sub s6, a0, s6\n\t" \
|
||
"seqz s6, s6\n\t" \
|
||
"neg s6, s6\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* < — NOS < TOS */
|
||
#define PRIM_LESS_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"slt s6, a0, s6\n\t" /* 1 if NOS < TOS */ \
|
||
"neg s6, s6\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* > — NOS > TOS */
|
||
#define PRIM_GREATER_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s4)\n\t" \
|
||
"addi s4, s4, -8\n\t" \
|
||
"slt s6, s6, a0\n\t" /* 1 if TOS < NOS, i.e. NOS > TOS */ \
|
||
"neg s6, s6\n\t" \
|
||
::: "a0", "s4", "s6" \
|
||
)
|
||
|
||
/* BRANCH — unconditional */
|
||
#define PRIM_BRANCH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s3)\n\t" /* offset */ \
|
||
"add s3, s3, a0\n\t" /* IP += offset */ \
|
||
::: "a0", "s3", "memory" \
|
||
)
|
||
|
||
/* 0BRANCH — branch if TOS == 0 */
|
||
#define PRIM_ZBRANCH_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"ld a0, 0(s3)\n\t" /* offset */ \
|
||
"ld s6, 0(s4)\n\t" /* new TOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
/* if old TOS was zero: IP += offset; else IP += 8 */ \
|
||
"bnez s6, 1f\n\t" /* skip branch if nonzero */ \
|
||
"add s3, s3, a0\n\t" \
|
||
"j 2f\n\t" \
|
||
"1:\n\t" \
|
||
"addi s3, s3, 8\n\t" \
|
||
"2:\n\t" \
|
||
::: "a0", "s3", "s4", "s6", "memory" \
|
||
)
|
||
|
||
/* EXECUTE — execute word whose xt is TOS */
|
||
#define PRIM_EXECUTE_RV64() \
|
||
__asm__ __volatile__ ( \
|
||
"mv a0, s6\n\t" /* a0 = xt */ \
|
||
"ld s6, 0(s4)\n\t" /* new TOS */ \
|
||
"addi s4, s4, -8\n\t" \
|
||
"jr a0\n\t" /* jump to xt */ \
|
||
::: "a0", "s4", "s6", "memory" \
|
||
)
|
||
|
||
#else /* !USE_DIRECT_THREADING */
|
||
|
||
#define NEXT_RV64() do {} while(0)
|
||
#define vm_setup_registers_rv64(vm) do {} while(0)
|
||
#define vm_save_registers_rv64(vm) do {} while(0)
|
||
|
||
#endif /* USE_DIRECT_THREADING */
|
||
|
||
#endif /* VM_INNER_INTERP_RISCV64_H */
|