What Is the Difference Between the libc and Linux Platforms in Maru?

The libc platform delegates all system operations to the host C library, while the Linux platform operates as a freestanding environment using raw kernel syscalls without any C library dependency.

Maru is a self-hosting Lisp system that abstracts hardware and OS details through platforms—modules that define how the virtual machine interacts with memory, I/O, and process control. When examining the difference between the libc and linux platforms, the fundamental distinction lies in their assumptions about the host environment: libc treats Maru as a standard C program, while Linux treats it as a bare-metal process speaking directly to the kernel.

Platform Architecture in Maru

Maru's platform layer isolates VM primitives such as memory allocation, stream flushing, and diagnostics. The loader at source/platforms/load-platform.l selects the appropriate implementation based on the target name. Both platforms expose identical high-level APIs—including platform/flush-streams, platform/acquire-memory, and platform/diag-print—but their underlying implementations diverge significantly according to the constraints of their respective environments.

The libc Platform: Hosted C Runtime

The libc platform (also called C99) assumes Maru is compiled and linked as a standard C program with the full C runtime available. This is the default for hosted builds such as make test-bootstrap-x86.

Entry Point and Arguments

In source/platforms/libc/libc.l, the platform receives argc, argv, and environment variables directly from the host C runtime's main function. The VM does not parse these from the stack; the host environment provides them as standard C parameters.

Memory Management

Memory allocation delegates entirely to the host C library. The platform/acquire-memory form in source/platforms/libc/libc.l expands directly to libc/malloc:

(define-form platform/acquire-memory (size)
  `(libc/malloc ,size))

I/O and Diagnostics

All I/O uses standard C library functions. The platform/flush-streams function calls libc/fflush on stdout and stderr, while platform/diag-print uses libc/fputs to write to stderr:

(define-function platform/flush-streams ()
  (libc/fflush libc/stdout)
  (libc/fflush libc/stderr))

(define-form platform/diag-print (str)
  `(libc/fputs (string->C-string ,str) libc/stderr))

The Linux Platform: Freestanding Syscalls

The Linux platform operates without any C library. It is designed for freestanding builds where Maru runs directly on the kernel, making it suitable for bare-metal targets or minimal runtime environments.

Entry Point and Arguments

Instead of receiving arguments from a C main function, the Linux platform parses argc, argv, and env directly from the stack layout that the Linux kernel sets up when launching an executable via execve. This raw stack parsing happens in source/platforms/linux/linux.l without C library assistance.

Memory Management

The Linux platform implements its own memory allocator using raw syscalls. The platform/acquire-memory function in source/platforms/linux/linux.l manages the program break using SYS_brk and explicitly sets memory protections with SYS_mprotect to ensure regions are readable, writable, and executable:

(define-function platform/acquire-memory (size)
  (unless *initial-break*
    (set *current-break* (set *initial-break* (linux-syscall linux/SYS_brk 0))))
  (let ((new-break (linux-syscall linux/SYS_brk (+ *current-break* size))))
    (set *current-break* new-break)
    (linux-syscall linux/SYS_mprotect *initial-break*
                   (- *current-break* *initial-break*)
                   (+ (+ linux/PROT_READ linux/PROT_WRITE)
                      linux/PROT_EXEC))
    *initial-break*))

I/O and Diagnostics

All I/O is performed through Linux syscalls. The platform/flush-streams function is effectively a no-op because writes go directly to file descriptors via SYS_write. The platform/diag-print macro constructs syscall invocations to write to STDERR_FILENO:

(define-function platform/flush-streams ()
  ;; flush is a no‑op; the kernel writes directly.
  )

(define-form platform/diag-print (str)
  (if (is <string> str)
      `(linux-syscall linux/SYS_write linux/STDERR_FILENO ,@(string+length str))
      `(let ((cstr (string->C-string ,str)))
         (linux-syscall linux/SYS_write linux/STDERR_FILENO cstr
                         (C-string-length cstr)))))

Key Implementation Differences

When evaluating the difference between the libc and linux platforms in Maru, consider these technical distinctions:

  • Memory Allocation: libc delegates to libc/malloc and libc/free, while Linux manages the program break manually with SYS_brk and SYS_mprotect.
  • I/O Primitives: libc uses buffered streams (libc/fflush, libc/fprintf), whereas Linux writes directly to file descriptors via SYS_write.
  • Entry Point: libc receives arguments from the C runtime's main function; Linux parses the raw stack layout provided by the kernel's execve.
  • String Operations: libc uses libc/memcpy and libc/memmove for object copying, while Linux implements byte-wise loops (copy-oops, platform/move-oops) that understand the VM's object model.
  • Exit Handling: libc calls libc/exit or libc/abort; Linux invokes SYS_exit directly.

Source File Locations

The platform implementations are located in the following files within the attila-lendvai/maru repository:

  • Platform loader: source/platforms/load-platform.l – selects the platform based on the target name.
  • libc implementation: source/platforms/libc/libc.l – defines FFI wrappers for C library functions and the hosted platform API.
  • Linux implementation: source/platforms/linux/linux.l – implements syscall wrappers, manual memory management with brk/mprotect, and the freestanding platform API.
  • Documentation: doc/platforms.md – provides high-level platform overview and selection criteria.

Summary

  • The libc platform treats Maru as a hosted C program, delegating all system interactions to the standard C library (malloc, printf, exit).
  • The Linux platform operates as a freestanding executable, using raw kernel syscalls (SYS_brk, SYS_write, SYS_exit) without any C library dependency.
  • Both platforms expose identical high-level APIs (platform/acquire-memory, platform/flush-streams, platform/diag-print), but their implementations differ in memory management strategy, I/O buffering, and argument parsing.
  • Choose libc for standard Unix development with debugging tools; choose Linux for minimal runtime environments, bare-metal targets, or when eliminating C library overhead.

Frequently Asked Questions

Can I switch between libc and Linux platforms without changing my Maru code?

Yes. Maru's platform abstraction ensures that high-level code remains identical regardless of the target. The loader at source/platforms/load-platform.l selects the appropriate implementation file (libc/libc.l or linux/linux.l) at build time, providing the same platform/acquire-memory, platform/flush-streams, and platform/diag-print APIs in both cases.

Why does the Linux platform manually manage memory with brk instead of using malloc?

The Linux platform is designed for freestanding environments where no C library is linked. Since malloc is part of libc, the Linux implementation must request memory directly from the kernel. It uses the SYS_brk syscall to adjust the program break and SYS_mprotect to set permissions, effectively implementing its own memory allocator on top of raw kernel primitives as seen in source/platforms/linux/linux.l.

Which platform should I use for debugging and development?

Use the libc platform for development and debugging. Because it delegates to the standard C library, you can use conventional tools like gdb, valgrind, and standard I/O tracing. The Linux platform is optimized for minimal runtime size and direct kernel interaction, making it ideal for production builds targeting bare metal or containerized environments where C library overhead is undesirable.

How does argument parsing differ between the two platforms?

The libc platform receives argc, argv, and environment variables directly from the C runtime's main function signature. The Linux platform, however, must parse these values manually from the initial stack layout that the Linux kernel sets up when launching an executable via execve. This raw stack parsing happens in source/platforms/linux/linux.l without any assistance from C library startup code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →