Skip to content

WASM Engine

The core binary operations (SHA-1, zlib via libdeflate, packfiles, deltas) run in Zig-compiled WASM modules with SIMD128 acceleration. Two modules serve different use cases:

Modulenpm EntrySizeUse case
Server (gitmode.wasm)import { WasmEngine } from "gitmode/server"865KBFull git server — all operations including libgit2
Client (gitmode-core.wasm)import { WasmEngineCore } from "gitmode/client"83KBLightweight client — SHA-1, zlib, delta only (no packfile/libgit2)

The server module is used internally by createHandler() / RepoStore — you don’t import it directly unless building custom server logic. The client module is standalone and runs anywhere WASM runs (browsers, Node.js, Workers, Deno).

Zig 0.15 (ReleaseSmall)
→ wasm-metadce (remove unused exports)
→ wasm-opt -Oz (size optimization)
→ 865KB server / 83KB core

Build commands:

Terminal window
pnpm run build:wasm # Server module only
pnpm run build:wasm-core # Core module only
pnpm run build:wasm-all # Both modules

The WASM module exports 29 functions organized by category:

ExportDescription
memoryLinear memory
allocAllocate bytes in WASM heap
resetHeapReset heap (arena allocator)
getHeapUsedGet current heap usage
heapSaveSave heap offset (returns checkpoint)
heapRestoreRestore heap to checkpoint (frees allocations after it)
ExportDescription
sha1_hashHash raw bytes
sha1_hash_objectHash with git object header (e.g., blob 5\0hello)
ExportDescription
zlib_inflateDecompress zlib data
zlib_inflate_trackedDecompress with size tracking
zlib_deflateCompress data
ExportDescription
parse_object_headerParse type size\0 header
serialize_objectCreate object with header
parse_treeParse tree entries
ExportDescription
pack_parse_headerParse PACK header (magic, version, count)
pack_parse_entry_headerParse entry type and size
pack_buildBuild a packfile from objects
ExportDescription
delta_applyApply delta instructions to base object
delta_createCreate delta between two objects
ExportDescription
pktline_encodeEncode data with 4-byte hex length prefix
pktline_decodeDecode pkt-line framed data
ExportDescription
simd_memeqlSIMD-accelerated memory comparison
simd_memchrSIMD-accelerated byte search
ExportDescription
checkout_commitMaterialize a commit’s tree into R2 worktree for browseable file access
ExportDescription
libgit2_initInitialize libgit2
libgit2_shutdownShut down libgit2
libgit2_diffCompute diff between trees
libgit2_revwalkWalk commit history
libgit2_blameLine-level blame

The server WASM binary is 865KB after optimization:

  1. wasm-metadce — Dead code elimination based on an export graph. Only 28 exports are kept; all unreachable functions are removed. This reduced function count from ~3000+ to 1272.

  2. wasm-opt -Oz — Binaryen’s aggressive size optimizer. Applies code folding, constant propagation, dead argument elimination, and instruction combining.

Both tools require feature flags for the WASM features used:

Terminal window
--enable-simd --enable-bulk-memory --enable-nontrapping-float-to-int

The WASM source lives in wasm/ with two build targets:

  • zig build wasm — server module (main.zig, includes libgit2)
  • zig build core — core module (main_core.zig, SHA-1/zlib/delta only)

Key build settings:

  • entry = .disabled — no _start entry point (library mode)
  • rdynamic = true — export all public functions (required in Zig 0.15)
  • stack_size = 4MB — sufficient for deep tree recursion and large packfiles

Zlib operations use vendored libdeflate 1.25 (wasm/vendor/libdeflate/), imported via Zig’s @cImport. This replaced Zig’s std.compress.flate which crashed on certain inputs. libdeflate is purpose-built for in-memory inflate/deflate and consistently faster than general-purpose zlib implementations.

The JS↔WASM boundary avoids unnecessary copies using two techniques:

viewBytes() returns a Uint8Array subarray directly into WebAssembly.Memory — zero copy. The view is valid until the next WASM alloc, reset, or memory grow. Used in packfile building and compute workers where the deflate result is immediately consumed:

// Zero-copy: deflate result stays in WASM memory
const compressedView = wasm.zlibDeflateView(content);
entry.set(compressedView, headerLen); // copies once into final buffer

hashAndDeflate() performs SHA-1 hashing and zlib deflation in a single heap session. Content is written to WASM memory once; the SHA-1 is computed, then the header + content are assembled in WASM memory (using an intra-WASM copy, not a JS roundtrip) and deflated. One resetHeap() instead of two, one content write instead of two.

heapSave() and heapRestore() allow multiple results to coexist in WASM memory without a full resetHeap(). This enables pipeline patterns where intermediate results stay in WASM memory and are consumed by the next operation without copying out to JS.