feat(build): dual-arch build system — build_nim.sh, build_full.sh, run_aarch64.sh

This commit is contained in:
Markus Maiwald 2026-02-15 19:59:26 +01:00
parent 49c58fbd94
commit a38bc523a8
11 changed files with 1032 additions and 71 deletions

383
build.zig
View File

@ -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);
}

125
build_full.sh Executable file
View File

@ -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

61
build_lwip.sh Executable file
View File

@ -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

126
build_nim.sh Executable file
View File

@ -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"

View File

@ -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

View File

@ -4,10 +4,22 @@
#include <stddef.h>
#include <stdarg.h>
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);

130
run.sh
View File

@ -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 ""
qemu-system-riscv64 \
-M virt \
-cpu max \
-m 512M \
-nographic \
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
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

86
run_aarch64.sh Executable file
View File

@ -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

View File

@ -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 \

View File

@ -13,17 +13,78 @@
#include <string.h>
#include <setjmp.h>
#include <termios.h>
// Globals
char **environ = NULL;
extern void console_write(const void* p, size_t len);
#include <stdarg.h>
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);
}

2
zig-cc-wrapper.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
zig cc -target riscv64-freestanding-none -mcpu=sifive_u54 "$@"