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:
- Conveyor core: continuous, no leaks, no
<sample>/<arrest>. Belt advance, exit rate, admission, capacity.
- Leaks: zones, exponential vs linear,
<leak_integers/>, ignore_earlier_zone_losses.
- The hard flags:
discrete, in_limit carry, quantization carry, <sample>/<arrest>, held-exit, belt grow/shrink.
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
ConveyorTransitTooLongat 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:A queue-only model routes through
queue_compile::compile_simand 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 aresrc/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 substitutef64.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_limitwith its per-time-unit carry andon_time_boundaryreset, inflow quantization carry.zone_start/zone_endpath-based schedules, exponential vs linear,<leak_integers/>carry,ignore_earlier_zone_losses.QueueOutflowKind::Coupledoutflow 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 fromcurr[plan.len_off]is wrong. The queue lowering's bump allocator + doubling ring (src/wasmgen/passes.rs) is the pattern to reuse; aSlatiscontentplusleak_basis[n_leaks]andleak_window[n_leaks](conveyor.rs:141-149), i.e. a constant stride of1 + 2*n_leaksf64s 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), soemit_euler_stepis 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_valuemust be a side-effect-free preview (vm.rs:1187-1216), or a host readingcurrmid-run double-advances the belt. Seeemit_preview_passinsrc/wasmgen/passes.rsfor the queue-side pattern: save descriptors, repoint at a bump-allocated clone, run, restore, rewind.Acceptance
test/conveyors/runs through the DLR-FTwasm-interpreterparity harness and the wasm slab matches the VM slab at existing epsilons.test/changes. Bit-identity with the VM is not the bar; the checked-in expected data matching at its existing epsilons is.Suggested sequencing
Landing this as one change is not advisable. Suggested order, each with parity green:
<sample>/<arrest>. Belt advance, exit rate, admission, capacity.<leak_integers/>,ignore_earlier_zone_losses.discrete,in_limitcarry, quantization carry,<sample>/<arrest>, held-exit, belt grow/shrink.