Skip to content

wasm: lower the conveyor belt pass (queue half landed in d0434347) #922

Description

@bpowers

Sub-issue of #884. The queue half landed in d043434 (engine: lower the queue side-table pass to wasm); this is the conveyor half, and it is much the larger of the two.

Depends on the wasm runtime error channel (filed separately): the belt pass can raise ConveyorTransitTooLong at runtime, and the blob currently cannot report it.

Current state

wasmgen::compile_datamodel_to_artifact (src/wasmgen/module.rs:162) rejects any model with a conveyor stock up front:

if crate::conveyor_compile::project_has_conveyor(datamodel, model_name) {
    return Err(WasmGenError::Unsupported(...));
}

A queue-only model routes through queue_compile::compile_sim and is lowered natively. A conveyor model never reaches the dispatch.

What has to be lowered

The belt runtime is src/conveyor.rs; the pass driver and plan resolution are src/conveyor_compile.rs. The VM is the correctness oracle.

  • slat_count: floor(x + 0.5), half-away-from-zero, clamped to at least one slat (conveyor.rs:88-94). Match it bit-for-bit -- do not substitute f64.nearest, which is round-half-to-even.
  • phase_a: arrest, <sample> re-latch, belt grow/shrink, leaks, advance, exit rate, held-exit.
  • phase_b: admission, capacity room, in_limit with its per-time-unit carry and on_time_boundary reset, inflow quantization carry.
  • Leaks: zone_start/zone_end path-based schedules, exponential vs linear, <leak_integers/> carry, ignore_earlier_zone_losses.
  • Discrete belts.
  • Conveyor-to-queue coupling. Note the queue lowering currently rejects a QueueOutflowKind::Coupled outflow explicitly rather than mis-lowering it; that rejection comes out here.

The two facts that shape the design

The belt is not fixed-size. It grows (conveyor.rs:963) and shrinks (conveyor.rs:1208) whenever <sample> re-latches the transit time. Any design that sizes the belt once at init from curr[plan.len_off] is wrong. The queue lowering's bump allocator + doubling ring (src/wasmgen/passes.rs) is the pattern to reuse; a Slat is content plus leak_basis[n_leaks] and leak_window[n_leaks] (conveyor.rs:141-149), i.e. a constant stride of 1 + 2*n_leaks f64s per plan.

Plans are fully-resolved compile-time data. ConveyorPlan (conveyor_compile.rs:361) is flat: slot offsets, small vectors, and bools (discrete, exponential_leak, ignore_earlier_zone_losses, leak count, inflow count). Emit unrolled, plan-specialized code. Do not emit a runtime interpreter that walks a plan structure in linear memory. The only runtime loops should be over the dynamic slat count. This is what the queue lowering does and it is where the performance comes from.

Hook points

Conveyors are Euler-only, enforced by a compile-time gate (conveyor_compile.rs:952-956), so emit_euler_step is the only site the step pass hangs off. Container-access publishing happens at step start, before the Flows phase (conveyor_compile.rs:2889) -- a second, distinct hook point. The queue lowering already established both; do not collapse them.

Mid-run get_value must be a side-effect-free preview (vm.rs:1187-1216), or a host reading curr mid-run double-advances the belt. See emit_preview_pass in src/wasmgen/passes.rs for the queue-side pattern: save descriptors, repoint at a bump-allocated clone, run, restore, rewind.

Acceptance

  • Every conveyor fixture under test/conveyors/ runs through the DLR-FT wasm-interpreter parity harness and the wasm slab matches the VM slab at existing epsilons.
  • Nothing under test/ changes. Bit-identity with the VM is not the bar; the checked-in expected data matching at its existing epsilons is.
  • Verify the parity gate by mutation, not by observing green: perturb the wasm output and confirm the fixtures go red. The queue lowering was verified this way (a 0.125 perturbation of the admit path), and it caught that the fixtures had previously been running through neither backend.

Suggested sequencing

Landing this as one change is not advisable. Suggested order, each with parity green:

  1. Conveyor core: continuous, no leaks, no <sample>/<arrest>. Belt advance, exit rate, admission, capacity.
  2. Leaks: zones, exponential vs linear, <leak_integers/>, ignore_earlier_zone_losses.
  3. The hard flags: discrete, in_limit carry, quantization carry, <sample>/<arrest>, held-exit, belt grow/shrink.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions