NURL

Unikernels and embedded targets

Freestanding Unikernels (x86_64, AArch64, RISC-V64) and bare-metal microcontrollers (ESP32, RISC-V).

NURL compiles to target-agnostic LLVM IR or C, making it straightforward to run without a host operating system — either as a bootable Unikernel appliance or directly on bare-metal microcontrollers.

Unikernels (x86_64, AArch64, RISC-V64)

unikernel/ turns an ordinary .nu program into a self-contained, bootable kernel image with no Linux kernel or host userland underneath:

./unikernel/build.sh app.nu                        # x86_64 PVH ELF (QEMU / KVM / Firecracker)
./unikernel/build.sh --arch aarch64 app.nu         # ARM64 QEMU virt image
./unikernel/build.sh --arch riscv64 app.nu         # RISC-V64 QEMU virt image
./unikernel/build.sh --iso app.nu                  # bootable BIOS + UEFI hybrid .iso

What is inside the unikernel target:

  • nolibc + runtime_bare.c — a freestanding C library (including a from-scratch libm and malloc), cooperative fiber scheduler, and hardware entropy driver (RDRAND / RNDR / VirtIO-RNG).
  • Sub-5 ms boot — enters directly via the PVH ELF note (x86_64) or virt boot stub (AArch64 / RISC-V64), with optional SMP multi-vCPU wake (APIC / PSCI / SBI HSM).
  • VirtIO-Net + sans-IO network stack — unikernel/net/sockets.nu implements the standard NURL socket ABI (tcp_*, udp_*, dns_*, TLS 1.3, HTTP/1.1, HTTP/2, MCP) in pure NURL over stdlib/net/ and unikernel/drivers/virtionet.nu.
  • VirtIO-Blk + FAT12/16/32 & initfs — read/write persistent block storage (unikernel/drivers/virtioblk.nu, stdlib/fs/fat32.nu) plus baked-in read-only filesystems.

See unikernel/README.md for QEMU, Firecracker, and Kubernetes (unikernel/k8s/) deployment guides.

Memory-mapped I/O

stdlib/hal/mmio.nu provides the register-level primitives used to build hardware drivers. Each primitive reads or writes a 32-bit word at a fixed physical address:

( mmio_read32  i addr )          → i    // load  *(u32*)addr
( mmio_write32 i addr i32 val )  → v    // store *(u32*)addr = val
( mmio_set32   i addr i32 mask ) → v    // *(u32*)addr |= mask
( mmio_clear32 i addr i32 mask ) → v    // *(u32*)addr &= ~mask

Because arbitrary physical addresses bypass memory safety, mmio_* functions are declared raw and must be called from an unsafe @ driver function. They compile to LLVM's load volatile / store volatile, so the optimizer never hoists, reorders, or coalesces an MMIO access.

ESP32

stdlib/hal/esp32.nu is a register-level HAL for the classic ESP32 (Xtensa). You build it entirely on mmio.nu. There is no ESP-IDF. There is no FFI beyond the memory-mapped registers themselves. Register addresses are cross-checked against Espressif's technical reference manual.

( esp32_gpio_enable_output i mask ) → v   // drive these pins as outputs
( esp32_gpio_set   i mask ) → v            // set pins high
( esp32_gpio_clear i mask ) → v            // set pins low

( esp32_uart_putc i ch ) → v               // blocking write, UART0
( esp32_uart_getc )      → i               // blocking read, UART0

Both CPU families on the chip are covered, through two different toolchains:

Chip familyISAToolchain
ESP32 / S2 / S3 (classic)Xtensa LX6/LX7esp-clang + ESP-IDF
ESP32-C3 / C6 / H2RISC-V (RV32IMC)stock LLVM llc

The Xtensa path has been flashed to real ESP32 silicon. It is verified over serial. A GPIO blink uses only NURL's register writes. A call crosses from C into NURL and back. examples/esp32/ has both paths. idf-blink/ is a complete ESP-IDF project. All GPIO access is NURL. C supplies only app_main. blink.nu targets the RISC-V chips directly through stock LLVM. It needs no vendor fork.

Next

Last updated on

On this page