# M1 Microkernel + IPC + MoonBit Interface Draft

This document defines a concrete M1 baseline for the architecture:

- Microkernel keeps only scheduling, physical memory, and interrupt forwarding.
- OS components (`netd`, `fsd`, `wmd`) run as isolated user-space processes.
- Applications run in MoonBit/WASM sandbox and access system capabilities only via IPC.

## 1) M1 Scope

M1 target is one complete path:

`MoonBit weather app -> IPC -> netd -> external HTTP API -> response -> app render`

Out of scope for M1:

- Full POSIX compatibility
- Async/await IPC multiplexing
- DMA zero-copy networking
- GPU acceleration

## 2) Process and Capability Model

Each process gets a capability bitset from the launcher. Default is deny-by-default.

Suggested M1 capability bits:

```text
CAP_IPC        = 1 << 0
CAP_NET        = 1 << 1
CAP_FS_READ    = 1 << 2
CAP_FS_WRITE   = 1 << 3
CAP_WINDOW     = 1 << 4
CAP_SHM        = 1 << 5
CAP_IRQ_BIND   = 1 << 6
```

Rules:

- MoonBit app does not access hardware or kernel objects directly.
- MoonBit app can only call host-imported APIs.
- Host APIs validate capability bits before forwarding IPC requests.

## 3) Syscall ABI (Kernel Boundary)

### 3.1 Universal syscall entry

```text
syscall(id, a0, a1, a2, a3, a4, a5) -> isize
```

- `>= 0`: success value
- `< 0`: `-errno`

### 3.2 M1 syscall IDs

| ID | Name | Arguments |
|---|---|---|
| `0x01` | `proc_spawn` | `a0=*ProcSpawnReq`, `a1=*ProcSpawnResp` |
| `0x02` | `proc_exit` | `a0=exit_code` |
| `0x03` | `cap_grant` | `a0=pid`, `a1=cap_bits_low`, `a2=cap_bits_high` |
| `0x04` | `cap_revoke` | `a0=pid`, `a1=cap_bits_low`, `a2=cap_bits_high` |
| `0x10` | `ipc_call` | `a0=*IpcCallReq` |
| `0x11` | `ipc_reply` | `a0=*IpcReplyReq` |
| `0x12` | `ipc_send` | `a0=*IpcSendReq` |
| `0x13` | `ipc_recv` | `a0=*IpcRecvReq` |
| `0x20` | `shm_create` | `a0=*ShmCreateReq`, `a1=*ShmCreateResp` |
| `0x21` | `shm_map` | `a0=*ShmMapReq` |
| `0x30` | `irq_bind` | `a0=*IrqBindReq` |
| `0x31` | `irq_ack` | `a0=irq_no` |

### 3.3 Rust layout definitions (`repr(C)`)

```rust
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KErrno {
  EPERM = 1,
  ENOENT = 2,
  EIO = 5,
  ENOMEM = 12,
  EACCES = 13,
  EINVAL = 22,
  ENOSYS = 38,
  ETIMEDOUT = 60,
  EPROTO = 71,
  EMSGSIZE = 90,
}

#[repr(C)]
pub struct ProcSpawnReq {
  pub image_ptr: u64,
  pub image_len: u32,
  pub flags: u32,
  pub name_ptr: u64,
  pub name_len: u32,
  pub reserved0: u32,
  pub cap_bits: u64,
}

#[repr(C)]
pub struct ProcSpawnResp {
  pub pid: u32,
  pub reserved0: u32,
}

#[repr(C)]
pub struct IpcCallReq {
  pub target_pid: u32,
  pub service_id: u16,
  pub method_id: u16,
  pub timeout_ms: u32,
  pub in_ptr: u64,
  pub in_len: u32,
  pub reserved0: u32,
  pub out_ptr: u64,
  pub out_cap: u32,
  pub flags: u32,
}

#[repr(C)]
pub struct IpcReplyReq {
  pub request_id: u64,
  pub status: i32,
  pub reserved0: u32,
  pub out_ptr: u64,
  pub out_len: u32,
  pub flags: u32,
}

#[repr(C)]
pub struct IpcSendReq {
  pub channel_id: u32,
  pub reserved0: u32,
  pub in_ptr: u64,
  pub in_len: u32,
  pub flags: u32,
}

#[repr(C)]
pub struct IpcRecvReq {
  pub channel_id: u32,
  pub timeout_ms: u32,
  pub out_ptr: u64,
  pub out_cap: u32,
  pub reserved0: u32,
  pub out_len_ptr: u64,   // writes actual bytes
  pub from_pid_ptr: u64,  // writes sender pid
}

#[repr(C)]
pub struct ShmCreateReq {
  pub size: u64,
  pub perms: u32,
  pub flags: u32,
}

#[repr(C)]
pub struct ShmCreateResp {
  pub handle: u32,
  pub reserved0: u32,
}

#[repr(C)]
pub struct ShmMapReq {
  pub handle: u32,
  pub target_pid: u32,
  pub offset: u64,
  pub length: u64,
  pub map_flags: u32,
  pub reserved0: u32,
  pub out_addr_ptr: u64,
}

#[repr(C)]
pub struct IrqBindReq {
  pub irq_no: u32,
  pub target_pid: u32,
  pub queue_depth: u16,
  pub flags: u16,
}
```

## 4) IPC Message Contract

For component-to-component messaging, use a fixed header + payload.

```rust
#[repr(C)]
pub struct IpcHeader {
  pub magic: u32,       // 0x4B48_5950 ('KHYP')
  pub version: u16,     // M1 = 1
  pub msg_type: u16,    // 1=req, 2=resp, 3=event, 4=error
  pub request_id: u64,
  pub service_id: u16,
  pub method_id: u16,
  pub flags: u32,
  pub status: i32,      // response/error only
  pub payload_len: u32,
}
```

M1 limits:

- Max payload per IPC frame: `64 KiB`
- Larger data uses `shm_create + shm_map` and sends handle metadata over IPC
- Default call timeout: `3000 ms`

## 5) Service IDs and Method IDs (M1)

```text
SERVICE_FS  = 1
SERVICE_NET = 2
SERVICE_WM  = 3
```

Suggested methods:

```text
FS_READ_FILE      = 1
FS_STAT           = 2

NET_HTTP_GET      = 1
NET_DNS_RESOLVE   = 2

WM_PRESENT_TEXT   = 1
WM_BLIT_RGBA      = 2
```

Payload encoding in M1: UTF-8 JSON bytes.

## 6) MoonBit Interface Definitions (Host FFI + SDK)

The MoonBit app side uses host-imported functions. Raw imports stay narrow and numeric; friendly wrappers can be built on top.

### 6.1 Raw host imports (`khy_sys` module)

```moonbit
// Capability probe: returns 1 if granted, otherwise 0.
fn cap_check(cap_bit : Int) -> Int = "khy_sys" "cap_check"

// Synchronous IPC call.
// Returns >= 0 on success, or negative errno.
fn ipc_call(
  service_id : Int,
  method_id : Int,
  req_ptr : Int,
  req_len : Int,
  resp_ptr : Int,
  resp_cap : Int,
  timeout_ms : Int
) -> Int = "khy_sys" "ipc_call"

// Query response metadata from last ipc_call.
fn ipc_last_len() -> Int = "khy_sys" "ipc_last_len"
fn ipc_last_status() -> Int = "khy_sys" "ipc_last_status"

// Async send/recv channel APIs.
fn ipc_send(channel_id : Int, msg_ptr : Int, msg_len : Int, flags : Int) -> Int = "khy_sys" "ipc_send"
fn ipc_recv(channel_id : Int, out_ptr : Int, out_cap : Int, timeout_ms : Int) -> Int = "khy_sys" "ipc_recv"

// Shared memory APIs.
fn shm_create(size : Int, perms : Int) -> Int = "khy_sys" "shm_create"
fn shm_map(handle : Int, offset : Int, length : Int) -> Int = "khy_sys" "shm_map"
```

### 6.2 Suggested MoonBit SDK surface (app-facing)

```moonbit
pub let CAP_NET = 1 << 1
pub let CAP_FS_READ = 1 << 2
pub let CAP_WINDOW = 1 << 4

pub let SERVICE_NET = 2
pub let NET_HTTP_GET = 1

pub fn has_capability(cap : Int) -> Bool {
  cap_check(cap) == 1
}

// High-level wrapper contract:
// - input/output are UTF-8 JSON bytes
// - ipc_call return value is status (0 success, <0 -errno)
// - actual response length comes from ipc_last_len()
pub fn call_json(
  service_id : Int,
  method_id : Int,
  request_json : Bytes,
  timeout_ms : Int
) -> (Int, Bytes)
```

### 6.3 Weather app call example (MoonBit)

```moonbit
pub fn fetch_weather() -> (Int, Bytes) {
  if !has_capability(CAP_NET) {
    return (-13, b"{\"error\":\"CAP_NET required\"}")
  }

  // Example payload in UTF-8 JSON bytes.
  let req = b"{\"city\":\"shanghai\"}"
  call_json(SERVICE_NET, NET_HTTP_GET, req, 3000)
}
```

## 7) End-to-End Flow (M1)

1. Launcher starts weather app with `CAP_IPC | CAP_NET | CAP_WINDOW`.
2. MoonBit app calls `call_json(SERVICE_NET, NET_HTTP_GET, ...)`.
3. Host runtime validates capabilities, builds `IpcHeader`, performs `ipc_call`.
4. `netd` executes DNS + HTTP GET and replies with JSON payload.
5. Runtime returns bytes to MoonBit app.
6. App renders text via `SERVICE_WM / WM_PRESENT_TEXT` or prints to CLI.

## 8) Compatibility Notes

- Keep all IDs (`service_id`, `method_id`, syscall numbers) stable once published.
- Add new methods by extension, never by reusing old IDs.
- If payload format changes, bump `IpcHeader.version` and keep backward parser for one major release.
- Current host adapter (`backend/src/services/wasm-sandbox/khySysHost.js`) expects `ipc_call(req_ptr, req_len, resp_ptr, resp_cap, ...)` in `u32 ptr/len` ABI with linear memory.
- MoonBit `wasm-gc` builds may pass non-pointer `externref` byte objects for `Bytes` FFI parameters. In current repo, this mismatch is rejected as `-EPROTO` with an explicit ABI mismatch error.
- `app register --abi numeric-v1` now fails fast when module imports `khy_sys.ipc_call` but does not export the expected memory symbol (to avoid late runtime failures).
- `app register` now validates import compatibility up front. Unsupported imports are rejected early; current host supports `khy_sys.*` (`cap_check`, `ipc_call`, `ipc_last_len`, `ipc_last_status`, `shm_create`, `shm_map`) and `spectest.print_char`.
- `app register --abi string-v2|json-v2` now validates required ABI exports (`memory`, `alloc`, optional `free`) and currently only accepts `return-mode=i64-ptr-len`.
- `wasmAppService.runFunction(..., abi=numeric-v1)` now performs a precheck: if module imports `khy_sys.ipc_call` but does not export expected linear memory, call is rejected before executing the target export.
- For reliable in-process `khy_sys.ipc_call` execution in current host, use modules that expose pointer-compatible memory ABI.

## 9) Local Simulation (Current Repo)

The repository now provides a host-side loopback transport for M1 IPC simulation.

Register a WASM app with explicit capabilities:

```bash
khy app register weather \
  --runtime wasm \
  --wasm /abs/path/weather.wasm \
  --abi json-v2 \
  --export run \
  --caps ipc,net
```

Call loopback NET service directly (without real kernel/net driver):

```bash
khy app ipc weather net http_get --json '{"city":"shanghai"}'
```

You can also use numeric IDs:

```bash
khy app ipc weather 2 1 --json '{"city":"beijing"}'
```
