# Native runtime objects

The Apple ARM64 and Linux AMD64 backends share the runtime ABI, C runtime,
constructor/global link layout, and snapshot support from `aihc-native`. Only
instruction selection, register allocation, and assembly emission belong to
the architecture packages.

Both backends are built on every platform. `aihc compile` defaults to the host
target on supported hosts and accepts an explicit target for cross-compilation:

```text
aihc compile Main.hs --target apple-arm64
aihc compile Main.hs --target linux-amd64
```

The selected LLVM target triple is passed to Clang for dependency objects, the
shared runtime, and the final executable. Cross-linking therefore requires a
Clang installation with the corresponding target linker and sysroot.

Native heap objects use a one-word tagged header followed by shape-specific
payload words. The low three header bits are the physical tag. The remaining
bits point to an aligned, statically emitted info table.

```text
saturated constructor: [header] [fields...]
thunk:                 [header] [environment...]
partial application:   [header] [fields...]
indirection:           [header] [target]
blackhole:             [header] [environment / reserved target...]
```

Each info table records the object's identity, populated field count, remaining
logical arity, pointer bitmap, next application-stage table, and an optional
native apply entry. Application changes the header to the statically known next
table. Consequently every managed object pays only for its tagged header and
payload; arity, tracing metadata, and apply code consume no per-object shape
word.

The native backends give saturated closure stages a generated apply entry.
Apply sites place the closure, continuation, and supplied values in the AIHC
register convention and branch through that info-table entry. The stage stub
loads captured fields directly from the closure, moves the supplied values into
their final argument registers, and tail-branches to the target function's
register entry. Non-saturating closures, partial constructors, and invalid
applications leave the apply entry empty and use the shared C slow path.

Primitive operations have no heap-object tag. A partially applied primitive is
lowered to an ordinary closure whose generated entry makes the saturated
primitive call.

Every updateable object reserves at least two words. Evaluating a thunk changes
its header to `BLACKHOLE`, executes the entry encoded by its old header, then
changes the same object to `INDIRECTION` and writes the returned heap pointer
into its first payload word. There is no separate cell allocation.

Exceptions have no native heap tag or object representation. They are removed
before native runtime lowering. The final physical tag is the semispace
collector's temporary forwarding marker.

The cooperative scheduler keeps thread records, blackhole records, wait queues,
and pending IO requests in auxiliary C allocations. Suspended threads retain
ordinary action or continuation closures, which the WebAssembly adapter expands
into the machine's growable reusable argument buffer only when selected. Native
backends resume the same records through their register convention. The machine
reuses one locals area because CPS transfers discard the preceding function
frame; all retained closure values and pending-request continuations are precise
collector roots. The argument buffer contains only transient transfers:
generated entries copy their parameters into the locals area before reaching an
allocation safepoint, so the collector does not scan the buffer.

`MVar#` uses a runtime-owned empty/full cell with separate FIFO queues for
blocked readers, takers, and putters. Putting into an empty cell wakes every
blocked reader with the same value and either hands the value directly to the
oldest taker or leaves the cell full. Taking from a full cell returns its old
value and, when a putter is waiting, installs that putter's value before waking
it. This direct handoff prevents a newly running thread from overtaking an
already blocked operation. Stored values, queued put values, continuations, and
their suspended threads are collector roots. The cells themselves are
auxiliary allocations owned for the machine's lifetime; weak pointers and
finalization are intentionally outside the initial interface.

## IO manager

The runtime ABI separates operation submission, scheduler suspension, and
result consumption:

1. An ordinary foreign call allocates an opaque request in the `submitted`
   state without blocking.
2. `awaitIO#` asks the configured backend to make progress. Immediate
   completions continue directly; otherwise the request becomes `pending` and
   retains the current green thread and continuation.
3. Backend polling changes a ready request to `completed` and enqueues its
   thread. A final ordinary foreign call takes the result, changes the request
   to `consumed`, and releases it.

Backend workers or readiness mechanisms produce only native completion data;
Haskell continuations are always reconstructed and enqueued on the scheduler
thread. This prevents moving-heap pointers from escaping to an asynchronous
backend. Pending requests are collector roots only for their saved continuation
and thread resume record. The opaque request pointer itself has `Addr#`
representation and is not traced as a Haskell heap pointer.

IO operations target opaque runtime-owned handles rather than OS descriptor
numbers. Standard input and output are the first preopened handles, while each
backend owns their platform representation. The POSIX backend stores a file
descriptor in each handle, sets it nonblocking, and uses `poll` when buffer
reads or writes report that they would block. Windows can instead store `HANDLE` or
`SOCKET` resources without exposing either representation to generated code.

Reads and writes operate on an offset and length within the payload of a pinned
`MutableByteArray#`. The proof-of-concept runtime allocates each byte array
outside the Haskell heap and does not release it. A request retains that stable
allocation through completion. Callers must not access the submitted slice
while the request is pending. A future garbage collector can own the same
descriptor and payload layout without changing `awaitIO#` or the backend
request model.

`copyAddrToByteArray#` copies an explicit number of bytes from an `Addr#` into a
bounds-checked destination slice. It does not scan for a terminating zero. The
source address must remain valid until the synchronous copy returns; only the
stable byte-array payload is retained by later asynchronous requests.

A non-negative request result is the number of transferred bytes. A non-empty
read returns zero at end-of-file. Either operation can return fewer bytes than
requested, so the future `Handle` layer must resubmit the remaining slice when
it requires a complete transfer. Errors use `-(errno + 1)` in the POSIX proof
of concept. `GHC.Event` owns only generic suspension. `GHC.IO.StdHandles`
exposes buffer allocation, address and indexed byte access, handle operations,
and the standard handles. Text encoding, locking, transfer loops, and full
`Handle` semantics remain above this boundary.
