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 guest-visible behavior and supervisor decisions; TypeScript executes the host effects needed to realize those decisions.

Kernel Supervisor 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. A status remains the low-level point where a thread step returns to TypeScript; it is not a grant of Linux policy authority to the runtime.

The versioned supervisor ABI adds structured thread events, host operations, host completions, resume records, decision records, debug snapshots, and deterministic log metadata. The control loop is:

sequenceDiagram
  participant Thread as thread worker
  participant Executor as TypeScript host executor
  participant Supervisor as kernel supervisor
  participant Host as browser / Node host

  Thread->>Executor: status and synchronization data
  Executor->>Supervisor: ThreadEvent
  Supervisor-->>Executor: HostOp or selected ResumeThread
  Executor->>Host: worker, memory, I/O, network, or timer effect
  Host-->>Executor: result
  Executor->>Supervisor: HostCompletion
  Supervisor-->>Executor: updated decision or next operation
  Executor->>Thread: resume only when selected

The ABI is additive so a responsibility can move behind an explicit record or decision export without copying constants or policy into TypeScript.

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 host executor
  participant Thread as thread worker
  participant Supervisor as kernel wasm / supervisor

  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 / host lifecycle init
  Owner->>Thread: prepare/init memory and kernel state
  Thread->>Supervisor: execute with budget
  Supervisor-->>Thread: status, exit, syscall, or blocking state
  Thread-->>Owner: ThreadWorkerStatusMessage
  Owner->>Supervisor: ThreadEvent
  Supervisor-->>Owner: HostOp or resume decision
  Owner->>Supervisor: HostCompletion after host effect
  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/>host executor and state transport"]
  Pool["worker pool"]
  ThreadA["thread worker"]
  ThreadB["thread worker"]
  Host["host bridges<br/>stdio, network, filesystem RPC"]
  Supervisor["kernel supervisor<br/>Linux-shaped decision authority"]

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

The runtime implementation reflects this topology through separate roles for kernel-worker state, process host execution, thread execution, host I/O, and shared-state transport. Runtime queues may track workers that are running, parked, or ready for a host action, but runnable-thread authority belongs to the supervisor.

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 submits the relevant event to the supervisor, executes requested host effects, reports completion, and delivers supervisor-selected resumes without making the kernel depend on browser worker APIs.

flowchart TB
  ProcessOwner["process owner host executor"]
  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<br/>supervisor"]
  Status["status + sync transport"]
  Decision["HostOp / ResumeThread"]

  ProcessOwner --> ThreadA
  ProcessOwner --> ThreadB
  ThreadA <--> SharedMemory
  ThreadB <--> SharedMemory
  ThreadA --> KernelExports
  ThreadB --> KernelExports
  KernelExports --> Status
  Status --> ProcessOwner
  ProcessOwner --> KernelExports
  KernelExports --> Decision
  Decision --> 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 --> Reported: status / ThreadEvent
  Reported --> HostEffect: supervisor HostOp
  HostEffect --> Reported: HostCompletion
  Reported --> Ready: supervisor selects continuation
  Reported --> Blocked: supervisor records blocked state
  Blocked --> Ready: supervisor selects resume
  Running --> Exited: exit status
  Running --> Failed: error
  Exited --> [*]
  Failed --> [*]

The runtime receives enough structured state to transport the event and perform the selected host effect. The supervisor decides whether execution continues, a blocked syscall resumes, child state becomes visible, or lifecycle cleanup is required.

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 supervisor and host handoff protocol:

  • kernel exports describe spawn kind and pending handoff state,
  • supervisor decisions define lifecycle and parent-visible ordering,
  • runtime worker modules execute requested memory and worker operations,
  • host completions return success or failure to the supervisor,
  • tests cover vfork/execve success, failure, parent restoration, and fd/OFD isolation cases.
flowchart LR
  Parent["parent process host executor"]
  Status["STATUS_VFORK_WAIT<br/>STATUS_EXECVE_SPAWN<br/>STATUS_CLONE_SPAWN"]
  Supervisor["kernel supervisor<br/>lifecycle decision"]
  HostOp["worker / memory HostOp"]
  KernelWorker["kernel worker host effect"]
  Child["child process owner"]
  Thread["thread worker init"]

  Parent --> Status
  Status --> Supervisor
  Supervisor --> HostOp
  HostOp --> KernelWorker
  HostOp --> Child
  Child --> Thread
  KernelWorker --> Supervisor
  Child --> Supervisor

The handoff protocol has to cover parent suspension and supervisor-selected resume, child creation, fd/OFD and pipe visibility, child-exit publication, and failure rollback. Tests should cover the kernel decision and the retained host effect separately before using a workload as composition evidence.