Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Benchmark the portable kernel

Use harn bench portable to measure the canonical compiler, artifact decoder, and native execution kernel separately. The command runs pure executions with no host grants, checks that repeated compilation produces identical artifact bytes, checks that every iteration produces the same terminal value, and can distribute independent dispatches across operating-system threads.

Prepare an input

The benchmark accepts one JSON value as the entry function's input. For the package reducer in crates/harn-wasm/demo/package-root.harn, save this as /tmp/reducer-input.json:

{
  "state": {"count": 0, "history": [], "label": "portable"},
  "event": {"kind": "increment", "amount": 1}
}

Run a native benchmark

harn bench portable crates/harn-wasm/demo/package-root.harn \
  --entry reduce \
  --entry-kind function \
  --input /tmp/reducer-input.json \
  --compile-iterations 30 \
  --iterations 500 \
  --threads 4 \
  --json \
  --output /tmp/portable-kernel-benchmark.json

--threads measures separate executions that share one decoded, immutable program artifact. It does not split one execution across threads. Each worker gets isolated execution state and the command fails if any terminal value differs from the first result. Use --threads 1 when comparing instruction cost without scheduler contention, then repeat with representative thread counts to measure host throughput. The accepted range is 1 through 256; when there are fewer dispatch iterations than requested threads, the command starts one worker per iteration.

The command reports the first compile separately as the cold compile sample for that process, then compiles the same source repeatedly for steady-state statistics. Parallel dispatch workers wait at a barrier after they are ready; batch wall time and throughput begin when the coordinator releases that barrier. Per-dispatch samples still measure each independent kernel call.

Portable receipts deliberately replace the full-VM profiler for this command. harn bench portable rejects --profile, --profile-json, HARN_PROFILE, and HARN_PROFILE_JSON instead of accepting flags whose data would describe a different execution boundary.

The receipt uses schema harn.portable_kernel.benchmark.v1; its checked JSON Schema is spec/schemas/portable-kernel-benchmark.v1.schema.json. It is generated with make gen-portable-benchmark-schema from the kernel-owned Rust receipt contract and limits; make check-portable-benchmark-schema prevents a consumer-facing file from becoming a second owner. The closed Rust receipt type, validation limits, statistics, provenance, and terminal digest live in harn-kernel. The CLI serializes that type directly, and the browser worker must round-trip its measurements through the same Wasm-backed type before publishing them. A schema parity test compares every object field set and validates both target variants. The receipt records:

  • target and build/runtime provenance, including artifact, semantic ABI, and opcode ABI versions or fingerprints;
  • source path, entry name, and kind;
  • artifact byte length and digest;
  • sample and worker counts;
  • initialization latency when the target has an initialization boundary;
  • first and repeated compile statistics;
  • decode statistics when decode is independently measurable;
  • first and repeated dispatch statistics, batch wall time, and throughput; and
  • a digest of the terminal value.

Repeated statistics use the kernel's canonical R-7 percentiles and population standard deviation. Their JSON fields are iterations, min_ms, mean_ms, p50_ms, p95_ms, max_ms, stddev_ms, and total_ms; receipt envelope fields use camelCase.

The host measures these durations with a monotonic clock. Clock access is not granted to the Harn program and is not embedded in the artifact.

Measure the browser path

First produce the release adapter and run its worker conformance checks:

make wasm-check

Then start the local server with make wasm-demo and open http://127.0.0.1:8765/demo/benchmark.html. A dedicated module worker records Wasm initialization, first and repeated compilation, and 500 calls through the same generated compilePackage and start exports used by the reducer worker. The page only presents the worker's result. Read the JSON receipt from the page or window.__HARN_BENCHMARK__.

Every repeated compile must produce the same artifact bytes and digest. Every start receives the same immutable artifact and JSON input and must produce the same terminal value. The worker sends timing samples through the Wasm adapter's bounded projection of harn_kernel::BenchmarkStatistics, so native and browser receipts use one percentile and standard-deviation definition.

Browser instances run in dedicated Web Workers. To measure parallel browser throughput, create several workers and give each worker the same artifact bytes and independent input state. The portable kernel does not require SharedArrayBuffer, Wasm atomics, JavaScript Promise Integration, or Wasm threads.

Interpret the numbers

Do not combine initialization, compile, decode, and dispatch into one latency claim. Browser initialization is a deployment cost; first compilation is a cold frontend cost; repeated compilation shows frontend steady state.

Target boundaries differ. Native dispatch calls the kernel with an already decoded ProgramArtifact, DataValue, and GrantSet; native decode is reported separately. Browser dispatch measures the public start(artifactBytes, inputJson, grantsJson) adapter boundary, so it includes artifact decoding, JSON/grant adaptation, kernel execution, and result projection. Treat it as adapter-start latency, not as an exact comparison with decoded native dispatch.

Compare like-for-like release builds on the same machine and browser, and preserve the receipt with the revision under test. Measurements from an earlier candidate were removed after the compiler/executor changed; publish new numbers only after rebuilding and exercising the final candidate.

Choose the right profiling surface

harn bench portable measures the compiler/artifact/kernel boundary. It does not emit full-VM phase profiles, user timing spans, provider timings, or tool timings.

  • Use harn bench <file> --profile or --profile-json to aggregate full native VM categories over repeated executions.
  • Use harn time run <file> to attribute one full run to parse, typecheck, bytecode compilation, setup, main execution, and module work.
  • Use std/timing to add named application spans to a hostful Harn run and export them through the normal profile and OTel paths.
  • Use harn bench replay for deterministic transcript and permission-fidelity measurements. It measures a different contract from portable dispatch.

Keeping these surfaces separate prevents host observation policy from becoming part of portable execution semantics.

Measured cutover snapshot

These measurements are evidence for this implementation boundary, not a performance promise. They were collected on 2026-08-02 on an Apple M5 Pro (18 logical CPUs, 48 GiB), macOS 26.5.1, Chrome 150.0.7871.187, and wasm-pack 0.15.0. Both adapters used release builds; their measured boundaries still differ as described above.

Release browser moduleRaw Wasmgzip -9SHA-256
Portable kernel browser module3,505,267 bytes1,231,341 bytesd1618ef72d386dbc738ec4693118044d87baad9e0ced2d6d423e6f74a24e457d

The shared Unicode regex, secret catalog, hashing, package compiler, and safety machinery add 1,120,115 raw bytes and 407,484 gzip bytes over the earlier pure reducer baseline. No file, process, network, clock, randomness, or model import appears in the module; make wasm-audit-imports enforces that boundary.

One fresh dedicated-worker receipt for the generated two-module package reported:

Browser measurementObserved value
Wasm initialization57.1 ms
First package compile827.3 ms
Repeated package compile p50 / p9542.05 / 2,476.25 ms
First execute/start1,480.1 ms
Repeated execute/start p50 / p954.10 / 35.46 ms
500-call batch wall time10,916.6 ms
Batch throughput45.8 starts/s

That manual Chrome receipt overlapped a CPU-heavy release build; its large compile and dispatch outliers are an observed contention result, not a clean latency baseline. A native release receipt collected after the build, for the same source, input, 30 repeated compiles, 500 dispatches, and four host threads, reported 3.526 ms first compile, 2.021 / 2.379 ms repeated compile p50 / p95, 1.141 / 1.428 ms decode p50 / p95, 0.0053 / 0.0077 ms dispatch p50 / p95, and 290,114 dispatches/s. Both adapters produced the exact 2,256-byte artifact with digest 42765c0eb8bd02a0ed15435f19743f838c0cfc40566647448660425c6f693c5a. The matching bytes are parity evidence; the timing difference reflects distinct adapter boundaries and browser contention.

The browser start(artifactBytes, inputJson, grantsJson) measurement includes artifact decoding, validation, JSON conversion, grant adaptation, execution, and result projection on every call. Preserve complete receipts and repeat trials before using these observations for optimization decisions.