# Chapter 2: Native mode overview

Chapter 1 got a program running. This chapter steps back and explains what the two native modes, `host.native` and `host.dual`, mean to the compiler: where they sit among Loup's six modes, what they share, how they differ, and which parts of the language a native binary cannot run yet. The rest of the book builds on this picture.

## Loup's six modes

Every Loup file declares a mode on its first line. The mode decides where the code runs (a browser, an operating-system process, or bare metal), how it is delivered (interpreted, compiled ahead of time, or either), and which features it may use (Language Reference §1-2.4):

| Mode | Runs in | Delivered as | Entry point | Extension |
|---|---|---|---|---|
| `host.scripted` | an OS process | interpreted by `loup run` | top-level statements, in order | `.lpscr` |
| `host.dual` | an OS process | interpreted, WebAssembly, **or** a native binary | `main()` | `.loup` |
| `host.native` | an OS process | a native binary | `main()` | `.lphc` |
| `web.scripted`, `web.compiled` | a browser | WebAssembly | a `mount(...)` call | `.lpweb` |
| `freestanding` | bare metal | a native image with no runtime | an `@entry` function | `.lpfree` |

This book covers the two middle rows. Scripts have their own book (*The Loup Scripting Book*), and so do browser code (*The Loup Web Book*) and firmware (*The Loup Freestanding Book*).

The directive must be the first token in the file; comments may precede it. A later directive is `E_MODE_POSITION`, a second one is `E_MODE_DUPLICATE`. If a file has no directive, the project's `[build] mode` applies, and failing that the file is a `host.scripted` script, so always write the line.

## What the two native modes share

`host.native` and `host.dual` are both *ahead-of-time* modes. That gives them the same structure:

- **`main()` is the entry point.** The runtime calls it after the module is initialized. It takes no parameters and returns `void` or `int` (ruling MAIN-1).
- **No top-level statements.** Only declarations and constant initializers may appear at module scope. Anything else is `E_TOPLEVEL_CONTROL_AOT`.
- **Static types are expected.** Every parameter and return type is declared. `Any` still compiles, but it produces a warning, because the compiler has to fall back to dynamic dispatch:

```loup
@mode(host.native)

func show(x: Any): void {
    println(x);
}

func main(): void {
    show(3);
}
```

*Listing 2-1: `Any` compiles in an AOT mode, with a warning*

```text
3
main.lphc:3:14: warning: W_ANY_IN_AOT_MODE: use of `Any` in host.native mode prevents AOT specialisation — consider a static type if performance is critical
main.lphc:3:14: warning: W_ANY_IN_AOT_MODE: use of `Any` in host.native mode prevents AOT specialisation — consider a static type if performance is critical
```

`loup run` prints warnings after the program's own output (Stage0 1.25.1 prints this one twice). The program still runs; a warning never stops a build.

- **ARC memory semantics.** Class instances are reference-counted; structs, enums, tuples and collections are values that are copied (Language Reference §1-8.1.1). Chapter 9 covers this in detail. This is not a difference from the other modes: every mode presents the same reference-counting semantics, and none of them uses a tracing garbage collector.
- **The ownership system.** Parameter verbs (`reads`, `edits`, `takes`, `shares`), `modifier` methods, `safe { }` blocks, `borrow`, and `unique` bindings are checked at compile time in every mode. Native programs lean on them most, because they decide when reference counts change and who may mutate what. Chapters 15 to 18 cover them.

Here is a small program that uses several of these pieces: a class with a `modifier` method, a `safe` block that permits calling it, a dictionary, and string methods.

```loup
@mode(host.native)

class WordCounter {
    var counts: dict[string, int] = {};

    pub modifier func add(word: string): void {
        let w = word.toLowerCase();
        let current = this.counts.get(w) ?? 0;
        this.counts[w] = current + 1;
    }

    pub func count(word: string): int {
        return this.counts.get(word) ?? 0;
    }

    pub func vocabularySize(): int {
        return this.counts.length();
    }
}

func main(): void {
    let text = "the cat and the hat and THE bat";
    let wc = WordCounter();
    safe {
        for (w in text.split(" ")) {
            wc.add(w);
        }
    }
    println(i"'the' appears {$ wc.count("the") $} times");
    println(i"{$ wc.vocabularySize() $} distinct words");
}
```

*Listing 2-2: A word counter*

```text
'the' appears 3 times
5 distinct words
```

A few things to notice:

- The field `counts` is a `var`, and only a method declared `modifier` may write to it.
- `wc` is a reference-counted class instance, and calling a `modifier` method on one requires a `safe { … }` block (or a `safe func`). That is where the compiler checks that nothing else is mutating the same object at the same time. Chapter 15 explains the rule.
- `counts.get(w)` returns an `Option[int]`; `??` supplies the value to use when it is `None`.
- Fields without `pub` are private to the class.

## Choosing between `host.native` and `host.dual`

The two modes accept almost the same language. The difference is how the code may be delivered, and that decides which features are allowed.

**`host.native`** is for programs whose deliverable is a native binary. It permits the features that only make sense when Loup compiles straight to machine code: `unique class` (single-owner class types, chapter 17), inline `asm { }` blocks (chapter 19), and `volatile borrow` parameters and `Volatile[T]` for memory-mapped device access.

**`host.dual`** is for code that must also run without the native toolchain: on the interpreter with `loup run`, as WebAssembly with `loup-bootstrap build` and `loup-launcher`, or as a packaged app with `loup-bootstrap build --target desktop`. Because a `host.dual` file must behave the same on every path, the features that need direct machine-code generation are refused there. Today a `host.dual` program is also the way to use the two features that native binaries cannot run yet, template fences and `async`, on the interpreter or in a WebAssembly build.

Choose `host.native` when you ship a binary and want the native-only features. Choose `host.dual` for libraries and tools that should work everywhere, including Windows and Linux arm64, where native builds are not available yet. Every `host.dual` file is also a valid `host.native` file, so moving from dual to native is a one-line change; the reverse may not be.

### Mistake: a native-only feature in a dual file

A `unique class` lives in exactly one place at a time, and the compiler moves it rather than counting references. That cannot round-trip through the other delivery paths, so `host.dual` rejects it:

```loup,does_not_compile
@mode(host.dual)

unique class FileLock {
    pub let path: string;
    new(path: string) { this.path = path; }
}

func main(): void {
    let lock = FileLock("/tmp/app.lock");
    println(lock.path);
}
```

```text
main.loup:3:1: error[E_UNIQUE_CLASS_ARC_BINDING]: E_UNIQUE_CLASS_ARC_BINDING: `unique class 'FileLock'` is only valid in `@mode(native)` or `@mode(freestanding)`
```

The message still uses the retired name `native` for `host.native`. There are two fixes. If the program will only ever be a native binary, change the first line to `@mode(host.native)` and rename the file to `.lphc`:

```loup
@mode(host.native)

unique class FileLock {
    pub let path: string;
    new(path: string) { this.path = path; }
}

func main(): void {
    let lock = FileLock("/tmp/app.lock");
    println(lock.path);
}
```

*Listing 2-3: `unique class` is a `host.native` feature*

If the code must stay portable, keep `host.dual`, declare an ordinary `class`, and give the one binding that owns it single-owner semantics with `unique let lock = FileLock(…)`. `unique let` works in every mode; chapter 17 shows how.

Inline assembly is the other common case:

```loup,does_not_compile
@mode(host.dual)

func cpuPause(): void {
    asm { "pause" }
}

func main(): void {
    cpuPause();
}
```

```text
main.loup:4:5: error[E_ASM_NOT_COMPILED]: E_ASM_NOT_COMPILED: `asm { }` is only valid in a compiled mode (`web.compiled`, `host.native`, `freestanding`); the module mode is `host.dual` (§3-3.7)
```

Here there is no portable alternative: assembly is machine code for one architecture, so the function belongs in a `host.native` file.

## What native binaries cannot do yet

Loup 1.25.1 builds native binaries for a large part of the language: value types, strings, collections, classes with reference counting, interfaces, closures, enums and `match`, and exceptions. Some features that the specification lists for `host.native` are not implemented in the native code generator, or not shipped in the standard library, yet:

| Feature | Status in 1.25.1 | Chapter |
|---|---|---|
| `async` / `await` | Runs on the interpreter (eagerly) and in WebAssembly; a native build refuses it | 21 |
| Template fences (`html`, `css`, …) | Run on the interpreter and in WebAssembly; a native build refuses them | 14 |
| `for concurrent` | Refused by native builds | 20 |
| `weak` / `unowned` fields | Accepted, but a native binary keeps them as strong references, so reference cycles leak | 9, 18 |
| `int` overflow | Wraps silently; the specification says it should throw | 3 |
| Printing an enum whose payload is a tuple or a `Result` (and deeper nestings such as `Option[list[…]]`) | Refused by native builds | 8 |
| `--lib shared` / `--lib static` | Write a placeholder file, not a loadable native library | 24 |
| `native.ffi`, `native.mem`, `native.simd`, `native.gpu`, `ui.desktop`, `sync.*` | Not in the standard library | 19–23 |
| `arc let` / `arc var` | Not parsed | 9 |

When a native build meets one of these, it stops with an `E-BUILD-NATIVE` error that names the construct rather than producing a binary that behaves differently. For example, building a program with an `async func`:

```text
error[E-BUILD-NATIVE]: unsupported in managed native (libc foundation): async function `half` (native builds have no task scheduler: `async`/`await` run on the interpreters only); …
```

The chapters that cover these topics say exactly what works today and mark the rest **⚠ Planned**.

## Coming from another mode

**From scripts (`host.scripted`).** The language is the same; the structure changes. Code moves into `main()` and functions, every parameter gets a type, and `Any` becomes a warning. You will start writing ownership verbs, because a native program's correctness depends on who may mutate what. The `__name__ == "__main__"` guard has no purpose in an AOT mode, since `main()` is always the entry point.

**From the browser (`web.*`).** Host capabilities such as the file system (`readText`, `writeText`), processes (`exec`) and the environment (`runtime.args()`, `env`) are available. There is no DOM.

**From freestanding.** You gain the runtime: reference-counted classes, exceptions, growable collections and the standard library. You lose `@entry`, `@interrupt`, `@naked` and the manifest's panic policy, which exist only in freestanding.

## Summary

- Loup has six modes. This book covers `host.native` (`.lphc`), compiled to a native binary, and `host.dual` (`.loup`), which can also run on the interpreter or as WebAssembly.
- Both start at `main()`, forbid top-level statements, expect static types (`Any` is `W_ANY_IN_AOT_MODE`), present ARC semantics, and check the ownership system at compile time.
- `host.native` adds `unique class`, `asm { }` and `Volatile[T]`; using them in `host.dual` is an error (`E_UNIQUE_CLASS_ARC_BINDING`, `E_ASM_NOT_COMPILED`).
- Choose `host.native` for binaries that need those features; choose `host.dual` for code that must run everywhere. Every `host.dual` file is a valid `host.native` file.
- Native builds in 1.25.1 refuse `async`, template fences and `for concurrent`, keep `weak` references strong, and let `int` arithmetic wrap. The relevant chapters mark these **⚠ Planned**.
- Chapter 3 starts on the language itself: bindings, numeric types, and what happens on overflow.
