Skip to content

Execution Architecture

This page describes how the current implementation creates a runtime, starts a guest process, and moves execution between workers and kernel WebAssembly.

Runtime Creation

Runtime.create receives kernel WebAssembly bytes and initializes a runtime instance. The current implementation precompiles the module, allocates a SharedArrayBuffer-backed page cache, creates a kernel worker, and sends an init-kernel request.

sequenceDiagram
  participant App as Application
  participant Runtime as Runtime.create
  participant KernelWorker as kernel worker
  participant KernelWasm as kernel wasm exports

  App->>Runtime: create({ wasmBytes, ramSize, networkBridge })
  Runtime->>Runtime: precompile WebAssembly module
  Runtime->>Runtime: allocate SharedArrayBuffer page cache
  Runtime->>KernelWorker: init-kernel(wasmModule, sabPageCache)
  KernelWorker->>KernelWasm: instantiate kernel environment
  KernelWasm-->>KernelWorker: initial KernelRuntimeState
  KernelWorker-->>Runtime: ready(kernelState)
  Runtime-->>App: Runtime instance

The runtime owns the JavaScript/TypeScript worker lifecycle. The kernel owns the guest-visible behavior once execution enters kernel exports.

Kernel ABI And Status Codes

The kernel exports status codes, syscall numbers, constants, and process/thread entry points through its WebAssembly ABI. The runtime reads those exports rather than maintaining an independent copy of the same contract.

The current ABI includes status categories for filesystem work, futex waits, pipe waits, epoll waits, vfork waits, exec-style process creation, clone-style thread or process creation, sleep, sockets, and JIT readiness. Runtime message contracts carry kernel runtime state alongside those statuses.

This makes the status boundary explicit: kernel execution stops with a status, and the runtime decides which browser-side orchestration step is needed next.

Process Startup

The SDK and runtime expose different levels of process startup.

  • The SDK accepts a command name, resolves it against a guest PATH, handles simple shebang fallback through /bin/sh, reads executable bytes, and calls the runtime.
  • The runtime accepts executable bytes and lower-level process options.
  • The worker layer prepares memory, filesystem state, process identity, stdio, and thread-worker execution.
sequenceDiagram
  participant App as Application
  participant SDK as Tidemark SDK
  participant Runtime as Runtime
  participant KRPC as kernel-worker RPC
  participant Owner as process owner
  participant Thread as thread worker
  participant Kernel as kernel wasm

  App->>SDK: run(command, args)
  SDK->>Runtime: stat/read filesystem paths
  Runtime->>KRPC: stat/read-file requests
  KRPC-->>Runtime: executable bytes
  SDK->>Runtime: spawn(executableBytes, argv, env, cwd)
  Runtime->>Owner: create process handle
  Owner->>KRPC: register process / resolve cwd / lifecycle init
  Owner->>Thread: prepare/init memory and kernel state
  Thread->>Kernel: execute with budget
  Kernel-->>Thread: status, exit, syscall, or blocking state
  Thread-->>Owner: ThreadWorkerStatusMessage
  Owner-->>Runtime: stdout/exit/error events
  Runtime-->>SDK: process result
  SDK-->>App: exit code and output

This sequence is intentionally more explicit than a single run call. The runtime has to coordinate browser workers, kernel state, guest memory, filesystem state, and host I/O.

Worker Topology

flowchart TB
  Runtime["Runtime instance"]
  KernelWorker["kernel worker<br/>canonical runtime-facing state"]
  Owner["process owner worker<br/>process lifecycle and scheduling"]
  Pool["worker pool"]
  ThreadA["thread worker"]
  ThreadB["thread worker"]
  Host["host bridges<br/>stdio, network, filesystem RPC"]

  Runtime --> KernelWorker
  Runtime --> Owner
  Runtime --> Host
  Owner --> Pool
  Pool --> ThreadA
  Pool --> ThreadB
  Owner <--> KernelWorker
  ThreadA <--> Owner
  ThreadB <--> Owner
  Owner <--> Host

The runtime implementation reflects this topology through separate roles for kernel-worker state, process ownership, scheduling, thread execution, host I/O, and shared-state synchronization. The important design point is that these roles remain explicit instead of being hidden behind one broad execution loop.

Thread Workers

Thread workers are the runtime substrate for guest thread execution. A thread-worker executes a single guest thread against shared process memory and returns explicit status and synchronization data to the process owner. The runtime can then coordinate blocking, resume, signals, fork/exec transitions, filesystem effects, and process lifecycle without making the kernel depend on browser worker APIs.

flowchart TB
  ProcessOwner["process owner worker"]
  SharedMemory["shared process memory<br/>WebAssembly.Memory / SharedArrayBuffer-backed state"]
  ThreadA["thread worker A<br/>guest thread step loop"]
  ThreadB["thread worker B<br/>guest thread step loop"]
  KernelExports["kernel wasm exports"]
  Status["status + sync effects<br/>fd/OFD, pipe, socket, memory writes"]

  ProcessOwner --> ThreadA
  ProcessOwner --> ThreadB
  ThreadA <--> SharedMemory
  ThreadB <--> SharedMemory
  ThreadA --> KernelExports
  ThreadB --> KernelExports
  KernelExports --> Status
  Status --> ProcessOwner

This design supports workloads that expect a threaded Linux userland substrate: language runtimes, compiler drivers, build tools, thread pools, futex waits, signal interruption, and child process orchestration. Those workloads exercise the substrate; they are not special cases in the runtime architecture.

Step And Status Loop

Thread workers receive prepare, init, and step messages. A step includes a budget and the kernel state required to continue. The thread worker returns status messages that can include register details, syscall number, kernel state, fd/OFD snapshots, pipe slots, socket snapshots, guest memory writes, kernel memory writes, sync effects, child-exit records, and blocking hints.

stateDiagram-v2
  [*] --> Prepared
  Prepared --> Ready: init
  Ready --> Running: step(budget)
  Running --> Ready: status with continuation
  Running --> Blocked: blocking status
  Blocked --> Running: resumeBlockedSyscall
  Running --> Exited: exit status
  Running --> Failed: error
  Exited --> [*]
  Failed --> [*]

The runtime receives enough structured state to decide whether to continue, resume a blocked syscall, publish state to the kernel worker, propagate child exit records, or tear down a process tree.

Fork, Vfork, And Execve

Fork-style operations are not only memory copies. They also involve fd/OFD ownership, pipe state, process identity, child-exit records, cwd and executable state, and worker readiness ordering.

The current implementation exposes this as an explicit process handoff protocol:

  • kernel exports describe spawn kind and pending handoff state,
  • runtime worker modules build or rehydrate child process owners,
  • kernel-worker messages resume vfork parents and synchronize shared state,
  • tests cover vfork/execve success, failure, parent restoration, and fd/OFD isolation cases.
flowchart LR
  Parent["parent process owner"]
  Status["STATUS_VFORK_WAIT<br/>STATUS_EXECVE_SPAWN<br/>STATUS_CLONE_SPAWN"]
  ForkModule["fork transition logic"]
  KernelShare["shared kernel-object sync"]
  KernelWorker["kernel worker lifecycle state"]
  Child["child process owner"]
  Thread["thread worker init"]

  Parent --> Status
  Status --> ForkModule
  ForkModule --> KernelShare
  KernelShare --> KernelWorker
  ForkModule --> Child
  Child --> Thread
  Child --> KernelWorker

The handoff protocol has to cover parent suspension and resume, child creation, fd/OFD visibility, pipe visibility, child-exit records, and failure rollback. Tests should cover successful transitions, failure restoration, descriptor isolation, pipe behavior, and parent-resume ordering.