March Docs

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

fnf32_to_float_arrf32_to_float_arr(arr)#

Widen a NativeF32Arr to NativeFloatArr (exact). O(n).

fnfloat_to_f32_arrfloat_to_f32_arr(arr)#

Narrow a NativeFloatArr to a NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).

fnfold_f32fold_f32(arr, acc, f)#

Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).

fnfold_floatfold_float(arr, acc, f)#

Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).

fnfold_i32fold_i32(arr, acc, f)#

Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).

fnfold_intfold_int(arr, acc, f)#

Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).

fnfold_u8fold_u8(arr, acc, f)#

Left fold over all elements. Argument order: collection first, init second, callback last (uncurried-collection convention). Callback f(acc, elem) = new_acc. O(n).

fnfrom_list_f32from_list_f32(lst)#

Build a NativeF32Arr from a List(Float), rounding each element to binary32. O(n).

fnfrom_list_floatfrom_list_float(lst)#

Build a NativeFloatArr from a List(Float). O(n).

fnfrom_list_i32from_list_i32(lst)#

Build a NativeI32Arr from a List(Int), wrapping each element to i32. O(n).

fnfrom_list_intfrom_list_int(lst)#

Build a NativeIntArr from a List(Int). O(n).

fnfrom_list_u8from_list_u8(lst)#

Build a NativeU8Arr from a List(Int), wrapping each element mod 256. O(n).

fnget_f32get_f32(arr, i : Int)#

Return the element at index [i] (0-based), widened to Float. Panics if out of bounds.

fnget_floatget_float(arr, i : Int)#

Return the element at index [i] (0-based). Panics if out of bounds.

fnget_i32get_i32(arr, i : Int)#

Return the element at index [i] (0-based), sign-extended to Int. Panics if out of bounds.

fnget_intget_int(arr, i : Int)#

Return the element at index [i] (0-based). Panics if out of bounds.

fnget_u8get_u8(arr, i : Int)#

Return the element at index [i] (0-based), zero-extended to Int (0..255). Panics if out of bounds.

fni32_to_f32_arri32_to_f32_arr(arr)#

Convert a NativeI32Arr to NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).

fni32_to_int_arri32_to_int_arr(arr)#

Widen a NativeI32Arr to NativeIntArr (exact sign-extend). O(n).

fnint_to_i32_arrint_to_i32_arr(arr)#

Narrow a NativeIntArr to NativeI32Arr, wrapping each element mod 2^32 two's-complement. O(n).

fnint_to_u8_arrint_to_u8_arr(arr)#

Narrow a NativeIntArr to NativeU8Arr, wrapping each element mod 256. O(n).

fnlength_f32length_f32(arr)#

Return the number of elements in [arr].

fnlength_floatlength_float(arr)#

Return the number of elements in [arr].

fnlength_i32length_i32(arr)#

Return the number of elements in [arr].

fnlength_intlength_int(arr)#

Return the number of elements in [arr].

fnlength_u8length_u8(arr)#

Return the number of elements in [arr].

fnmake_f32make_f32(n : {Int | _ >= 0}, fill : Float)#

Create a new f32 array of [n] elements, each initialised to [fill] rounded to binary32.

fnmake_floatmake_float(n : {Int | _ >= 0}, fill : Float)#

Create a new float array of [n] elements, each initialised to [fill].

fnmake_i32make_i32(n : {Int | _ >= 0}, fill : Int)#

Create a new i32 array of [n] elements, each initialised to [fill] wrapped to i32.

fnmake_intmake_int(n : {Int | _ >= 0}, fill : Int)#

Create a new integer array of [n] elements, each initialised to [fill].

fnmake_u8make_u8(n : {Int | _ >= 0}, fill : Int)#

Create a new u8 array of [n] elements, each initialised to [fill] wrapped to u8.

fnmap2_f32map2_f32(arr1, arr2, f)#

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).

fnmap2_floatmap2_float(arr1, arr2, f)#

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).

fnmap2_i32map2_i32(arr1, arr2, f)#

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).

fnmap2_intmap2_int(arr1, arr2, f)#

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).

fnmap2_u8map2_u8(arr1, arr2, f)#

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).

fnmap_f32map_f32(arr, f)#

Apply [f] to every element (widened Float in, rounded to binary32 out), returning a new NativeF32Arr. O(n).

fnmap_floatmap_float(arr, f)#

Apply [f] to every element, returning a new NativeFloatArr. O(n).

fnmap_i32map_i32(arr, f)#

Apply [f] to every element (sign-extended Int in, wrapped to i32 out), returning a new NativeI32Arr. O(n).

fnmap_intmap_int(arr, f)#

Apply [f] to every element, returning a new NativeIntArr. O(n).

fnmap_u8map_u8(arr, f)#

Apply [f] to every element (zero-extended Int in, wrapped to u8 out), returning a new NativeU8Arr. O(n).

fnset_f32set_f32(arr, i : Int, v : Float)#

Set element [i] to [v] rounded to binary32 (nearest-even). Never traps.

fnset_floatset_float(arr, i : Int, v : Float)#

Return a new array with element [i] replaced by [v]. O(n).

fnset_i32set_i32(arr, i : Int, v : Int)#

Set element [i] to [v] wrapped mod 2^32 two's-complement. Never traps.

fnset_intset_int(arr, i : Int, v : Int)#

Return a new array with element [i] replaced by [v]. O(n).

fnset_u8set_u8(arr, i : Int, v : Int)#

Set element [i] to [v] wrapped mod 256. Never traps.

fnsum_f32sum_f32(arr) : Float#

Sum all elements as a double accumulation over pre-rounded binary32 elements. O(n) tight loop.

fnsum_floatsum_float(arr) : Float#

Sum all elements. O(n) tight loop.

fnsum_i32sum_i32(arr) : Int#

Sum all elements, accumulated in Int (i64). O(n) tight loop.

fnsum_intsum_int(arr) : Int#

Sum all elements. O(n) tight loop.

fnsum_u8sum_u8(arr) : Int#

Sum all elements, accumulated in Int (i64). O(n) tight loop.

fnto_float_arrto_float_arr(arr)#

Widen every element to Float, returning a new NativeFloatArr. O(n).

fnto_list_f32to_list_f32(arr)#

Convert a NativeF32Arr to a List(Float) (exact widen). O(n).

fnto_list_floatto_list_float(arr)#

Convert a NativeFloatArr to a List(Float). O(n).

fnto_list_i32to_list_i32(arr)#

Convert a NativeI32Arr to a List(Int) (exact widen). O(n).

fnto_list_intto_list_int(arr)#

Convert a NativeIntArr to a List(Int). O(n).

fnto_list_u8to_list_u8(arr)#

Convert a NativeU8Arr to a List(Int) (exact zero-extend). O(n).

fnu8_to_f32_arru8_to_f32_arr(arr)#

Convert a NativeU8Arr to NativeF32Arr, rounding each element to binary32 (nearest-even). O(n).

fnu8_to_int_arru8_to_int_arr(arr)#

Widen a NativeU8Arr to NativeIntArr (exact zero-extend). O(n).