NativeArray
NativeArray — flat numeric arrays for high-performance numeric loops.
Unlike Array (which uses a 32-way persistent trie), NativeArray is backed by a flat OCaml array that maps directly to sequential memory. This makes it cache-friendly for sequential scan patterns and enables tight inner loops for sum, map, and fold operations.
Compiled (--compile) vectorization, in practice:
- sum_int / sum_float: both auto-vectorize under clang -O2 (NEON on
arm64, SSE4.2+ on x86-64) — sum_float needed a scoped
`#pragma clang fp reassociate(on)` in the runtime C loop, since
strict IEEE 754 float semantics otherwise block the reduction from
vectorizing at all.
- map_int with a non-capturing or single-capture lambda: the compiler
inlines the callback directly into the loop instead of dispatching
through a closure pointer, which lets clang's own inliner and
vectorizer see through it — arithmetic-heavy bodies (`fn x -> x*x+1`,
`fn x -> x + captured`) compile to real SIMD instructions. A closure
reused elsewhere, or one with more than one captured variable, still
works correctly but falls back to the slower closure-dispatch path.
- map_float: correct and (for capturing/non-capturing alike) gets the
same closure-inlining treatment as map_int. Float crossing a
closure-call boundary is normally heap-boxed (march_alloc_float),
which blocks vectorization — but when the callback's signature is
concretely all-Float (no generic type variable), the compiler clones
the inlined callback under natural double params/return with zero
boxing, and the loop DOES vectorize (real NEON/SSE, confirmed via
-emit-llvm). A callback whose signature is still generic at this
point falls back to the boxed (correct, non-vectorized) path. See
specs/optimizations.md P10 "Float-boxing Stage 4, Option B" and
specs/plans/2026-07-13-float-boxing-design.md.
- fold_int / fold_float / fold_f32 / fold_i32 / fold_u8 all have compiled
implementations: the accumulator crosses the closure boundary in the
erased/boxed representation (see march_typed_array_fold's RC
discipline, which native_*_arr_fold mirrors), so no per-width
vectorization applies — this is a correctness-first scalar loop.
- map2_int / map2_float (two-array zip-with, e.g. for column-column
arithmetic) get the identical closure-inlining/vectorization treatment
map_int/map_float do above — same eligibility bar, same unboxed clone
for a concrete-Float callback. Verified ~47x faster than the pre-fix
boxed closure-call path on a 5M-element benchmark; see
docs/simd-benchmarks.md's "Fix history: map2".Five opaque element types are supported: NativeIntArr — wraps int array (i64) NativeFloatArr — wraps float array (f64) NativeF32Arr — narrow float array (f32); stores round to nearest-even NativeI32Arr — narrow int array (i32); stores truncate mod 2^32 NativeU8Arr — narrow int array (u8); stores truncate mod 2^8 Narrow-width stores never trap; loads widen exactly (u8 zero-extends, i32 sign-extends). See docs/simd-vectorization.md's "Narrow element widths" section for the full boundary rule and benchmarks.
Usage: let arr = NativeArray.make_float(1000, 0.0) let arr2 = NativeArray.map_float(arr, fn x -> x *. 2.0) let s = NativeArray.sum_float(arr2)
Functions
Widen a NativeF32Arr to NativeFloatArr (exact). O(n).
Narrow a NativeFloatArr to a NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).
Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).
Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).
Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).
Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).
Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).
Build a NativeF32Arr from a List(Float), rounding each element to binary32. O(n).
Build a NativeFloatArr from a List(Float). O(n).
Build a NativeI32Arr from a List(Int), wrapping each element to i32. O(n).
Build a NativeIntArr from a List(Int). O(n).
Build a NativeU8Arr from a List(Int), wrapping each element mod 256. O(n).
Return the element at index [i] (0-based), widened to Float. Panics if out of bounds.
Return the element at index [i] (0-based). Panics if out of bounds.
Return the element at index [i] (0-based), sign-extended to Int. Panics if out of bounds.
Return the element at index [i] (0-based). Panics if out of bounds.
Return the element at index [i] (0-based), zero-extended to Int (0..255). Panics if out of bounds.
Convert a NativeI32Arr to NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).
Widen a NativeI32Arr to NativeIntArr (exact sign-extend). O(n).
Narrow a NativeIntArr to NativeI32Arr, wrapping each element mod 2^32 two's-complement. O(n).
Narrow a NativeIntArr to NativeU8Arr, wrapping each element mod 256. O(n).
Return the number of elements in [arr].
Return the number of elements in [arr].
Return the number of elements in [arr].
Return the number of elements in [arr].
Return the number of elements in [arr].
Create a new f32 array of [n] elements, each initialised to [fill] rounded to binary32.
Create a new float array of [n] elements, each initialised to [fill].
Create a new i32 array of [n] elements, each initialised to [fill] wrapped to i32.
Create a new integer array of [n] elements, each initialised to [fill].
Create a new u8 array of [n] elements, each initialised to [fill] wrapped to u8.
Apply [f] elementwise to two same-length arrays, returning a new NativeF32Arr. f(a_elem, b_elem) = out_elem, rounded to binary32. Panics if the arrays differ in length. O(n).
Apply [f] elementwise to two same-length arrays, returning a new NativeFloatArr. f(a_elem, b_elem) = out_elem. Panics if the arrays differ in length. O(n).
Apply [f] elementwise to two same-length arrays, returning a new NativeI32Arr. f(a_elem, b_elem) = out_elem, wrapped to i32. Panics if the arrays differ in length. O(n).
Apply [f] elementwise to two same-length arrays, returning a new NativeIntArr. f(a_elem, b_elem) = out_elem. Panics if the arrays differ in length. O(n).
Apply [f] elementwise to two same-length arrays, returning a new NativeU8Arr. f(a_elem, b_elem) = out_elem, wrapped mod 256. Panics if the arrays differ in length. O(n).
Apply [f] to every element (widened Float in, rounded to binary32 out), returning a new NativeF32Arr. O(n).
Apply [f] to every element, returning a new NativeFloatArr. O(n).
Apply [f] to every element (sign-extended Int in, wrapped to i32 out), returning a new NativeI32Arr. O(n).
Apply [f] to every element, returning a new NativeIntArr. O(n).
Apply [f] to every element (zero-extended Int in, wrapped to u8 out), returning a new NativeU8Arr. O(n).
Set element [i] to [v] rounded to binary32 (nearest-even). Never traps.
Return a new array with element [i] replaced by [v]. O(n).
Set element [i] to [v] wrapped mod 2^32 two's-complement. Never traps.
Return a new array with element [i] replaced by [v]. O(n).
Set element [i] to [v] wrapped mod 256. Never traps.
Sum all elements as a double accumulation over pre-rounded binary32 elements. O(n) tight loop.
Sum all elements. O(n) tight loop.
Sum all elements, accumulated in Int (i64). O(n) tight loop.
Sum all elements. O(n) tight loop.
Sum all elements, accumulated in Int (i64). O(n) tight loop.
Widen every element to Float, returning a new NativeFloatArr. O(n).
Convert a NativeF32Arr to a List(Float) (exact widen). O(n).
Convert a NativeFloatArr to a List(Float). O(n).
Convert a NativeI32Arr to a List(Int) (exact widen). O(n).
Convert a NativeIntArr to a List(Int). O(n).
Convert a NativeU8Arr to a List(Int) (exact zero-extend). O(n).
Convert a NativeU8Arr to NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).
Widen a NativeU8Arr to NativeIntArr (exact zero-extend). O(n).