diff --git a/core/kernel.nim b/core/kernel.nim index 15e2922..a638511 100644 --- a/core/kernel.nim +++ b/core/kernel.nim @@ -64,8 +64,15 @@ proc get_ion_load(): int = proc rumpk_yield_internal() {.cdecl, exportc.} = let load = get_ion_load() - # 🏛️ ADAPTIVE GOVERNOR (Phase 3) - if load > 0: + # 🏛️ ADAPTIVE GOVERNOR (Phase 3: FLOOD CONTROL) + # Ring Size = 256. 80% = ~200. + if load > 200: + # BACKPRESSURE MODE: Ring is filling up! Panic Flush! + # Force switch to ION Fiber to drain. + if current_fiber != addr fiber_ion: + switch(addr fiber_ion) + return + elif load > 0: # WAR MODE: Priority to IO Loop. Bypass NexShell/Watchdog. if current_fiber == addr fiber_subject: switch(addr fiber_ion) diff --git a/npl/flood_ion.zig b/npl/flood_ion.zig new file mode 100644 index 0000000..c43b977 --- /dev/null +++ b/npl/flood_ion.zig @@ -0,0 +1,172 @@ +// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) +// RUMPK NPL // FLOOD (THE STRESS TEST) + +const std = @import("std"); + +// 1. The SysTable Contract (Must match Kernel!) +const ION_BASE = 0x83000000; + +const IonPacket = extern struct { + data: u64, + phys: u64, + len: u16, + id: u16, +}; + +const CmdPacket = extern struct { + kind: u32, + arg: u32, +}; + +const RingBufferPacket = extern struct { + head: u32, + tail: u32, + mask: u32, + data: [256]IonPacket, +}; + +const RingBufferCmd = extern struct { + head: u32, + tail: u32, + mask: u32, + data: [256]CmdPacket, +}; + +const SysTable = extern struct { + magic: u32, + s_rx: *RingBufferPacket, + s_tx: *RingBufferPacket, + s_event: *RingBufferPacket, + s_cmd: *RingBufferCmd, +}; + +fn get_systable() *SysTable { + return @ptrFromInt(ION_BASE); +} + +// Minimal Shims +extern fn write(fd: c_int, buf: [*]const u8, count: usize) isize; + +const YIELD_LOC = 0x83000FF0; +fn fiber_yield() void { + const ptr: *const *const fn () callconv(.c) void = @ptrFromInt(YIELD_LOC); + const func = ptr.*; + func(); +} + +fn print(text: []const u8) void { + _ = write(1, text.ptr, text.len); +} + +fn print_u64(val: u64) void { + var buf: [32]u8 = undefined; + const s = std.fmt.bufPrint(&buf, "{}", .{val}) catch "ERR"; + print(s); +} + +inline fn cpu_relax() void { + // Hint to CPU that we are spinning (Pause instruction on x86, NOP on RISC-V/ARM usually) + asm volatile ("nop"); +} + +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { + _ = error_return_trace; + _ = ret_addr; + print("\n[FLOOD] PANIC: "); + print(msg); + print("\n"); + while (true) {} +} + +export fn main() c_int { + const sys = get_systable(); + print("[FLOOD] Opening Sluice Gates...\n"); + + if (sys.magic != 0x4E585553) { + print("[FLOOD] Magic mismatch! Aborting.\n"); + return 1; + } + + var tx_count: u64 = 0; + var drop_count: u64 = 0; + + // The "Firehose" Pattern + const cmd_ring = sys.s_cmd; + + // Use a burst size to allow successful processing before yielding + // or just hammer it? Prompt says "bypass Yield if Full logic... spin briefly". + // But in a cooperative OS, if we spin TOO much, we starve the kernel. + // The Governor Logic: "When RX_RING > 80% Full -> Boost Priority of ION Fiber". + // So if we fill it, the kernel scheduler should eventually switch us out? + // Wait, the scheduler only runs when WE yield. + // "Since they share the CPU (Cooperative), if the Flood Payload spins too much, the Kernel starves." + // Exactly. So we MUST yield occasionally, OR the Governor must be inside the Scheduler... + // but the Scheduler isn't preemptive! + // + // UNLESS... "The Adaptive Governor in sched.nim ... forces a context switch away from the Flood Fiber". + // How can it force a switch if we don't call yield or suffer an interrupt? + // Ah, Rumpk v1.1 is currently COOPERATIVE. There is no preemption yet (Timer IRQ is mocked/disabled). + // So the Flood Payload *MUST* call `fiber_yield()`. + // If it *refuses* to yield (while(true) without yield), the system hangs (Saboteur scenario). + // + // However, the instructions say: "Verify the 'War Mode' Governor stays locked...". + // This implies we DO yield, but we want to maximize our time slice? + // No, War Mode in Task 1 was about *bypassing* the other fibers (NexShell/Watchdog). + // + // Here, we have Contention: Producer (Flood) vs Consumer (ION). + // If Flood yields, ION runs. ION drains. Flood runs. + // If Flood creates packets faster than ION consumes, Ring fills. + // Once Ring is full, Flood MUST yield. + // + // So the test is: + // Can we saturate the ring? + // Does the system recover (drain) when full? + // Do we avoid "Drops = 100%" (which implies ION never runs)? + + while (true) { + // 1. Attempt Atomic Push (Physics) + const head = @atomicLoad(u32, &cmd_ring.head, .monotonic); + const tail = @atomicLoad(u32, &cmd_ring.tail, .monotonic); + const mask = cmd_ring.mask; + const next = (head + 1) & mask; + + var success = false; + if (next != tail) { + // Space available + cmd_ring.data[head & mask] = .{ .kind = 100, .arg = 0 }; // NOOP kind + @atomicStore(u32, &cmd_ring.head, next, .release); + success = true; + } + + if (success) { + tx_count += 1; + // Burst logic: Don't yield immediately on success, try to fill ring. + // But we check periodic telemetry. + } else { + drop_count += 1; + // Backpressure: The Ring is full. + // We spin briefly then yield to let consumer drain. + // If we yield immediately, it's efficient. + // If we spin, we test the "War Mode" Governor's ability to handle pressure? + // Actually, if we spin, we just waste cycles. + // But "We test Backpressure and Starvation". + // Let's yield here. + fiber_yield(); + } + + // 2. Periodic Telemetry (Throttle to avoid IO overhead affecting Flood) + if ((tx_count + drop_count) % 100_000 == 0) { + print("[FLOOD] TX: "); + print_u64(tx_count); + print(" | DROPS: "); + print_u64(drop_count); + print("\n"); + + // Mandatory Yield to keep system alive even if not full? + // If we don't yield on success, we fill the ring very fast. + // Then we hit "drop_count" path and yield. + // That is acceptable behavior. + } + } + return 0; +}