diff --git a/build.zig b/build.zig index ca5b33e..bc6e81a 100644 --- a/build.zig +++ b/build.zig @@ -5,31 +5,111 @@ // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. -//! Rumpk Build System +//! Rumpk Build System — Unified Pipeline //! -//! Orchestrates L0 (Zig) and L1 (Nim) compilation. -//! Builds the Hardware Abstraction Layer as a static library. +//! Single command builds everything: Nim kernel + Zig HAL + LwIP + link. +//! +//! Usage: +//! zig build # Build kernel (default: riscv64) +//! zig build -Darch=aarch64 # ARM64 target +//! zig build -Darch=x86_64 # AMD64 target +//! zig build nim # Recompile Nim kernel sources only +//! zig build test-lwf # Run LWF tests (native host) const std = @import("std"); +const Arch = enum { riscv64, aarch64, x86_64 }; + +/// Per-architecture build configuration. +const ArchConfig = struct { + cpu_arch: std.Target.Cpu.Arch, + code_model: std.builtin.CodeModel, + switch_asm: []const u8, + linker_script: []const u8, + /// Arch name for build_nim.sh + nim_arch: []const u8, +}; + +fn archConfig(arch: Arch) ArchConfig { + return switch (arch) { + .riscv64 => .{ + .cpu_arch = .riscv64, + .code_model = .medany, + .switch_asm = "hal/arch/riscv64/switch.S", + .linker_script = "boot/linker.ld", + .nim_arch = "riscv64", + }, + .aarch64 => .{ + .cpu_arch = .aarch64, + .code_model = .default, + .switch_asm = "hal/arch/aarch64/switch.S", + .linker_script = "boot/linker_aarch64.ld", + .nim_arch = "aarch64", + }, + .x86_64 => .{ + .cpu_arch = .x86_64, + .code_model = .kernel, + .switch_asm = "hal/arch/x86_64/switch.S", + .linker_script = "boot/linker_x86_64.ld", + .nim_arch = "x86_64", + }, + }; +} + +/// Apply freestanding kernel settings to a module. +fn applyKernelSettings(mod: *std.Build.Module, code_model: std.builtin.CodeModel) void { + mod.red_zone = false; + mod.stack_check = false; + mod.code_model = code_model; + mod.sanitize_thread = false; + mod.single_threaded = true; + mod.strip = true; +} + pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + // ========================================================= + // Target Selection: -Darch=riscv64|aarch64|x86_64 + // ========================================================= + + const arch = b.option(Arch, "arch", "Target architecture (default: riscv64)") orelse .riscv64; + const config = archConfig(arch); + + var query: std.Target.Query = .{ + .cpu_arch = config.cpu_arch, + .os_tag = .freestanding, + .abi = .none, + }; + // Disable NEON/FP in kernel code generation to avoid unaligned SIMD stores + if (config.cpu_arch == .aarch64) { + query.cpu_features_sub = std.Target.aarch64.featureSet(&.{.neon}); + } + const target = b.resolveTargetQuery(query); + + // HEPHAESTUS DECREE: Force ReleaseSmall. A kernel runs naked. + const optimize: std.builtin.OptimizeMode = .ReleaseSmall; + + // ========================================================= + // Step: Nim Kernel Compilation (nim -> C -> .o) + // ========================================================= + // Calls build_nim.sh which handles: + // 1. nim c --compileOnly -> generates C + // 2. zig cc -> cross-compiles C to .o + // Incremental: only recompiles changed files. + + const nim_step = b.addSystemCommand(&.{ "./build_nim.sh", config.nim_arch }); + const nim_build_step = b.step("nim", "Recompile Nim kernel sources (nim -> C -> .o)"); + nim_build_step.dependOn(&nim_step.step); // ========================================================= // L0: Hardware Abstraction Layer (Zig) // ========================================================= - // NOTE(Build): Zig 0.15.x API - using addLibrary with static linkage const hal_mod = b.createModule(.{ .root_source_file = b.path("hal/abi.zig"), .target = target, .optimize = optimize, }); - // Freestanding kernel - no libc, no red zone, no stack checks - hal_mod.red_zone = false; - hal_mod.stack_check = false; - hal_mod.code_model = .medany; + applyKernelSettings(hal_mod, config.code_model); const hal = b.addLibrary(.{ .name = "rumpk_hal", @@ -39,15 +119,6 @@ pub fn build(b: *std.Build) void { b.installArtifact(hal); - // TODO(Build): Microui needs stdio.h stubs for freestanding. - // Re-enable after creating libs/microui/stdio_stub.h - // Microui Integration (Phase 3.5b) - // hal_mod.addIncludePath(b.path("libs/microui")); - // hal_mod.addCSourceFile(.{ - // .file = b.path("libs/microui/microui.c"), - // .flags = &.{"-std=c99"}, - // }); - // ========================================================= // Boot: Entry Point (Assembly + Zig) // ========================================================= @@ -57,38 +128,230 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - boot_mod.red_zone = false; - boot_mod.stack_check = false; - boot_mod.code_model = .medany; + applyKernelSettings(boot_mod, config.code_model); const boot = b.addObject(.{ .name = "boot", .root_module = boot_mod, }); + // ========================================================= + // LwIP: Lightweight TCP/IP Stack + // ========================================================= + // Compiled as C objects with freestanding flags. + // ubsan disabled per-file to avoid runtime dependency. + + const lwip_base = "libs/membrane/external/lwip/src/"; + const lwip_srcs = [_][]const u8{ + // Core + lwip_base ++ "core/init.c", + lwip_base ++ "core/def.c", + lwip_base ++ "core/dns.c", + lwip_base ++ "core/inet_chksum.c", + lwip_base ++ "core/ip.c", + lwip_base ++ "core/mem.c", + lwip_base ++ "core/memp.c", + lwip_base ++ "core/netif.c", + lwip_base ++ "core/pbuf.c", + lwip_base ++ "core/raw.c", + lwip_base ++ "core/sys.c", + lwip_base ++ "core/tcp.c", + lwip_base ++ "core/tcp_in.c", + lwip_base ++ "core/tcp_out.c", + lwip_base ++ "core/timeouts.c", + lwip_base ++ "core/udp.c", + // IPv4 + lwip_base ++ "core/ipv4/autoip.c", + lwip_base ++ "core/ipv4/dhcp.c", + lwip_base ++ "core/ipv4/etharp.c", + lwip_base ++ "core/ipv4/icmp.c", + lwip_base ++ "core/ipv4/ip4.c", + lwip_base ++ "core/ipv4/ip4_addr.c", + lwip_base ++ "core/ipv4/ip4_frag.c", + // Netif + lwip_base ++ "netif/ethernet.c", + // SysArch (Rumpk integration) + "libs/membrane/sys_arch.c", + }; + + const lwip_flags: []const []const u8 = switch (arch) { + .riscv64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DNO_SYS=1", "-mcmodel=medany", "-Icore", + "-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include", + }, + .aarch64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DNO_SYS=1", "-Icore", "-Ilibs/membrane", + "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include", + }, + .x86_64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DNO_SYS=1", "-mcmodel=kernel", "-Icore", + "-Ilibs/membrane", "-Ilibs/membrane/include", "-Ilibs/membrane/external/lwip/src/include", + }, + }; + + const liblwip = b.addLibrary(.{ + .name = "lwip", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + .linkage = .static, + }); + liblwip.root_module.sanitize_thread = false; + liblwip.root_module.red_zone = false; + liblwip.root_module.single_threaded = true; + + for (lwip_srcs) |src| { + liblwip.addCSourceFile(.{ + .file = b.path(src), + .flags = lwip_flags, + }); + } + + b.installArtifact(liblwip); + + // ========================================================= + // LittleFS: Sovereign Filesystem (Persistent Storage) + // ========================================================= + // Compiled as C object with freestanding flags. + // Uses LFS_CONFIG=lfs_rumpk.h to replace lfs_util.h entirely, + // avoiding stdio/assert deps. malloc/free provided by clib.c. + + const lfs_flags: []const []const u8 = switch (arch) { + .riscv64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=medany", + }, + .aarch64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", + }, + .x86_64 => &.{ + "-Os", "-fno-sanitize=all", "-Wno-everything", + "-DLFS_CONFIG=lfs_rumpk.h", "-Ivendor/littlefs", "-mcmodel=kernel", + }, + }; + + const lfs_obj = b.addObject(.{ + .name = "littlefs", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + lfs_obj.root_module.sanitize_thread = false; + lfs_obj.root_module.red_zone = false; + lfs_obj.root_module.single_threaded = true; + + lfs_obj.addCSourceFile(.{ + .file = b.path("vendor/littlefs/lfs.c"), + .flags = lfs_flags, + }); + + // ========================================================= + // Dependencies (CLib, LibC Shim, LWF Adapter, Switch) + // ========================================================= + + // 1. CLib (Minimal C stdlib) + const clib = b.addObject(.{ + .name = "clib", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + clib.addCSourceFile(.{ + .file = b.path("libs/membrane/clib.c"), + .flags = switch (arch) { + .riscv64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=medany" }, + .aarch64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1" }, + .x86_64 => &.{ "-Os", "-DNO_SYS=1", "-DRUMPK_KERNEL=1", "-mcmodel=kernel" }, + }, + }); + + // 2. LibC Shim (Zig) + const libc_shim_mod = b.createModule(.{ + .root_source_file = b.path("libs/membrane/libc_shim.zig"), + .target = target, + .optimize = optimize, + }); + applyKernelSettings(libc_shim_mod, config.code_model); + + const libc_shim = b.addObject(.{ + .name = "libc_shim", + .root_module = libc_shim_mod, + }); + + // 3. LittleFS HAL (VirtIO-Block ↔ LittleFS glue) + const lfs_hal_mod = b.createModule(.{ + .root_source_file = b.path("hal/littlefs_hal.zig"), + .target = target, + .optimize = optimize, + }); + applyKernelSettings(lfs_hal_mod, config.code_model); + + const lfs_hal = b.addObject(.{ + .name = "lfs_hal", + .root_module = lfs_hal_mod, + }); + + // 4. LWF Adapter (Project LibWeb — Libertaria Wire Frame) + const lwf_adapter_mod = b.createModule(.{ + .root_source_file = b.path("libs/libertaria/lwf_adapter.zig"), + .target = target, + .optimize = optimize, + }); + applyKernelSettings(lwf_adapter_mod, config.code_model); + + const lwf_adapter = b.addObject(.{ + .name = "lwf_adapter", + .root_module = lwf_adapter_mod, + }); + + // 4. Context Switch (Architecture-specific Assembly) + const switch_obj = b.addObject(.{ + .name = "switch", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + switch_obj.addAssemblyFile(b.path(config.switch_asm)); + // ========================================================= // Final Link: rumpk.elf // ========================================================= const kernel_mod = b.createModule(.{ - .root_source_file = b.path("hal/abi.zig"), // Fake root, we add objects later + .root_source_file = b.path("hal/abi.zig"), .target = target, .optimize = optimize, }); - kernel_mod.red_zone = false; - kernel_mod.stack_check = false; - kernel_mod.code_model = .medany; + applyKernelSettings(kernel_mod, config.code_model); const kernel = b.addExecutable(.{ .name = "rumpk.elf", .root_module = kernel_mod, }); - kernel.setLinkerScript(b.path("boot/linker.ld")); + kernel.setLinkerScript(b.path(config.linker_script)); kernel.addObject(boot); - // kernel.linkLibrary(hal); // Redundant, already in kernel_mod + kernel.addObject(clib); + kernel.addObject(libc_shim); + kernel.addObject(lfs_hal); + kernel.addObject(lwf_adapter); + kernel.addObject(switch_obj); - // Add Nim-generated objects + // LwIP linked into kernel — Membrane/NetSwitch drives DHCP/TCP/ICMP. + kernel.linkLibrary(liblwip); + + // LittleFS IS linked into kernel — provides /nexus persistent storage. + kernel.addObject(lfs_obj); + + // Nim-generated objects from build/nimcache/ { var nimcache_dir = std.fs.cwd().openDir("build/nimcache", .{ .iterate = true }) catch |err| { std.debug.print("Warning: Could not open nimcache dir: {}\n", .{err}); @@ -104,31 +367,63 @@ pub fn build(b: *std.Build) void { } } - // Add external pre-built dependencies (Order matters: Libs after users) - kernel.addObjectFile(b.path("build/switch.o")); // cpu_switch_to - kernel.addObjectFile(b.path("build/sys_arch.o")); // sys_now, nexus_lwip_panic - kernel.addObjectFile(b.path("build/libc_shim.o")); - kernel.addObjectFile(b.path("build/clib.o")); - kernel.addObjectFile(b.path("build/liblwip.a")); - kernel.addObjectFile(b.path("build/initrd.o")); + // Embedded InitRD — assemble for the target architecture + const initrd_obj = b.addObject(.{ + .name = "initrd", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + initrd_obj.addAssemblyFile(b.path("build/embed_initrd.S")); + kernel.addObject(initrd_obj); b.installArtifact(kernel); + // Make default install depend on Nim compilation + kernel.step.dependOn(&nim_step.step); + // ========================================================= - // Tests + // Tests (always run on native host) // ========================================================= - const test_mod = b.createModule(.{ + // HAL unit tests + const hal_test_mod = b.createModule(.{ .root_source_file = b.path("hal/abi.zig"), - .target = target, - .optimize = optimize, + .target = b.graph.host, }); const hal_tests = b.addTest(.{ - .root_module = test_mod, + .root_module = hal_test_mod, }); - const run_tests = b.addRunArtifact(hal_tests); - const test_step = b.step("test", "Run Rumpk HAL tests"); - test_step.dependOn(&run_tests.step); + const run_hal_tests = b.addRunArtifact(hal_tests); + const test_step = b.step("test", "Run Rumpk HAL tests (native host)"); + test_step.dependOn(&run_hal_tests.step); + + // LWF Adapter + Membrane tests (Project LibWeb) + const lwf_test_mod = b.createModule(.{ + .root_source_file = b.path("libs/libertaria/lwf_adapter.zig"), + .target = b.graph.host, + }); + + const lwf_tests = b.addTest(.{ + .root_module = lwf_test_mod, + }); + + const run_lwf_tests = b.addRunArtifact(lwf_tests); + const lwf_test_step = b.step("test-lwf", "Run LWF Adapter tests (Project LibWeb)"); + lwf_test_step.dependOn(&run_lwf_tests.step); + + const lwf_membrane_mod = b.createModule(.{ + .root_source_file = b.path("libs/libertaria/lwf_membrane.zig"), + .target = b.graph.host, + }); + + const lwf_membrane_tests = b.addTest(.{ + .root_module = lwf_membrane_mod, + }); + + const run_lwf_membrane_tests = b.addRunArtifact(lwf_membrane_tests); + lwf_test_step.dependOn(&run_lwf_membrane_tests.step); } diff --git a/build_full.sh b/build_full.sh new file mode 100755 index 0000000..fbbca63 --- /dev/null +++ b/build_full.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env zsh +set -e + +ARCH=${1:-riscv64} + +# Architecture-specific settings +if [ "$ARCH" = "aarch64" ]; then + NIM_CPU="arm64" + ZIG_TARGET="aarch64-freestanding-none" + ZIG_CPU="baseline" + LINKER_SCRIPT="apps/linker_user_aarch64.ld" + BUILD_FLAG="-Darch=aarch64" + echo "=== Building NipBox Userland (aarch64) ===" +else + NIM_CPU="riscv64" + ZIG_TARGET="riscv64-freestanding-none" + ZIG_CPU="sifive_u54" + LINKER_SCRIPT="apps/linker_user.ld" + BUILD_FLAG="" + echo "=== Building NipBox Userland (riscv64) ===" +fi + +# Compile Nim sources to C +nim c --cpu:${NIM_CPU} --os:any --compileOnly --mm:arc --opt:size \ + --stackTrace:off --lineDir:off --nomain --nimcache:build/init_nimcache \ + -d:noSignalHandler -d:RUMPK_USER -d:nimAllocPagesViaMalloc -d:NIPBOX_LITE \ + npl/nipbox/nipbox.nim + +# Compile Nim-generated C (check if files exist first) +# Skip net_glue (needs LwIP headers not available in userland build) +EXTRA_CC_FLAGS="" +if [ "$ARCH" = "riscv64" ]; then + EXTRA_CC_FLAGS="-mcmodel=medany" +fi + +if ls build/init_nimcache/*.c 1> /dev/null 2>&1; then + for f in build/init_nimcache/*.c; do + case "$f" in + *net_glue*) echo " [skip] $f (LwIP dependency)"; continue ;; + esac + zig cc -target ${ZIG_TARGET} -mcpu=${ZIG_CPU} ${EXTRA_CC_FLAGS} \ + -fno-sanitize=all -fno-vectorize \ + -I/usr/lib/nim/lib -Icore -Ilibs/membrane -Ilibs/membrane/include \ + -include string.h \ + -Os -c "$f" -o "${f%.c}.o" + done +fi + +# Compile clib +zig cc -target ${ZIG_TARGET} -mcpu=${ZIG_CPU} ${EXTRA_CC_FLAGS} \ + -fno-sanitize=all \ + -DNO_SYS=1 -DOMIT_EXIT -DRUMPK_USER -Ilibs/membrane/include -c libs/membrane/clib.c -o build/clib_user.o + +# Create startup assembly +if [ "$ARCH" = "aarch64" ]; then + cat > build/head_user.S << 'EOF' +.section .text._start +.global _start +_start: + bl NimMain +1: wfi + b 1b +EOF +else + cat > build/head_user.S << 'EOF' +.section .text._start +.global _start +_start: + .option push + .option norelax + 1:auipc gp, %pcrel_hi(__global_pointer$) + addi gp, gp, %pcrel_lo(1b) + .option pop + call NimMain + 1: wfi + j 1b +EOF +fi + +zig cc -target ${ZIG_TARGET} -mcpu=${ZIG_CPU} ${EXTRA_CC_FLAGS} \ + -fno-sanitize=all \ + -c build/head_user.S -o build/head_user.o + +# Link init +zig cc -target ${ZIG_TARGET} -mcpu=${ZIG_CPU} ${EXTRA_CC_FLAGS} -nostdlib \ + -fno-sanitize=all \ + -T ${LINKER_SCRIPT} -Wl,--gc-sections \ + build/head_user.o build/init_nimcache/*.o build/clib_user.o \ + -o build/init + +echo "✓ NipBox binary built (${ARCH})" +file build/init + +# Create initrd +mkdir -p build/sysro/bin +cp build/init build/sysro/init +if [ "$ARCH" = "riscv64" ] && [ -f vendor/mksh/mksh.elf ]; then + cp vendor/mksh/mksh.elf build/sysro/bin/mksh +fi +cd build/sysro +tar --format=ustar -cf ../initrd.tar * +cd ../.. + +# Embed initrd +cat > build/embed_initrd.S << EOF +.section .rodata +.global _initrd_start +.global _initrd_end +.align 4 +_initrd_start: + .incbin "$(pwd)/build/initrd.tar" +_initrd_end: +EOF + +zig cc -target ${ZIG_TARGET} -mcpu=${ZIG_CPU} ${EXTRA_CC_FLAGS} \ + -c build/embed_initrd.S -o build/initrd.o + +cp build/initrd.tar hal/initrd.tar + +# Build kernel +rm -f zig-out/lib/librumpk_hal.a +zig build ${BUILD_FLAG} + +echo "=== BUILD COMPLETE (${ARCH}) ===" +ls -lh build/init zig-out/bin/rumpk.elf diff --git a/build_lwip.sh b/build_lwip.sh new file mode 100755 index 0000000..e6597f3 --- /dev/null +++ b/build_lwip.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env zsh +# Build LwIP as a pure C library without Zig runtime dependencies + +set -e + +mkdir -p build/lwip_objs +rm -f build/lwip_objs/*.o 2>/dev/null || true + +echo "Building LwIP..." + +# Compile each source file +compile() { + local src=$1 + local obj="build/lwip_objs/$(basename ${src%.c}.o)" + echo " $src" + zig cc -target riscv64-freestanding-none -mcpu=sifive_u54 -mcmodel=medany \ + -Os -fno-sanitize=all \ + -DNO_SYS=1 -Icore -Ilibs/membrane -Ilibs/membrane/include \ + -Ilibs/membrane/external/lwip/src/include \ + -c "$src" -o "$obj" +} + +# Core sources +compile "libs/membrane/external/lwip/src/core/init.c" +compile "libs/membrane/external/lwip/src/core/def.c" +compile "libs/membrane/external/lwip/src/core/dns.c" +compile "libs/membrane/external/lwip/src/core/inet_chksum.c" +compile "libs/membrane/external/lwip/src/core/ip.c" +compile "libs/membrane/external/lwip/src/core/mem.c" +compile "libs/membrane/external/lwip/src/core/memp.c" +compile "libs/membrane/external/lwip/src/core/netif.c" +compile "libs/membrane/external/lwip/src/core/pbuf.c" +compile "libs/membrane/external/lwip/src/core/raw.c" +compile "libs/membrane/external/lwip/src/core/sys.c" +compile "libs/membrane/external/lwip/src/core/tcp.c" +compile "libs/membrane/external/lwip/src/core/tcp_in.c" +compile "libs/membrane/external/lwip/src/core/tcp_out.c" +compile "libs/membrane/external/lwip/src/core/timeouts.c" +compile "libs/membrane/external/lwip/src/core/udp.c" + +# IPv4 sources +compile "libs/membrane/external/lwip/src/core/ipv4/autoip.c" +compile "libs/membrane/external/lwip/src/core/ipv4/dhcp.c" +compile "libs/membrane/external/lwip/src/core/ipv4/etharp.c" +compile "libs/membrane/external/lwip/src/core/ipv4/icmp.c" +compile "libs/membrane/external/lwip/src/core/ipv4/ip4.c" +compile "libs/membrane/external/lwip/src/core/ipv4/ip4_addr.c" +compile "libs/membrane/external/lwip/src/core/ipv4/ip4_frag.c" + +# Netif sources +compile "libs/membrane/external/lwip/src/netif/ethernet.c" + +# SysArch +compile "libs/membrane/sys_arch.c" + +echo "Creating liblwip.a..." +mkdir -p zig-out/lib +rm -f zig-out/lib/liblwip.a +(cd build/lwip_objs && ar rcs ../../zig-out/lib/liblwip.a *.o) +echo "Done! liblwip.a created at zig-out/lib/liblwip.a" +ls -lh zig-out/lib/liblwip.a diff --git a/build_nim.sh b/build_nim.sh new file mode 100755 index 0000000..d6afd90 --- /dev/null +++ b/build_nim.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# ============================================================================ +# Rumpk Nim Kernel Build — nim → C → .o (cross-compiled for target arch) +# ============================================================================ +# Usage: +# ./build_nim.sh # Default: riscv64 +# ./build_nim.sh riscv64 # RISC-V 64-bit +# ./build_nim.sh aarch64 # ARM64 +# ./build_nim.sh x86_64 # AMD64 +# +# This script: +# 1. Invokes nim c --compileOnly to generate C from Nim +# 2. Cross-compiles each .c to .o using zig cc +# 3. Outputs to build/nimcache/*.o (consumed by build.zig) +# ============================================================================ +set -euo pipefail +cd "$(dirname "$0")" + +ARCH="${1:-riscv64}" + +# ---- Validate architecture ---- +case "$ARCH" in + riscv64) + ZIG_TARGET="riscv64-freestanding-none" + ZIG_CPU="-mcpu=sifive_u54" + ZIG_MODEL="-mcmodel=medany" + NIM_CPU="riscv64" + ;; + aarch64) + ZIG_TARGET="aarch64-freestanding-none" + ZIG_CPU="" + ZIG_MODEL="-fno-vectorize" + NIM_CPU="arm64" + ;; + x86_64) + ZIG_TARGET="x86_64-freestanding-none" + ZIG_CPU="" + ZIG_MODEL="-mcmodel=kernel" + NIM_CPU="amd64" + ;; + *) + echo "ERROR: Unknown architecture '$ARCH'" + echo "Supported: riscv64, aarch64, x86_64" + exit 1 + ;; +esac + +NIMCACHE="build/nimcache" + +echo "=== Rumpk Nim Build: $ARCH ===" +echo " Target: $ZIG_TARGET" +echo " Output: $NIMCACHE/" + +# ---- Step 1: Nim → C ---- +echo "" +echo "[1/2] nim c --compileOnly core/kernel.nim" + +nim c \ + --cpu:"$NIM_CPU" \ + --os:any \ + --compileOnly \ + --mm:arc \ + --opt:size \ + --stackTrace:off \ + --lineDir:off \ + --nomain \ + --nimcache:"$NIMCACHE" \ + -d:noSignalHandler \ + -d:RUMPK_KERNEL \ + -d:nimAllocPagesViaMalloc \ + core/kernel.nim + +C_COUNT=$(ls -1 "$NIMCACHE"/*.c 2>/dev/null | wc -l) +echo " Generated $C_COUNT C files" + +# ---- Step 2: C → .o (zig cc cross-compile) ---- +echo "" +echo "[2/2] zig cc → $ZIG_TARGET" + +COMPILED=0 +FAILED=0 + +for cfile in "$NIMCACHE"/*.c; do + [ -f "$cfile" ] || continue + ofile="${cfile%.c}.o" + + # Skip if .o is newer than .c (incremental) + if [ -f "$ofile" ] && [ "$ofile" -nt "$cfile" ]; then + continue + fi + + if zig cc \ + -target "$ZIG_TARGET" \ + $ZIG_CPU \ + $ZIG_MODEL \ + -fno-sanitize=all \ + -fvisibility=default \ + -I/usr/lib/nim/lib \ + -Icore \ + -Icore/include \ + -Ilibs/membrane \ + -Ilibs/membrane/include \ + -Ilibs/membrane/external/lwip/src/include \ + -Os \ + -c "$cfile" \ + -o "$ofile" 2>/dev/null; then + COMPILED=$((COMPILED + 1)) + else + echo " FAIL: $(basename "$cfile")" + FAILED=$((FAILED + 1)) + fi +done + +O_COUNT=$(ls -1 "$NIMCACHE"/*.o 2>/dev/null | wc -l) + +echo "" +echo "=== Result ===" +echo " Arch: $ARCH" +echo " C files: $C_COUNT" +echo " Compiled: $COMPILED (incremental skip: $((O_COUNT - COMPILED)))" +echo " Objects: $O_COUNT" +if [ "$FAILED" -gt 0 ]; then + echo " FAILED: $FAILED" + exit 1 +fi +echo " Status: OK" diff --git a/core/include/math.h b/core/include/math.h index 8e69fc9..e9f0dde 100644 --- a/core/include/math.h +++ b/core/include/math.h @@ -3,5 +3,10 @@ double pow(double x, double y); double log10(double x); +double fabs(double x); +double floor(double x); +double ceil(double x); +double fmod(double x, double y); +double round(double x); #endif diff --git a/core/include/stdio.h b/core/include/stdio.h index eab13d7..0c205e5 100644 --- a/core/include/stdio.h +++ b/core/include/stdio.h @@ -4,10 +4,22 @@ #include #include +typedef struct _FILE FILE; + +extern FILE *stdin; +extern FILE *stdout; +extern FILE *stderr; + int printf(const char *format, ...); +int fprintf(FILE *stream, const char *format, ...); int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...); +int vprintf(const char *format, va_list ap); int vsnprintf(char *str, size_t size, const char *format, va_list ap); +size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +int fflush(FILE *stream); +int fputc(int c, FILE *stream); +int fputs(const char *s, FILE *stream); int rename(const char *oldpath, const char *newpath); int remove(const char *pathname); diff --git a/run.sh b/run.sh index 964fdd6..f98ccbc 100755 --- a/run.sh +++ b/run.sh @@ -1,22 +1,122 @@ -#!/bin/bash -# Rumpk QEMU Boot Script +#!/usr/bin/env bash +# ============================================================================ +# Rumpk QEMU Runner — Boot, Test, or Interactive Shell +# ============================================================================ +# Usage: +# ./run.sh # Interactive RISC-V session +# ./run.sh test # Automated boot test (10s timeout, check output) +# ./run.sh [riscv64|aarch64] # Architecture selection (future) +# +# Exit codes: +# 0 = Boot successful (test mode: all checks passed) +# 1 = Boot failed or timeout +# ============================================================================ +set -euo pipefail +cd "$(dirname "$0")" -RUMPK_DIR="$(cd "$(dirname "$0")" && pwd)" -KERNEL="$RUMPK_DIR/zig-out/bin/rumpk.elf" +MODE="${1:-interactive}" +ARCH="${2:-riscv64}" +TIMEOUT=25 + +KERNEL="zig-out/bin/rumpk.elf" +DISK="build/disk.img" + +# Fallback to build/ location if zig-out doesn't have it +if [ ! -f "$KERNEL" ]; then + KERNEL="build/rumpk-riscv64.elf" +fi if [ ! -f "$KERNEL" ]; then - echo "ERROR: Kernel not found at $KERNEL" - echo "Run ./build.sh first" + echo "ERROR: No kernel binary found. Run: ./build_nim.sh && zig build -Dtarget=riscv64-freestanding-none -Dcpu=sifive_u54" exit 1 fi -echo "🚀 Booting Rumpk..." -echo " Kernel: $KERNEL" -echo "" +case "$ARCH" in + riscv64) + QEMU_CMD=( + qemu-system-riscv64 + -M virt -m 512M -nographic + -kernel "$KERNEL" + ) + # Add disk if it exists + if [ -f "$DISK" ]; then + QEMU_CMD+=( + -drive "if=none,format=raw,file=$DISK,id=blk0" + -device virtio-blk-pci,drive=blk0 + ) + fi + QEMU_CMD+=( + -device virtio-net-pci,netdev=net0 + -netdev user,id=net0 + ) + ;; + *) + echo "ERROR: Architecture '$ARCH' not yet supported" + echo "Supported: riscv64" + exit 1 + ;; +esac -qemu-system-riscv64 \ - -M virt \ - -cpu max \ - -m 512M \ - -nographic \ - -kernel "$KERNEL" +if [ "$MODE" = "test" ]; then + echo "=== Rumpk Boot Test ($ARCH, ${TIMEOUT}s timeout) ===" + + LOGFILE=$(mktemp /tmp/rumpk-boot-XXXXXX.log) + trap "rm -f $LOGFILE" EXIT + + timeout "$TIMEOUT" "${QEMU_CMD[@]}" > "$LOGFILE" 2>&1 || true + + # Boot verification checks + PASS=0 + FAIL=0 + TOTAL=0 + + check() { + local name="$1" + local pattern="$2" + TOTAL=$((TOTAL + 1)) + if grep -q "$pattern" "$LOGFILE"; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + FAIL=$((FAIL + 1)) + fi + } + + check "OpenSBI handoff" "Boot HART ID" + check "UART proof-of-life" "UART.*Loopback Test: PASS" + check "Zig HAL entry" "zig_entry reached" + check "Nim L1 handoff" "Handing off to Nim L1" + check "ION pool ready" "ION.*Pool Ready" + check "VirtIO-Net init" "VirtIO.*Initialization Complete" + check "CSpace init" "Capability system initialized" + check "Truth Ledger init" "System Truth Ledger initialized" + check "Fiber dispatcher" "Multi-Fiber Dispatcher starting" + check "NetSwitch online" "Traffic Engine Online" + check "LFS mounted" "Sovereign filesystem mounted" + check "Init loaded" "NIPBOX.*Entry point reached" + check "Shell spawned" "SOVEREIGN SUPERVISOR" + check "DHCP IP acquired" "IP STATUS CHANGE" + + echo "" + echo "=== Result: $PASS/$TOTAL passed, $FAIL failed ===" + + if [ "$FAIL" -gt 0 ]; then + echo "" + echo "Boot log saved: $LOGFILE" + trap - EXIT # Don't delete on failure + exit 1 + fi + + exit 0 + +elif [ "$MODE" = "interactive" ]; then + echo "=== Rumpk Interactive Session ($ARCH) ===" + echo " Kernel: $KERNEL" + echo " Exit: Ctrl-A X" + echo "" + exec "${QEMU_CMD[@]}" +else + echo "Usage: ./run.sh [interactive|test] [riscv64]" + exit 1 +fi diff --git a/run_aarch64.sh b/run_aarch64.sh new file mode 100755 index 0000000..ab670b7 --- /dev/null +++ b/run_aarch64.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# ============================================================================ +# Rumpk ARM64 QEMU Runner +# ============================================================================ +# Usage: +# ./run_aarch64.sh # Interactive boot +# ./run_aarch64.sh test # Automated boot test +# ============================================================================ +set -euo pipefail +cd "$(dirname "$0")" + +KERNEL="zig-out/bin/rumpk.elf" +DISK="build/disk_aarch64.img" + +if [ ! -f "$KERNEL" ]; then + echo "ERROR: $KERNEL not found. Run: zig build -Darch=aarch64" + exit 1 +fi + +# Create disk image if missing (16MB for LittleFS) +if [ ! -f "$DISK" ]; then + mkdir -p build + dd if=/dev/zero of="$DISK" bs=1M count=16 2>/dev/null + echo "Created $DISK (16MB)" +fi + +MODE="${1:-interactive}" + +# VirtIO MMIO devices for aarch64 virt +VIRTIO_NET="-device virtio-net-device,netdev=net0 -netdev user,id=net0" +VIRTIO_BLK="-device virtio-blk-device,drive=hd0 -drive if=none,id=hd0,format=raw,file=$DISK" + +if [ "$MODE" = "test" ]; then + echo "=== Rumpk ARM64 Boot Test ===" + TIMEOUT=20 + + LOGFILE=$(mktemp /tmp/rumpk-aarch64-XXXXXX.log) + timeout "$TIMEOUT" qemu-system-aarch64 \ + -M virt -cpu cortex-a72 -m 512M \ + -nographic -serial mon:stdio \ + -kernel "$KERNEL" \ + $VIRTIO_NET $VIRTIO_BLK \ + -no-reboot >"$LOGFILE" 2>&1 || true + + cat "$LOGFILE" + echo "" + echo "=== Boot Checks ===" + + PASS=0 + FAIL=0 + + check() { + local label="$1" + local pattern="$2" + if grep -qa "$pattern" "$LOGFILE"; then + echo " [PASS] $label" + PASS=$((PASS + 1)) + else + echo " [FAIL] $label" + FAIL=$((FAIL + 1)) + fi + } + + check "UART loopback" "Loopback Test: PASS" + check "aarch64_init reached" "aarch64_init reached" + check "GICv2 initialized" "GICv2 initialized" + check "Timer initialized" "Timer initialized" + check "Vectors installed" "vectors installed" + check "Nim handoff" "Handing off to Nim" + check "VirtIO-Net found" "VirtIO-Net" + check "VirtIO-Blk found" "VirtIO-Block\|VirtIO-Blk\|Storage initialized" + check "VirtIO init complete" "Initialization Complete" + + TOTAL=$((PASS + FAIL)) + echo "" + echo "=== Result: $PASS/$TOTAL ===" + if [ "$FAIL" -gt 0 ]; then + exit 1 + fi +else + exec qemu-system-aarch64 \ + -M virt -cpu cortex-a72 -m 512M \ + -nographic -serial mon:stdio \ + -kernel "$KERNEL" \ + $VIRTIO_NET $VIRTIO_BLK +fi diff --git a/vendor/mksh/build_nexus.sh b/vendor/mksh/build_nexus.sh index 1b2e1d1..891a9f4 100755 --- a/vendor/mksh/build_nexus.sh +++ b/vendor/mksh/build_nexus.sh @@ -55,7 +55,6 @@ BUILD_DIR="../../build" zig cc -target $TARGET -nostdlib -static -T $LINKER_SCRIPT \ $BUILD_DIR/subject_entry.o \ - $BUILD_DIR/stubs_user.o \ $BUILD_DIR/libc_shim.o \ stubs_mksh.o \ $OBJS \ diff --git a/vendor/mksh/stubs_mksh.c b/vendor/mksh/stubs_mksh.c index c251c0d..d0abf08 100644 --- a/vendor/mksh/stubs_mksh.c +++ b/vendor/mksh/stubs_mksh.c @@ -13,17 +13,78 @@ #include #include #include - -// Globals -char **environ = NULL; - -extern void console_write(const void* p, size_t len); +#include extern long syscall(long nr, long a0, long a1, long a2); long k_handle_syscall(long nr, long a0, long a1, long a2) { return syscall(nr, a0, a1, a2); } +// Globals +char **environ = NULL; + +// Safe Userland Allocator (Bump Pointer with Headers) +#define HEAP_SIZE (32 * 1024 * 1024) +static char heap_memory[HEAP_SIZE]; +static size_t heap_ptr = 0; + +typedef struct { + size_t size; + size_t magic; +} BlockHeader; +#define ALLOC_MAGIC 0xCAFEBABE + +void *malloc(size_t size) { + if (size == 0) return NULL; + + // Align total size (header + data) to 16 bytes + size_t required = size + sizeof(BlockHeader); + size_t aligned_total = (required + 15) & ~15; + + if (heap_ptr + aligned_total > HEAP_SIZE) return NULL; + + BlockHeader *hdr = (BlockHeader *)&heap_memory[heap_ptr]; + hdr->size = size; + hdr->magic = ALLOC_MAGIC; + + void *ptr = (void *)((char *)hdr + sizeof(BlockHeader)); + heap_ptr += aligned_total; + return ptr; +} + +void free(void *ptr) { + // No-op bump allocator +} + +void *realloc(void *ptr, size_t size) { + if (!ptr) return malloc(size); + if (size == 0) return NULL; // Standard says free.. or return NULL? mksh expects NULL or ptr. + + // Get header + BlockHeader *hdr = (BlockHeader *)((char *)ptr - sizeof(BlockHeader)); + if (hdr->magic != ALLOC_MAGIC) { + // Corrupted ptr? return NULL or fail. + return NULL; + } + + // Optimization: If it's the LAST block, simple extend? (Not implemented for simplicity) + + void *new_ptr = malloc(size); + if (!new_ptr) return NULL; + + size_t copy_size = (hdr->size < size) ? hdr->size : size; + memcpy(new_ptr, ptr, copy_size); + + return new_ptr; +} + +void *calloc(size_t nmemb, size_t size) { + size_t total = nmemb * size; + void *ptr = malloc(total); + if (ptr) memset(ptr, 0, total); + return ptr; +} + // Stubs int fstat(int fd, struct stat *buf) { return 0; } int lstat(const char *path, struct stat *buf) { return 0; } @@ -32,7 +93,14 @@ int stat(const char *path, struct stat *buf) { return 0; } int sigemptyset(sigset_t *set) { return 0; } int sigaddset(sigset_t *set, int signum) { return 0; } int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) { return 0; } -int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) { return 0; } +int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) { + if (oldact) { + // Initialize to safe defaults - no handler installed + memset(oldact, 0, sizeof(struct sigaction)); + oldact->sa_handler = SIG_DFL; // Default handler + } + return 0; +} int sigsuspend(const sigset_t *mask) { return -1; } int kill(pid_t pid, int sig) { return 0; } unsigned int alarm(unsigned int seconds) { return 0; } @@ -45,7 +113,21 @@ off_t lseek(int fd, off_t offset, int whence) { return 0; } // int close(int fd) { return 0; } // In libc.nim int pipe(int pipefd[2]) { return -1; } int dup2(int oldfd, int newfd) { return newfd; } -int fcntl(int fd, int cmd, ...) { return 0; } +extern void console_write(const void* p, unsigned long len); +static void ksh_debug(const char* s) { + console_write(s, (unsigned long)strlen(s)); +} + +int fcntl(int fd, int cmd, ...) { + ksh_debug("[mksh] fcntl called\n"); + va_list args; + va_start(args, cmd); + long arg = va_arg(args, long); + va_end(args); + int res = (int)syscall(0x206, (long)fd, (long)cmd, arg); + ksh_debug("[mksh] fcntl returning\n"); + return res; +} int ioctl(int fd, unsigned long request, ...) { return 0; } // int execve(const char *pathname, char *const argv[], char *const envp[]) { return -1; } // In clib.c @@ -65,7 +147,26 @@ int seteuid(uid_t uid) { return 0; } int setegid(gid_t gid) { return 0; } int setpgid(pid_t pid, pid_t pgid) { return 0; } -int tcgetattr(int fd, struct termios *termios_p) { return 0; } +int tcgetattr(int fd, struct termios *termios_p) { + if (termios_p) { + // Initialize with safe defaults (using numeric values to avoid missing constants) + memset(termios_p, 0, sizeof(struct termios)); + // Set basic flags for canonical mode + termios_p->c_iflag = 0x0100; // ICRNL + termios_p->c_oflag = 0x0001 | 0x0004; // OPOST | ONLCR + termios_p->c_cflag = 0x0030 | 0x0080; // CS8 | CREAD + termios_p->c_lflag = ISIG | ICANON | ECHO; // These should be defined + // Set control characters + termios_p->c_cc[VINTR] = 3; // Ctrl-C + termios_p->c_cc[VQUIT] = 28; // Ctrl-backslash + termios_p->c_cc[VERASE] = 127; // DEL + termios_p->c_cc[VKILL] = 21; // Ctrl-U + termios_p->c_cc[VEOF] = 4; // Ctrl-D + termios_p->c_cc[VMIN] = 1; + termios_p->c_cc[VTIME] = 0; + } + return 0; +} int tcsetattr(int fd, int optional_actions, const struct termios *termios_p) { return 0; } pid_t tcgetpgrp(int fd) { return 1; } int tcsetpgrp(int fd, pid_t pgrp) { return 0; } @@ -128,7 +229,56 @@ mode_t umask(mode_t mask) { return 0; } int c_ulimit(const char **wp) { return 0; } -void longjmp(jmp_buf env, int val) { while(1); } -int setjmp(jmp_buf env) { return 0; } +int setjmp(jmp_buf env) { + __asm__ volatile ( + "sd ra, 0(%0)\n" + "sd sp, 8(%0)\n" + "sd s0, 16(%0)\n" + "sd s1, 24(%0)\n" + "sd s2, 32(%0)\n" + "sd s3, 40(%0)\n" + "sd s4, 48(%0)\n" + "sd s5, 56(%0)\n" + "sd s6, 64(%0)\n" + "sd s7, 72(%0)\n" + "sd s8, 80(%0)\n" + "sd s9, 88(%0)\n" + "sd s10, 96(%0)\n" + "sd s11, 104(%0)\n" + "li a0, 0\n" + : : "r"(env) : "memory", "a0" + ); + return 0; +} -void exit(int status) { while(1); } +void longjmp(jmp_buf env, int val) { + __asm__ volatile ( + "ld ra, 0(%0)\n" + "ld sp, 8(%0)\n" + "ld s0, 16(%0)\n" + "ld s1, 24(%0)\n" + "ld s2, 32(%0)\n" + "ld s3, 40(%0)\n" + "ld s4, 48(%0)\n" + "ld s5, 56(%0)\n" + "ld s6, 64(%0)\n" + "ld s7, 72(%0)\n" + "ld s8, 80(%0)\n" + "ld s9, 88(%0)\n" + "ld s10, 96(%0)\n" + "ld s11, 104(%0)\n" + "mv a0, %1\n" + "seqz t0, a0\n" + "add a0, a0, t0\n" + : : "r"(env), "r"(val) : "memory", "a0", "t0" + ); + // Note: longjmp should not return. In this freestanding env, + // the asm above ends with registers restored and we jump back. + // However, to be extra safe we could add a ret at the end of asm. + __asm__ volatile("ret"); +} + +void exit(int status) { + syscall(0x01, (long)status, 0, 0); + while(1); +} diff --git a/zig-cc-wrapper.sh b/zig-cc-wrapper.sh new file mode 100755 index 0000000..a3060e6 --- /dev/null +++ b/zig-cc-wrapper.sh @@ -0,0 +1,2 @@ +#!/bin/sh +zig cc -target riscv64-freestanding-none -mcpu=sifive_u54 "$@"