wasmedge-hs-0.17.1.0: docs/ARCHITECTURE.md
# Architecture
## Overview
`wasmedge-hs` is a two-layer FFI package:
```
┌─────────────────────────────────────────────────┐
│ Public API layer (WasmEdge.*) │
│ • Idiomatic Haskell types (WasmVal, Configure) │
│ • Bracket-style resource management │
│ • WasmResult error monad │
├─────────────────────────────────────────────────┤
│ Internal layer (WasmEdge.Internal.*) │
│ • OwnedCtx / BorrowedCtx ownership model │
│ • Storable instances for C struct layout │
│ • Raw FFI imports (WasmEdge.Internal.FFI.*) │
├─────────────────────────────────────────────────┤
│ C shim layer (cbits/) │
│ • wasmedge_hs_shims.{h,c} — out-param wrappers │
│ • wasmedge_hs_int128.c — V128 helpers │
│ Compiled directly by GHC │
├─────────────────────────────────────────────────┤
│ WasmEdge C API (libwasmedge) │
└─────────────────────────────────────────────────┘
```
## Why Two Layers?
### Layer 1: Raw FFI (`WasmEdge.Internal.FFI.*`)
These modules contain only `foreign import` declarations with `Ptr` types.
They are intentionally thin — no business logic, no error handling.
This makes them easy to audit when the WasmEdge C API changes.
**Key rule**: Every function in `WasmEdge.Internal.FFI.*` must have a
1-to-1 correspondence with a C function signature in the shim header.
### Layer 2: Public API (`WasmEdge.*`)
These modules provide idiomatic Haskell APIs. They:
- Convert between Haskell types (Text, ByteString, WasmVal) and C types
- Manage resource lifetimes via `OwnedCtx` and `ForeignPtr`
- Convert `WasmEdge_Result` to `WasmEdgeError` exceptions
- Provide `bracket`-style resource management
## Ownership Model
All WasmEdge contexts fall into two categories:
### Owned contexts (`OwnedCtx tag`)
- Wrapped in `ForeignPtr` with a C delete function as finalizer
- Created by `WasmEdge_*Create` functions
- Example: `Configure`, `VM`, `Store`, `ModuleInstance` (when created from AST)
### Borrowed contexts (`BorrowedCtx tag`)
- Raw `Ptr tag` — no finalizer, no ownership
- Obtained via query functions (e.g., `VMGetStoreContext`, `CallingFrameGetModuleInstance`)
- **Lifetime is guaranteed by the parent context**
- The caller MUST NOT call `delete*` on a borrowed context
### 0.18 Finalized Module Instances
In WasmEdge 0.18+, `WasmEdge_ExecutorInstantiate` returns a
**finalized** module instance that is owned by the **Store**, not the caller.
We model this as:
```haskell
-- After instantiation, ModuleInstance is borrowed from the Store
instantiateModule :: Executor -> Store -> ASTModule -> IO BorrowedModuleInstance
```
The caller must not delete a finalized module instance. Deleting the Store
deletes all finalized modules within it.
## C Shim Layer
### Why shims?
GHC's FFI can only call C functions that:
1. Return void, or
2. Return a scalar type (int, pointer, double, etc.)
WasmEdge's C API returns many structs by value: `WasmEdge_String`,
`WasmEdge_Value`, `WasmEdge_Result`, `WasmEdge_ValType`.
GHC **cannot** marshal struct-by-value return values. Our shims convert
each such function into a void function with an extra `out*` parameter.
### Naming convention
```
WasmEdge_StringCreateByCString(...) → C function (struct by value)
hs_WasmEdge_StringCreateByCString(out, ...) → shim (out-param)
c_StringCreateByCString → Haskell FFI import of the shim
```
### No `c2hs`, no `inline-c`, no `cpphs`
All C interop goes through:
1. The shim `.c` file (compiled by GHC directly via `c-sources:`)
2. `foreign import ccall` declarations in `.hs` files
3. `hsc2hs` for struct layout constants (`#size`, `#offset`, `#alignment`)
This means the build has **zero external codegen dependencies**. Only
`hsc2hs` (bundled with GHC) is needed.
## Module Hierarchy
```
WasmEdge ← umbrella re-export
Types ← Haskell types (WasmVal, ValType, ...)
Error ← WasmEdgeError exception type
Value ← WasmVal ↔ C encoding
Log ← logging config
Configure ← WasmEdge_ConfigureContext
Statistics ← WasmEdge_StatisticsContext
Store ← WasmEdge_StoreContext
Executor ← WasmEdge_ExecutorContext
Module ← WasmEdge_ModuleInstanceContext
Function ← WasmEdge_FunctionInstanceContext
Table ← WasmEdge_TableInstanceContext
Memory ← WasmEdge_MemoryInstanceContext
Global ← WasmEdge_GlobalInstanceContext
Tag ← WasmEdge_TagInstanceContext (0.18+)
VM ← WasmEdge_VMContext (high-level)
Async ← WasmEdge_Async
Compiler ← WasmEdge_CompilerContext
Plugin ← WasmEdge_PluginContext
Internal.Types ← phantom tags
Internal.Ownership ← OwnedCtx, BorrowedCtx
Internal.Storable ← Storable for C struct layout
Internal.Utils ← Text/String helpers, checkResult
Internal.FFI.Basic ← version, log, string, bytes, result
Internal.FFI.Value ← ValType, Value gen/get
Internal.FFI.Configure ← configure + statistics
Internal.FFI.AST ← ASTModule
Internal.FFI.Execution ← loader, validator, executor, store, async
Internal.FFI.Instance ← all instance contexts
Internal.FFI.VM ← VM
Internal.FFI.Plugin ← plugin
Internal.FFI.Compiler ← AOT compiler
Internal.FFI.Experimental ← experimental hooks
```
## Thread Safety
WasmEdge's "thread-safe" functions (those that can block, e.g., execution)
are imported with `safe` FFI. This allows the GHC RTS to run other Haskell
threads while the C function blocks.
"Unsafe" functions (those that return immediately, e.g., create/delete/query)
use `unsafe` FFI for performance.
**Rule**: Any FFI import that could block the OS thread uses `safe`.
Any FFI import that is guaranteed to return immediately uses `unsafe`.