Skip to content

Pipeline contract

Plan format v1, internal ABI v6. Execution is synchronous. Pipelines reuse 10 operations and 68 type signatures across eight numeric dtypes; they do not add semantic operations. See pipeline examples.

Plans and supported nodes

engine.compilePipeline({ dtype, nodes }) returns an immutable compiled object. Each node consumes the previous accumulator, starting with the primary input. Outputs keep the same dtype. Binary operands are constants or { input: 0 } references to the first vector passed in run(input, [extra]). Slots must be used contiguously from zero, with at most eight extras. All vectors match dtype and length; a one-element array does not broadcast. Constants undergo ordinary scalar conversion and validation at compile time.

DtypeNodes
f32/f64add/sub/mul/div/abs/neg/square/min/max/clamp
i8/i16/i32add/sub/mul/abs/neg/square/min/max/clamp
u8/u16/u32add/sub/mul/square/min/max/clamp

Integer division, predicates, masks, select, conversions, reductions, scans, advanced mathematics and arbitrary DAGs are rejected by plan format v1. No JS callback or user-provided source string is parsed or executed.

Serialization

pipeline.plan is deeply frozen and contains version: 1, the registry operationVersion SHA-256, precision: 'strict', dtype and normalized nodes. operandCount and capability are properties of the compiled object.

ts
const restored = engine.compilePipeline(
  JSON.parse(JSON.stringify(pipeline.plan)),
)

Constants may be numbers or the strings NaN, Infinity, -Infinity, -0. Normalized plans always serialize these special values as strings. f32 constants round before serialization; integer constants must be representable. Clamp checks the original lower/upper ordering even if f32 rounding would make them equal. Definitions may omit version and precision to use current values. Serialized plans always include both. Old fingerprints, unknown versions and non-strict precision fail. Plans are portable data; compiled objects and resident handles belong to their engine. Workers accept the portable plan.

Execution and numerical behavior

TS validates plans, and each native/Wasm entry independently validates the entire node table, dtype, constants, bounds and operand slots before touching data. The generic loop walks elements then nodes; f32 SIMD walks vector blocks then nodes. One accumulator is reused and output is written after the final node. There are no full-size intermediates or repeated full-array passes per node. AVX2 uses eight f32 lanes, NEON/SIMD128 four, with equivalent scalar tails.

An exact three-node constant mul → add → clamp chain has a specialized loop for all eight dtypes. It retains every step: f32/f64 round at their precision, integers wrap after multiply and add before clamp. No FMA, reordering or algebraic elimination is allowed. Ordinary NaN, infinity and signed-zero rules apply. NaN payloads are unspecified.

pipeline.capability describes the whole chain. f32 follows the selected explicit SIMD kernel or scalar; other dtypes report scalar, with fallback on SIMD engines. Compiler optimizations do not change this classification.

Cache, space and lifetime

Plans contain 1–64 nodes. Each engine caches up to 32 entries using LRU. The normalized JSON key contains plan/registry versions, dtype, precision, ordered nodes, constants and input slots. Recompilation returns the same cached object and updates recency. getPipelineCacheInfo() exposes size/capacity/hits/misses.

The cache contains validated plans and a Float64Array instruction table with five f64 entries per node: [opcode, extraInputSlotOrMinusOne, scalar, lower, upper]. It is not machine code. Cache lookups still normalize the supplied definition; reuse compiled objects in hot paths.

Rust decodes and validates a bounded table on each call. Native synchronously borrows arrays; Wasm transfers at most 2,560 instruction bytes and 36 pointer-table bytes, releasing them after the call. Ordinary Wasm calls also upload/download vectors; resident execution does not. Scratch space scales with node/input count, not vector length. Outputs still allocate when no out is supplied.

LRU eviction does not invalidate a retained compiled object. Caller-retained plans are outside the cache bound. Engine disposal clears the cache and prevents execution of retained plans. Arena disposal invalidates its handles but does not prevent a plan from running in another live arena of the same engine.

Aliases and errors

Recheck lifetimes and backing memory after reading options and operand lists. All plans, arrays, lengths, operand counts and overlap checks complete before writing. Output may exactly alias the primary or any extra input; read all inputs for an element/block before writing its result. Read-only inputs may overlap; partial input/output overlap is rejected.

Native code uses checked metadata and pointers without creating overlapping mutable Rust slices. Wasm accepts only live matching allocations. Instruction and pointer tables are decoded into private data before execution, including when a direct internal-ABI caller aliases those tables with output.

Operations with per-element failures, such as integer division or checked conversion, are excluded from this format. An invalid later node cannot leave partial output. Shared/resizable/detached memory, disposed handles and foreign engine plans/handles are rejected.

See performance guidance for workload selection and benchmarks for measurements.

Released under the MIT License.