Building an OS in a New Language
When we built Lateralus, the question wasn't if we'd write an OS — it was when. A programming language proves itself at the edges: can it handle hardware interrupts? Can it manage memory without a runtime? Can it boot a machine? LateralusOS v1.0 answers yes to all three.
Why Write an OS?
Every systems language claims it can do low-level work. But claims aren't proof. Writing an actual bootable kernel forced us to solve real problems:
- No heap at boot. Memory allocation doesn't exist until you build it.
- No standard library. You write
printbefore you can use it. - Hardware is unforgiving. One wrong memory write and the CPU triple-faults.
- Interrupt handling. The CPU fires hardware interrupts at any time — your code must handle them.
LateralusOS forced the language to grow. Features that seemed nice-to-have became essential.
Architecture Overview
LateralusOS v1.0 Components
- Bootloader — UEFI boot, framebuffer init, memory map
- Kernel — GDT, IDT, interrupt handlers, PIC, PIT timer
- Memory — Physical frame allocator, virtual memory (paging), heap allocator
- Drivers — VGA text mode, keyboard (PS/2), serial port, PCI enumeration
- Filesystem — Basic VFS layer, RAM disk, read/write operations
- Shell — Interactive command-line with 12 built-in commands
- Networking — Ethernet frames, ARP, basic IP stack
The Kernel Entry Point
Here's how LateralusOS boots — the kernel entry point, written entirely in Lateralus:
// kernel/main.ltl — LateralusOS entry point
use kernel::{gdt, idt, pic, memory, drivers, shell}
fn kernel_main(boot_info: BootInfo) {
boot_info
|> gdt::init()
|> idt::init()
|> pic::init()
let allocator = boot_info.memory_map
|> memory::init_frame_allocator()
let heap = allocator
|> memory::init_heap(0x4444_0000, 1024 * 1024)
drivers::init_all()
shell::run()
}
The pipeline operator shines here. Kernel initialization is a sequence of dependent steps — exactly what pipelines model.
Memory Management
The physical frame allocator uses Lateralus's pattern matching to handle memory regions:
fn allocate_frame(allocator: &mut FrameAllocator) -> Option<Frame> {
allocator.memory_map
|> filter(|region| match region.kind {
Usable => true,
Bootloader => false,
Reserved => false,
_ => false,
})
|> flat_map(|r| r.start..r.end |> step_by(4096))
|> find(|addr| !allocator.used.contains(addr))
|> map(|addr| {
allocator.used.insert(addr)
Frame { address: addr }
})
}
Interrupt Handling
Hardware interrupts need pattern matching — the CPU gives you an interrupt number, and you need to dispatch it fast:
fn interrupt_handler(frame: InterruptFrame, irq: u8) {
match irq {
0 => timer::tick(),
1 => keyboard::handle_scancode(
port::read_u8(0x60)
),
14 => disk::handle_irq(),
n => println("Unhandled IRQ: {n}"),
}
pic::end_of_interrupt(irq)
}
The Shell
LateralusOS includes an interactive shell with pipeline support at the OS level:
// Built-in commands
fn execute(cmd: Command) {
match cmd {
Command::Help => show_help(),
Command::Clear => vga::clear_screen(),
Command::Ls(path) => fs::list_dir(path),
Command::Cat(path) => fs::read_file(path) |> println(),
Command::Echo(text) => println(text),
Command::Uptime => timer::uptime() |> format_duration() |> println(),
Command::MemInfo => memory::stats() |> display(),
Command::Shutdown => acpi::shutdown(),
Command::Unknown(s) => println("Unknown: {s}"),
}
}
Lessons Learned
Building LateralusOS taught us three things about language design:
- Pattern matching is essential for systems code. Hardware gives you numbers and flags — you need exhaustive matching to handle them safely.
- Pipelines work at every level. From kernel init to user shell commands, left-to-right data flow makes code clearer.
- Zero-cost abstractions matter. The pipeline operator compiles down to direct function calls — no runtime overhead in a kernel.
If your language can boot a computer, it can handle anything.
What FRISC teaches
Every chapter of FRISC maps to a fundamental OS concept. Here's the curriculum in order:
- Chapter 1: First Boot — bare-metal startup, UART output, understanding the boot process from power-on to "Hello, kernel!"
- Chapter 2: Memory — physical page allocation, virtual memory with Sv39, the MMU, and page faults
- Chapter 3: Interrupts — trap vectors, timer interrupts, the PLIC interrupt controller, and preemptive scheduling
- Chapter 4: Processes — context switching, user/kernel mode, system calls, and process lifecycle
- Chapter 5: Filesystems — VirtIO block driver, a simple filesystem implementation, and file I/O system calls
- Chapter 6: Multicore — SMP boot, per-hart scheduling, atomic operations, and spinlocks
- Chapter 7: Networking — VirtIO net driver, a minimal TCP/IP stack, and socket system calls
- Chapter 8: Graphics — VirtIO GPU, framebuffer, window manager, and the Nullkia desktop prototype
Each chapter builds on the last. By chapter 8, you have a functional operating system with networking, a graphical desktop, and multicore support — all written in Lateralus, all running on bare RISC-V hardware.
FRISC by the numbers
- 8,400 lines of Lateralus kernel code
- 200 lines of RISC-V assembly (just the boot stub and trap entry)
- 0 lines of C (everything compiles through the Lateralus C99 backend)
- 45 system calls implemented (enough for a POSIX-like userspace)
- 4 VirtIO drivers — block, network, GPU, input
- Boot time — 0.3 seconds to shell prompt in QEMU
What FRISC teaches
Every chapter of FRISC maps to a fundamental OS concept. Here's the curriculum in order:
- Chapter 1: First Boot — bare-metal startup, UART output, understanding the boot process from power-on to "Hello, kernel!"
- Chapter 2: Memory — physical page allocation, virtual memory with Sv39, the MMU, and page faults
- Chapter 3: Interrupts — trap vectors, timer interrupts, the PLIC interrupt controller, and preemptive scheduling
- Chapter 4: Processes — context switching, user/kernel mode, system calls, and process lifecycle
- Chapter 5: Filesystems — VirtIO block driver, a simple filesystem implementation, and file I/O system calls
- Chapter 6: Multicore — SMP boot, per-hart scheduling, atomic operations, and spinlocks
- Chapter 7: Networking — VirtIO net driver, a minimal TCP/IP stack, and socket system calls
- Chapter 8: Graphics — VirtIO GPU, framebuffer, window manager, and the Nullkia desktop prototype
Each chapter builds on the last. By chapter 8, you have a functional operating system with networking, a graphical desktop, and multicore support — all written in Lateralus, all running on bare RISC-V hardware.
FRISC by the numbers
- 8,400 lines of Lateralus kernel code
- 200 lines of RISC-V assembly (just the boot stub and trap entry)
- 0 lines of C (everything compiles through the Lateralus C99 backend)
- 45 system calls implemented (enough for a POSIX-like userspace)
- 4 VirtIO drivers — block, network, GPU, input
- Boot time — 0.3 seconds to shell prompt in QEMU