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

> Understand the core differences between Maru's libc and Linux platforms. Learn how libc uses host C library while Linux uses raw kernel syscalls for system operations.

- Repository: [Attila Lendvai/maru](https://github.com/attila-lendvai/maru)
- Tags: deep-dive
- Published: 2026-02-25

---

**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`:

```lisp
(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`:

```lisp
(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:

```lisp
(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`:

```lisp
(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`](https://github.com/attila-lendvai/maru/blob/main/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.