Files
LithosAnanake/include/vm_inner_interp_riscv64.h
T

540 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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 s2s6):
* - 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.52.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 s2s6.
*/
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 */