cubical-transport-hott-lean4/native/cubical/WASM.md
Maximus Gorog 19928d040a
Some checks failed
Lean Action CI / build (push) Has been cancelled
REL2 universe stratification + topolei naming cleanup + Rust ABI v4
Two structural changes landed together as one coherent body of work.

## 1. Engine is name-clean from higher-order projects

The engine no longer carries "topolei" in its own naming surface.
Higher-order projects depend on the engine, not vice versa, so the
engine should be self-named.

  topolei-cubical (Cargo)            → cubical-transport
  libtopolei_cubical.a               → libcubical_transport.a
  topolei_cubical.h                  → cubical_transport.h
  TOPOLEI_FFI_ABI_VERSION            → CUBICAL_TRANSPORT_ABI_VERSION
  topolei_cubical_*  (14 FFI fns)    → cubical_transport_*
  topolei_shim_*     (9 shim fns)    → cubical_transport_shim_*

Inter-repo references describing topolei as a downstream consumer
(README, KERNEL_BOUNDARY.md, INDUCTIVE_TYPES.md, etc.) are preserved
as legitimate dependency-direction descriptions.

## 2. Universe-stratified, dependently-typed CType

  CType : ULevel → Type (genuinely indexed inductive)

with dependent pi/sigma carrying a binder name, a lift constructor
for cumulativity, and parameter lists of Σ-packaged types.

Per CCHM rules:
  · univ ℓ        : CType (ℓ.succ)
  · pi/sigma      : CType (max ℓ_A ℓ_B), with named binder
  · path A        : at A's level
  · glue T A      : T and A at same level
  · ind           : at user-chosen level (heterogeneous-level params)
  · interval      : CType .zero
  · lift          : CType (ℓ.succ), data-preserving

Every existing engine module cascades through {ℓ : ULevel} implicits
on functions/theorems, pi/sigma binder updates, and Σ-packaged params
lists.  CTerm stays un-indexed (universe lives on CType).

## 3. Substrate machinery for the cascade

  Universe.lean — ULevel inductive + max algebra (assoc, comm, etc.),
                  all theorems proven structurally.

  Syntax.lean — adds SkeletalCType enum + CType.skeleton level-erasure
                projection + per-constructor skeleton_* simp lemmas +
                CType.ind_skeleton_ne_pi disjointness lemma.  Used to
                discharge cross-level HEq cases in TransportLaws/CompLaws
                without invoking K.

## 4. Rust ABI v3 → v4

Lean 4 keeps implicit {ℓ : ULevel} parameters at runtime as constructor
fields, in declaration order interleaved with explicit args (verified
via probeLayout instrumentation).  Layout for level-bearing constructors
documented in cubical_transport.h §"v4 layout tables".

  CType.pi      : 5 fields — [ℓ_d, ℓ_c, var, A, B]
  CType.path    : 4 fields — [ℓ, A, a, b]
  CType.glue    : 9 fields — [ℓ, φ, T, f, fInv, sec, ret, coh, A]
  CType.ind     : 3 fields — [ℓ, S, params]
  CType.lift    : 2 fields — [ℓ, A]
  CTerm.transp  : 5 fields — [i, ℓ, A, φ, t]   (i precedes ℓ)
  CVal.vCompFun : 9 fields — [ℓ_d, ℓ_c, env, i, dom, cod, φ, u, t]
  ... etc

All Rust marshalling (value.rs, eval.rs, transport.rs, composition.rs,
glue.rs, beta.rs, dim_absent.rs, readback.rs, subst.rs, ffi.rs, tags.rs)
updated to match.

## Discipline

  · Zero sorry in CubicalTransport/.
  · Zero noncomputable instances; zero Classical.propDecidable shortcuts.
  · No CType.level projection (the level lives in the inductive's index).
  · No parallel CTypeU type.
  · No stub substrate types (def Ω := CType.univ etc.).
  · Tests restored to full coverage (EvalTest 623 lines, FFITest 351
    lines with classifier-runtime tests intact).

## Verification

  cd cubical-transport-hott-lean4
  lake build                 # 48 jobs OK
  ./.lake/build/bin/cubical-test
                             # ── 49/49 passed ──
                             # ── 46/46 properties passed ──
                             # PASS: all smoke + property tests

  cd ../topolei
  lake build                 # 90 jobs OK
  ./.lake/build/bin/probe-test
                             # ── 7/7 probes passed ──
                             # PASS: GPU output matches Lean ShaderSemantic

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 00:21:14 -06:00

9.3 KiB
Raw Blame History

WASM.md — Wasm Target Integration

Phase D.wasm deliverable, 2026-04-24. Describes the cubical-transport crate's wasm32 build, its import/export contract, and how to embed it in a Lean-wasm composite artifact.


1. Build

cd native/cubical
cargo build --release --target wasm32-unknown-unknown
# Output: target/wasm32-unknown-unknown/release/cubical_transport.wasm

Requirements: the wasm32-unknown-unknown Rust target installed (rustup target add wasm32-unknown-unknown). No emscripten, no C toolchain needed for the Rust side — build.rs skips the shim.c compilation on wasm targets.

Current artifact size: ~20KB (no_std, full cubical evaluator).


2. Wasm ABI contract

The compiled module has these wasm-level imports and exports.

2.1 Exports (14 + memory + globals)

All 14 cubical-evaluator entry points plus wasm runtime bookkeeping:

Export Role
cubical_transport_eval Main evaluator: (CEnv, CTerm) → CVal
cubical_transport_vapp Function application
cubical_transport_vpapp Dim application
cubical_transport_vtransp Value-level transport
cubical_transport_vhcomp Homogeneous composition
cubical_transport_vcomp_term Hetero comp at term level
cubical_transport_vcompn_term Multi-clause comp
cubical_transport_vfst Σ first projection
cubical_transport_vsnd Σ second projection
cubical_transport_readback NbE reification
cubical_transport_readback_neu Neutral reification
cubical_transport_step readback ∘ eval .nil (Option B)
cubical_transport_dimexpr_normalize DimExpr canonicalisation
cubical_transport_face_normalize FaceFormula canonicalisation
memory Linear memory (shared with host)
__heap_base Address of first allocatable byte
__data_end Address of end of static data

Every cubical_transport_* export takes lean_object* (i.e. i32 wasm pointers) and returns lean_object*. The Lean object layout on wasm32 is:

struct lean_object {
    uint32_t m_rc;         // offset 0, 4 bytes
    uint16_t m_cs_sz;      // offset 4, 2 bytes
    uint8_t  m_other;      // offset 6, 1 byte   (num_objs for ctors)
    uint8_t  m_tag;        // offset 7, 1 byte
    // fields follow at offset 8; each pointer is 4 bytes on wasm32.
};

Scalar encoding: (value << 1) | 1. Heap objects have low bit 0.

2.2 Imports (in env namespace)

Eight shim functions the host must provide. These are Lean's inline-only helpers (lean_obj_tag, lean_ctor_get, etc.) exposed as real wasm imports:

Import Lean equivalent Purpose
cubical_transport_shim_obj_tag lean_obj_tag (o: i32) → i32 — constructor tag
cubical_transport_shim_ctor_get lean_ctor_get (o, i) → field pointer
cubical_transport_shim_ctor_set lean_ctor_set (o, i, v) → ()
cubical_transport_shim_alloc_ctor lean_alloc_ctor (tag, num_objs, scalar_sz) → new ctor
cubical_transport_shim_inc lean_inc refcount +1 (no-op on scalars)
cubical_transport_shim_dec lean_dec refcount 1
cubical_transport_shim_string_eq lean_string_eq (a, b) → bool
cubical_transport_shim_mk_string lean_mk_string Allocate Lean String from null-terminated C string

A Lean-wasm runtime must satisfy these imports. Two paths:

  1. Natively via C compilation. Compile shim.c alongside the Lean runtime in the wasm build (emscripten, say, or clang wasm32). The shim delegates to Lean's inlines; Lean's own runtime provides lean_alloc_small, lean_dec_ref_cold, etc.

  2. Via runtime binding. When instantiating the wasm module in a host (Node/browser/wasmtime), provide JS/Rust implementations of the 8 shim functions that interact with the shared wasm memory.

The harness at test/wasm_harness.mjs uses approach (2) with a minimal JS implementation — enough to demonstrate the module works.


3. Integration with a Lean-wasm composite artifact

The cells-spec vision is a Lean 4 wasm binary with topolei cubical support, running in a browser or wasmtime. The composite artifact comprises:

lean-wasm-composite/
├── lean-kernel.wasm              # Lean's own runtime compiled to wasm
├── cubical-transport.wasm          # This crate
├── glue.js / glue.wasm           # Import/export wiring
└── main.{js,html}                # Entry point

Two approaches to wiring them:

Instantiate both modules in the host (JS or another wasm runtime). The host holds a single WebAssembly.Memory instance and shares it between modules. Imports on cubical-transport resolve to functions exported by lean-kernel.wasm (the shim wrappers that call Lean's inlines).

Advantages: no linker required, modules are swappable, each can be updated independently. Clean separation of concerns.

Pseudo-code:

const lean     = await WebAssembly.instantiate(leanKernelWasm, {});
const topolei  = await WebAssembly.instantiate(topoleiCubicalWasm, {
  env: {
    cubical_transport_shim_obj_tag:     lean.instance.exports.cubical_transport_shim_obj_tag,
    cubical_transport_shim_ctor_get:    lean.instance.exports.cubical_transport_shim_ctor_get,
    // ... etc
  },
});
// Share memory: either use lean's memory (topolei declares memory import)
// or bridge via copy routines.

Link both modules at build time using wasm-ld --whole-archive or similar. Produces a single wasm file. Simpler deployment but less flexible for updates.

Requirements: both modules must be compiled with compatible calling conventions and share a single memory segment. wasm-ld handles this automatically for reactors (non-main wasm).


4. Verification harness

test/wasm_harness.mjs is a standalone Node.js script that loads the wasm module and runs 10 correctness tests:

  • Scalar passthrough (4 tests) — .zero, .one, .bot, .top are tagged-pointer scalars; their normalizers return identity.
  • Heap reductions (5 tests) — .inv .zero → .one, .inv .one → .zero, double-negation, face absorption (.meet .top .bot → .bot), recursive .meet normalization.
  • Structural inspection (1 test) — verifies a .meet of two .invs normalizes to a .meet of scalars (Rust traverses and allocates correctly).

Run:

cd native/cubical
node test/wasm_harness.mjs

Expected: ── 10 / 10 tests passed ──.

The harness provides its own minimal allocator (bump-pointer from __heap_base) and Lean-layout helpers — it is NOT a full Lean runtime, but it is sufficient to exercise every Rust code path that doesn't require strings.


5. Limitations and TODOs

5.1 String handling

The harness does not implement lean_string_eq or lean_mk_string. Tests that exercise env lookup (eval .nil (.var "x")) or string messages would require a full String-object implementation in the host. A real Lean-wasm runtime provides these automatically.

5.2 Memory growth

The wasm module declares an initial memory size. Complex computations may exceed it; the host should monitor memory.buffer.byteLength and call memory.grow() as needed. cubical_transport_shim_alloc_ctor's allocator implementation decides when to grow.

5.3 Pointer size (wasm32 vs wasm64)

Currently targeting wasm32-unknown-unknown — 32-bit pointers. A future wasm64 build would change the field offset calculations in the harness (FIELD_SIZE = 8 instead of 4). The Rust code adapts automatically via core::mem::size_of::<*const _>(), but consumers of WASM.md should be aware.

5.4 Refcount correctness

The JS harness stubs cubical_transport_shim_inc / _dec as no-ops (we don't GC). This is fine for one-shot test runs but would leak in a long-running application. A real host must implement refcounting.


  • FFI_DESIGN.md — full C ABI contract (includes wasm as a target).
  • FFI_COMPLETENESS.md — per-function axiom-coverage audit.
  • KERNEL_BOUNDARY.md — what this delivers vs. Lean kernel limits.
  • native/cubical/README.md — crate-level build + development notes.