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 have no compiled implementation yet — calling
  them from a --compile build will fail to link. Use sum/map or a
  manual index loop until this lands.
- 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".

Two opaque element types are supported: NativeIntArr — wraps int array NativeFloatArr — wraps float array

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

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

fnfrom_list_floatfrom_list_float(lst)#

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

fnfrom_list_intfrom_list_int(lst)#

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

fnget_floatget_float(arr, i : Int)#

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

fnget_intget_int(arr, i : Int)#

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

fnlength_floatlength_float(arr)#

Return the number of elements in [arr].

fnlength_intlength_int(arr)#

Return the number of elements in [arr].

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

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

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

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

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

fnmap_floatmap_float(arr, f)#

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

fnmap_intmap_int(arr, f)#

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

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

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

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

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

fnsum_floatsum_float(arr) : Float#

Sum all elements. O(n) tight loop.

fnsum_intsum_int(arr) : Int#

Sum all elements. O(n) tight loop.

fnto_float_arrto_float_arr(arr)#

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

fnto_list_floatto_list_float(arr)#

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

fnto_list_intto_list_int(arr)#

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