The Loup Freestanding Book — contents
  1. Chapter 1: What Is Freestanding Mode?
  2. Chapter 2: Building a Bare-Metal Binary
  3. Chapter 3: Integer Types and Wrapping Arithmetic
  4. Chapter 4: Functions and Control Flow
  5. Chapter 5: Structs and Layout Control
  6. Chapter 6: Enums and Pattern Matching
  7. Chapter 7: Classes and Allocators
  8. Chapter 8: Interfaces and Generics
  9. Chapter 9: Error Handling with Result
  10. Chapter 10: Ownership Verbs
  11. Chapter 11: Borrow and Lifetimes
  12. Chapter 12: Unique Ownership
  13. Chapter 13: Memory Primitives
  14. Chapter 14: Inline Assembly
  15. Chapter 15: MMIO with Volatile
  16. Chapter 16: Interrupts and Naked Functions
  17. Chapter 17: Entry Points and Panic
  18. Chapter 18: Sections and the Linker
  19. Chapter 19: Multicore and Atomics
  20. Chapter 20: The Available Standard Library Subset
  21. Chapter 21: Example — x86_64 Kernel Stub
  22. Chapter 22: Testing and Deploying Firmware
  23. Chapter 23: Memory Safety and Kernel Development Patterns
  24. Chapter 24: synchronized Classes and Lock Ordering

The Loup Freestanding Book

For Loup 1.25. Freestanding support is experimental in this release; features the Language Reference specifies but the toolchain does not implement yet are marked ⚠ Planned.

This book teaches @mode(freestanding): writing kernels, boot loaders, firmware and other bare-metal programs in Loup. A freestanding image runs with no operating system, no libc and no Loup runtime underneath it — when it starts, the only code in memory is what you wrote and what the toolchain linked for you (memcpy and similar helpers). Loup's type system, ownership verbs, unique values and lifetimes all work there, together with the features bare metal needs: inline assembly, volatile MMIO, interrupt handlers, naked functions, layout control and linker sections.

Who this book is for

  • You are writing an operating-system kernel, a boot loader, a hypervisor payload or microcontroller firmware.
  • You know what a linker script, an interrupt vector and a memory-mapped register are.
  • You have written some Loup, or are willing to pick up the syntax from The Loup Scripting Book or The Loup Native Book — the hosted books are the gentler place to learn the language and its ownership system.

The mode directive

Freestanding source files use the .lpfree extension and begin with:

@mode(freestanding)

const UART0_BASE: uint64 = 0x0900_0000u64

The directive must be the file's first token (only comments may precede it). There is no main: the image's entry point is a function marked @entry.

Installing the toolchain

Loup is installed from the official releases at github.com/code-craft-ai-company/loup-releases:

# Linux and macOS
curl -fsSL https://github.com/code-craft-ai-company/loup-releases/releases/latest/download/loup-install.sh | sh
# Windows (PowerShell)
irm https://github.com/code-craft-ai-company/loup-releases/releases/latest/download/loup-install.ps1 | iex

The installer puts the tools in ~/.loup/bin (or %LOCALAPPDATA%\Programs\Loup) and adds them to PATH; no administrator rights are needed. To pin a version, run the script with sh -s -- --version 1.25.1 or set LOUP_VERSION. After a browser download on macOS, clear the quarantine flag with xattr -dr com.apple.quarantine <dir>.

What each platform can do with freestanding code in 1.25:

  • Type-checking (loup check in a project; loup-bootstrap check on Linux x86-64) works on every platform.
  • Building images (loup build --target freestanding --arch …) works on Linux x86-64 and needs ld.lld. The default back end is Loup's own clang-free llbe; --backend llvm uses clang.
  • Running images needs QEMU (qemu-system-x86_64, qemu-system-aarch64, qemu-system-riscv64, qemu-system-arm) or real hardware. x86_64, aarch64, riscv64, armv7 and Cortex-M images are boot-tested under QEMU.

Hello, bare metal

Three files print a line on the serial console of QEMU's AArch64 virt machine. src/main.lpfree:

@mode(freestanding)

# QEMU `virt` (AArch64): the PL011 UART's data register.
let UART0_DATA = Volatile[uint8].at(0x0900_0000)

func uartWrite(s: string): void {
    for (c in s) {
        UART0_DATA.write(c as uint8)
    }
}

func kernelMain(): void {
    uartWrite("Hello from Loup freestanding\n")
    while (true) {
        asm { "wfi" clobbers: [] }
    }
}

@entry
@naked
@unsafe("naked: runs before any stack exists; sets sp from __stack_top")
@section(".text.boot")
func _start(): void {
    asm {
        "adrp x0, __stack_top"
        "add x0, x0, :lo12:__stack_top"
        "mov sp, x0"
        "bl kernelMain"
        "1: wfe"
        "b 1b"
        clobbers: ["memory"]
    }
}

kernel.ld:

ENTRY(_start)

PHDRS {
    text PT_LOAD FLAGS(7);
}

SECTIONS {
    . = 0x40080000;
    .text.boot : { KEEP(*(.text.boot)) } :text
    .text      : { *(.text .text.*) }       :text
    .rodata    : { *(.rodata .rodata.*) }   :text
    .data      : { *(.data .data.*) }       :text
    .bss       : {
        *(.bss .bss.*)
        . = ALIGN(16);
        . = . + 65536;
        __stack_top = .;
    } :text
}

hello.lppkg:

name = "hello"
version = "0.1.0"

[build]
entry  = "src/main.lpfree"
target = "freestanding"
mode   = "freestanding"
arch   = "aarch64"
panic  = "halt"

[linker]
script = "kernel.ld"
entry  = "_start"

[freestanding]
stack_size = 65536

Build on Linux x86-64 and boot:

loup build
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 256M -nographic -kernel ./hello
Hello from Loup freestanding

Chapters 1 and 2 explain every line; chapter 21 builds a larger kernel for x86_64.

The Loup books

Mode What it is for Book
host.scripted scripts and tools run by the interpreter The Loup Scripting Book
host.dual, host.native applications compiled to native binaries (or run by the interpreter) The Loup Native Book
web.scripted, web.compiled browser applications (the browser runtime is planned) The Loup Web Book
freestanding kernels, boot loaders, firmware — no runtime this book

What freestanding keeps, and what it leaves out

You keep the type system, generics and interfaces (resolved at compile time), match, Result-based error handling, the ownership verbs and unique values, borrows and lifetimes, and compile-time evaluation. You gain @entry, @naked, @callconv, asm { }, Volatile[T] and volatile borrow, @repr, @align, @section, buffer[T, N], onstack, @interrupt, @interruptSafe, InterruptGuard, @panic_handler, @allocator, PartialInit[T], and bare intrinsics for atomics and raw memory.

You leave out everything that needs a runtime: exceptions (throw, catch (T as e)), weak references, dynamic class, defer import, consume, async/await, bigint/decimal, growable collections, template fences, reflection, and the hosted standard library. Chapter 1 lists which of these the 1.25 checker rejects and which it does not yet catch.

Table of contents

Part I: The Bare-Metal Foundation

  1. What Is Freestanding Mode?
  2. Building a Bare-Metal Binary
  3. Integer Types and Wrapping Arithmetic
  4. Functions and Control Flow

Part II: Data and Types on the Metal

  1. Structs and Layout Control
  2. Enums and Pattern Matching
  3. Classes and Allocators
  4. Interfaces and Generics

Part III: Safety Without a Runtime

  1. Error Handling with Result
  2. Ownership Verbs
  3. Borrow and Lifetimes
  4. Unique Ownership
  5. Memory Primitives

Part IV: Talking to Hardware

  1. Inline Assembly
  2. MMIO with Volatile
  3. Interrupts and Naked Functions
  4. Entry Points and Panic
  5. Sections and the Linker

Part V: Putting It Together

  1. Multicore and Atomics
  2. The Available Standard Library Subset
  3. Example — x86_64 Kernel Stub
  4. Testing and Deploying Firmware
  5. Memory Safety and Kernel Development Patterns — kernel problems worked through with Loup features: fixed-capacity structures, address newtypes, user-pointer validation, DMA hand-off with unique ownership, cleanup with finally, generation-checked handles, interrupt-safe rings, and the @unsafe audit.
  6. synchronized Classes and Lock Ordering

Conventions

  • Every chapter's first listing shows @mode(freestanding). Later listings may hide it, along with helper declarations, so the visible code stays focused; every listing marked as compiling is type-checked with the 1.25 toolchain as a complete src/main.lpfree.
  • A listing labelled as not compiling is followed by the compiler's actual diagnostic. Listings marked as compiling but behaving wrongly are explained in the text.
  • > ⚠ **Planned.** marks a feature the Language Reference specifies that Loup 1.25 does not implement yet.
  • Shell commands are shown in sh blocks, program and compiler output in text blocks, manifests in toml blocks.
  • Section references such as "§3-3.17b" point into the four-volume Loup Language Reference; freestanding mode is specified in volume 3 (compilation), §3-3, at loup-language-compilation-v1_20.md, and the core language in loup-language-core-v1_20.md. Where the toolchain and the reference disagree, the book shows what the toolchain does and says what the reference specifies.

Chapters

  1. Chapter 1: What Is Freestanding Mode?

    What you'll learn. What @mode(freestanding) is, what it keeps from the rest of Loup, what it leaves out, what the 1.25 toolchain can do with it today, and what a first freestanding program looks like.

  2. Chapter 2: Building a Bare-Metal Binary

    This chapter is the workflow you repeat for every freestanding project: lay out the files, write the manifest and linker script, type-check, build an image for a target architecture, and boot it in…

  3. Chapter 3: Integer Types and Wrapping Arithmetic

    Kernel code is mostly integer code: register values, addresses, page-table bits, ring-buffer indexes, tick counters. This chapter covers the integer rules you meet in @mode(freestanding):

  4. Chapter 4: Functions and Control Flow

    Most of Loup's everyday syntax is the same in @mode(freestanding) as in any other mode: func, if/else, while, for, match, return, break, continue and lambdas. This chapter is a quick tour of that…

  5. Chapter 5: Structs and Layout Control

    Most hardware interfaces have a layout someone else chose. The CPU vendor decided what an interrupt frame and a task state segment look like; the network standard fixed the bytes of a UDP header; the…

  6. Chapter 6: Enums and Pattern Matching

    Enums carry a lot of the weight in freestanding code. Device states are enums, error types are enums, and so are the legal values of most register fields. Because match must be exhaustive, the code…

  7. Chapter 7: Classes and Allocators

    In the hosted modes a class instance lives on a heap managed by ARC. A freestanding image has neither a heap nor ARC, so classes need more care. This chapter covers:

  8. Chapter 8: Interfaces and Generics

    Freestanding code keeps Loup's interfaces and generics. A UART, a framebuffer console and a debug ring buffer can all implement one SerialOut interface; a table or a ring buffer can be written once…

  9. Chapter 9: Error Handling with Result

    The hosted modes have exceptions. Freestanding does not: throw and exception catch arms are rejected with E_FREESTANDING_EXCEPTIONS. This chapter explains why, then shows what replaces them —…

  10. Chapter 10: Ownership Verbs

    With no garbage collector and no reference counting under a freestanding image, something else has to say who may read a value, who may change it, and when its owner gives it away. In Loup that is…

  11. Chapter 11: Borrow and Lifetimes

    Chapter 10 introduced the verbs that say what a function does with an argument. This chapter covers the borrow forms underneath them and the lifetime labels that let the compiler check that a…

  12. Chapter 12: Unique Ownership

    A freestanding image has no reference counts, so "who owns this object, and when is it done?" must be answered at compile time. unique is how Loup answers it: a unique value has exactly one owner at…

  13. Chapter 13: Memory Primitives

    Sooner or later freestanding code works on memory directly: a page has to be zeroed before it is mapped, a descriptor ring has to sit at an address the device can find, a register block is a run of…

  14. Chapter 14: Inline Assembly

    Some instructions have no Loup equivalent: cli and sti, rdmsr/wrmsr, lidt, reading cr2, mrs/msr on Arm, csrr/csrw on RISC-V, the barrier instructions, the first few instructions after reset. For…

  15. Chapter 15: MMIO with Volatile

    On most systems a device is controlled through memory-mapped I/O (MMIO): its registers appear at fixed physical addresses, and a load or store to one of those addresses talks to the device instead of…

  16. Chapter 16: Interrupts and Naked Functions

    An interrupt handler runs at a moment you did not choose, on a stack you did not set up, in the middle of code that was doing something else. It must save whatever it touches, return with a special…

  17. Chapter 17: Entry Points and Panic

    A freestanding program has two special functions. The entry point is the first Loup code the CPU runs: there is no C runtime and no main, only an address the boot loader or the reset vector jumps to.…

  18. Chapter 18: Sections and the Linker

    A freestanding image is a set of named sections that the linker places at addresses a linker script chooses. The boot stub has to sit where the loader jumps, the exception vectors where the CPU looks…

  19. Chapter 19: Multicore and Atomics

    With more than one CPU, two pieces of code really do run at the same time against the same memory. You need operations that complete as one indivisible step, and a way to say which other memory…

  20. Chapter 20: The Available Standard Library Subset

    Almost all of Loup's standard library is hosted code: it expects the Loup runtime, an operating system, exceptions or a heap. A freestanding image has none of those, so what it can use is small and…

  21. Chapter 21: Example — x86_64 Kernel Stub

    This chapter puts the previous ones together in one small, complete kernel for x86_64. It boots under qemu-system-x86_64 -kernel, switches the CPU from 32-bit protected mode to 64-bit long mode,…

  22. Chapter 22: Testing and Deploying Firmware

    A freestanding program has no test runner underneath it and no exceptions to report a failed assertion. Testing it takes several layers, each catching what the others cannot:

  23. Chapter 23: Memory Safety and Kernel Development Patterns

    A kernel has memory-safety problems an application never sees. There is no allocator underneath it, devices write into its memory on their own schedule, interrupt handlers run in the middle of…

  24. Chapter 24: synchronized Classes and Lock Ordering

    Chapter 19 built spin locks by hand from atomic intrinsics. They work, but nothing ties the lock to the data it protects, nothing stops code from touching that data without the lock, and nothing…