rumpk/core/fiber.nim

193 lines
6.0 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: Fiber Execution (Motive Power)
##
## Implements the unified multi-arch fiber context switching.
## Supported Architectures: x86_64, AArch64, RISC-V.
##
## SAFETY: Direct manipulation of stack pointers and CPU registers via
## architecture-specific context frames. Swaps page tables during switch.
{.push stackTrace: off, lineTrace: off.}
# =========================================================
# Architecture-Specific Constants
# =========================================================
when defined(amd64) or defined(x86_64):
const CONTEXT_SIZE = 56
const RET_ADDR_INDEX = 6 # RIP at [sp + 48]
const ARCH_NAME = "x86_64"
elif defined(arm64) or defined(aarch64):
const CONTEXT_SIZE = 96
const RET_ADDR_INDEX = 11 # x30 (LR) at [sp + 88]
const ARCH_NAME = "aarch64"
elif defined(riscv64):
const CONTEXT_SIZE = 128
const RET_ADDR_INDEX = 0 # ra at [sp + 0]
const ARCH_NAME = "riscv64"
else:
{.error: "Unsupported architecture for Rumpk fibers".}
# =========================================================
# Types
# =========================================================
type
Spectrum* = enum
Photon = 0 # UI/Audio (Top Tier)
Matter = 1 # Interactive (Middle Tier)
Gravity = 2 # Batch (Bottom Tier)
Void = 3 # Unclassified/Demoted (Default)
FiberState* = object
sp*: uint64 # The Stack Pointer (Must be first field!)
entry*: proc() {.cdecl.} # Entry point for this fiber
Fiber* = ptr FiberObject
FiberObject* = object
id*: uint64
name*: cstring
state*: FiberState
stack*: ptr UncheckedArray[uint8]
stack_size*: int
sleep_until*: uint64 # NS timestamp
promises*: uint64 # [63:62]=Spectrum, [61:0]=Pledge bits
pledge_budget*: uint64 # Microseconds allowed per frame (legacy)
avg_burst*: uint64 # Moving average for telemetry (The Ratchet)
user_entry*: pointer # Phase 29: User function pointer for workers
user_arg*: uint64 # Phase 29: Argument for user function
satp_value*: uint64 # Phase 31: Page table root (0 = use kernel map)
wants_yield*: bool # Phase 37: Deferred yield flag
# SPEC-250: The Ratchet
budget_ns*: uint64 # "I promise to run for X ns max"
last_burst_ns*: uint64 # Actual execution time of last run
violations*: uint32 # Strike counter (3 strikes = demotion)
# Spectrum Accessors
proc getSpectrum*(f: Fiber): Spectrum =
if f == nil: return Spectrum.Void
Spectrum((f.promises shr 62) and 0x3)
proc setSpectrum*(f: Fiber, s: Spectrum) =
if f == nil: return
f.promises = (f.promises and 0x3FFFFFFFFFFFFFFF'u64) or (uint64(s) shl 62)
proc fiber_yield*() {.importc, cdecl.}
# Imports
# =========================================================
# Import the Assembly Magic (same symbol name, different implementation per arch)
proc cpu_switch_to(prev_sp_ptr: ptr uint64, next_sp: uint64) {.importc, cdecl.}
# Phase 31: Page Table Activation
proc mm_activate_satp(satp_val: uint64) {.importc, cdecl.}
proc mm_get_kernel_satp(): uint64 {.importc, cdecl.}
# Import console for debugging
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc debug*(s: string) =
if s.len > 0:
console_write(unsafeAddr s[0], csize_t(s.len))
proc print_arch_info*() =
debug("[Rumpk] Architecture Context: " & ARCH_NAME & "\n")
# =========================================================
# Constants
# =========================================================
const STACK_SIZE* = 4096
# =========================================================
# Fiber State
# =========================================================
var main_fiber: FiberObject
var current_fiber* {.global.}: Fiber = addr main_fiber
# =========================================================
# Trampoline (Entry point for new fibers)
# =========================================================
proc fiber_trampoline() {.cdecl, exportc, noreturn.} =
var msg = "[FIBER] Trampoline Entry!\n"
console_write(addr msg[0], csize_t(msg.len))
let f = current_fiber
if f.state.entry != nil:
f.state.entry()
# If the fiber returns, halt
when defined(amd64) or defined(x86_64):
while true:
{.emit: "asm volatile(\"hlt\");".}
elif defined(arm64) or defined(aarch64):
while true:
{.emit: "asm volatile(\"wfi\");".}
elif defined(riscv64):
while true:
{.emit: "asm volatile(\"wfi\");".}
# =========================================================
# Fiber Initialization (Arch-Specific)
# =========================================================
proc init_fiber*(f: Fiber, entry: proc() {.cdecl.}, stack_base: pointer, size: int) =
f.state.entry = entry
f.stack = cast[ptr UncheckedArray[uint8]](stack_base)
f.stack_size = size
f.sleep_until = 0
# Start at top of stack (using actual size)
var sp = cast[uint64](stack_base) + cast[uint64](size)
# 1. Align to 16 bytes (Universal requirement)
sp = sp and not 15'u64
# 2. Reserve space for the context frame
sp = sp - CONTEXT_SIZE
# 3. Setup the Context (zero all, set return address)
var stack_ptr = cast[ptr UncheckedArray[uint64]](sp)
# Zero out the frame
let num_regs = CONTEXT_SIZE div 8
for i in 0..<num_regs:
stack_ptr[i] = 0
# Set return address to trampoline
stack_ptr[RET_ADDR_INDEX] = cast[uint64](fiber_trampoline)
f.state.sp = sp
# =========================================================
# Context Switch
# =========================================================
proc switch*(next: Fiber) =
let prev = current_fiber
current_fiber = next
# Swap address space if necessary (Phase 31: Sv39 Memory Isolation)
if next.satp_value != 0:
mm_activate_satp(next.satp_value)
else:
# If fiber has no specific satp (0), restore kernel identity map
let k_satp = mm_get_kernel_satp()
if k_satp != 0:
mm_activate_satp(k_satp)
cpu_switch_to(addr prev.state.sp, next.state.sp)
{.pop.}