Skip to main content

submilli_engine/runtime/prelude/array/
mod.rs

1//! The submilli array's operations — the Rust port of the prelude's `Array`
2//! and `ArrayConstructor` methods.
3//!
4//! Unlike the string port, these work in wasmtime's value space: an array holds
5//! boxed object references, higher-order methods call back into a *guest*
6//! closure once per element, and the searching/`join`/default-`sort` paths
7//! re-enter each element's vtable (`toString`/`equals`). The `$Array`
8//! marshalling and host-fn registration live in [`install`]; the shared
9//! [`Closure`](crate::runtime::prelude::closure::Closure) handles callbacks.
10//! This file is the iteration and the element math.
11//!
12//! Mutators retain the receiver's identity and spare backing capacity. Methods
13//! that call guest code snapshot and root their inputs before callbacks run.
14
15mod install;
16pub(super) mod sort;
17
18pub(crate) use install::{array_like_type, declare_types};
19pub use install::{declare, install};
20pub(crate) use sort::merge_sort;
21
22use wasmtime::{Caller, StructRef, StructRefPre, Val};
23
24use crate::runtime::StoreData;
25use crate::runtime::array_storage::ArrayStorage;
26use crate::runtime::host::{host_boxed_number_vtable, write_submilli_array_struct};
27use crate::runtime::intrinsic_types::intrinsic_types;
28use crate::runtime::prelude::closure::Closure;
29use crate::runtime::prelude::collection::both_nan;
30use crate::runtime::prelude::iterator::{IterKind, make_index_iterator};
31use crate::runtime::prelude::keep::{KeptValue, KeptValues, keep_all};
32use crate::runtime::prelude::vtable::{dispatch_vtable_slot, read_string_units};
33
34// ---------------------------------------------------------------------------
35// Marshalling helpers
36// ---------------------------------------------------------------------------
37
38/// Read an `$Array` `Val` into its boxed elements, snapshotting up front so a
39/// callback may mutate the source array during iteration.
40pub(super) fn read_array(
41    caller: &mut Caller<'_, StoreData>,
42    val: &Val,
43    _name: &str,
44) -> wasmtime::Result<Vec<Val>> {
45    crate::runtime::array_storage::ArrayStorage::read(caller, val)?.snapshot(caller)
46}
47
48/// [`read_array`] for a method that runs the program's code while it holds
49/// the elements: a callback, or an element's own `toString`. That code can
50/// drop them from the source array, leaving the snapshot the only thing that
51/// refers to them, so it is kept reachable. Element `equals` and `hash` are
52/// never the program's, so the searching methods read without keeping.
53pub(super) fn read_kept_array(
54    caller: &mut Caller<'_, StoreData>,
55    val: &Val,
56    name: &str,
57) -> wasmtime::Result<Vec<Val>> {
58    let elements = read_array(caller, val, name)?;
59    keep_all(caller, &elements)?;
60    Ok(elements)
61}
62
63/// Build a fresh `$Array` `Val` from already-boxed elements.
64fn build_array(caller: &mut Caller<'_, StoreData>, elements: &[Val]) -> wasmtime::Result<Val> {
65    let st = write_submilli_array_struct(caller, elements)?;
66    Ok(Val::AnyRef(Some(st.to_anyref())))
67}
68
69/// Replace the live elements, retaining capacity and clearing removed slots.
70fn replace_elements(
71    caller: &mut Caller<'_, StoreData>,
72    receiver: &Val,
73    elements: &[Val],
74) -> wasmtime::Result<()> {
75    crate::runtime::array_storage::ArrayStorage::read(caller, receiver)?.replace(caller, elements)
76}
77
78/// Box an `f64` into a `$boxed_number` object (for iterator indices).
79fn box_number(caller: &mut Caller<'_, StoreData>, n: f64) -> wasmtime::Result<Val> {
80    let boxed = intrinsic_types(&mut *caller)?.boxed_number.clone();
81    let vtable = host_boxed_number_vtable(caller)?;
82    let pre = StructRefPre::new(&mut *caller, boxed);
83    let st = StructRef::new(&mut *caller, &pre, &[vtable, Val::F64(n.to_bits())])?;
84    Ok(Val::AnyRef(Some(st.to_anyref())))
85}
86
87/// Whether `val` is a (non-null) `$Array` — backs `flat`/`flatMap` flattening.
88pub(super) fn is_array(caller: &mut Caller<'_, StoreData>, val: &Val) -> wasmtime::Result<bool> {
89    let Val::AnyRef(Some(any)) = val else {
90        return Ok(false);
91    };
92    let Some(st) = any.as_struct(&mut *caller)? else {
93        return Ok(false);
94    };
95    let array_ty = intrinsic_types(&mut *caller)?.array.clone();
96    st.matches_ty(&*caller, &array_ty)
97}
98
99fn is_null(v: &Val) -> bool {
100    matches!(v, Val::AnyRef(None))
101}
102
103/// The element's `toString()` (vtable slot 0) as code units — `join` and the
104/// default `sort` order. `null` has no vtable, so each caller decides its text:
105/// JavaScript joins it as `""` but sorts it as `"null"`.
106async fn element_to_string(
107    caller: &mut Caller<'_, StoreData>,
108    elem: Val,
109) -> wasmtime::Result<Vec<u16>> {
110    if is_null(&elem) {
111        return Err(wasmtime::Error::msg("array element toString: null element"));
112    }
113    let s = dispatch_vtable_slot(caller, &elem, 0, &[]).await?;
114    read_string_units(caller, &s, "array element toString result")
115}
116
117/// An element's text in `join`: `null` joins as the empty string, as in JavaScript
118/// (`[1, null].join()` is `"1,"`).
119async fn join_text(caller: &mut Caller<'_, StoreData>, elem: Val) -> wasmtime::Result<Vec<u16>> {
120    if super::undefined::is_nullish(caller, &elem)? {
121        return Ok(Vec::new());
122    }
123    element_to_string(caller, elem).await
124}
125
126/// Whether `slot` holds `target` — `indexOf`/`lastIndexOf`/`includes`. `null`
127/// equals only `null` (`[1, null].indexOf(null)` is `1`) and is decided here: a null
128/// slot has no vtable, and a class's `equals` cannot take a null argument.
129async fn element_matches(
130    caller: &mut Caller<'_, StoreData>,
131    slot: Val,
132    target: Val,
133) -> wasmtime::Result<bool> {
134    if is_null(&slot) || is_null(&target) {
135        return Ok(is_null(&slot) && is_null(&target));
136    }
137    let left_undefined = super::undefined::is_undefined(caller, &slot)?;
138    let right_undefined = super::undefined::is_undefined(caller, &target)?;
139    if left_undefined || right_undefined {
140        return Ok(left_undefined && right_undefined);
141    }
142    element_equals(caller, slot, target).await
143}
144
145/// Whether `slot` equals `target` via `slot`'s `equals` (vtable slot 2).
146async fn element_equals(
147    caller: &mut Caller<'_, StoreData>,
148    slot: Val,
149    target: Val,
150) -> wasmtime::Result<bool> {
151    match dispatch_vtable_slot(caller, &slot, 2, &[target]).await? {
152        Val::I32(b) => Ok(b != 0),
153        other => Err(wasmtime::Error::msg(format!(
154            "array element equals returned {other:?}, expected i32"
155        ))),
156    }
157}
158
159// ---------------------------------------------------------------------------
160// Index math (ported 1:1 from the Wasm bodies)
161// ---------------------------------------------------------------------------
162
163/// `i32.trunc_sat_f64_s` — Rust's `as i32` saturates (NaN → 0) identically.
164fn trunc_sat(x: f64) -> i32 {
165    x as i32
166}
167
168/// JS slice/fill/copyWithin index: `if x<0 {x+=len}; clamp to [0, len]`.
169fn norm_clamp(x: f64, len: i32) -> i32 {
170    let mut i = trunc_sat(x);
171    if i < 0 {
172        i += len;
173    }
174    i.clamp(0, len)
175}
176
177/// `at` index: from-end on negatives, `None` when out of `[0, len)`.
178fn at_index(x: f64, len: i32) -> Option<usize> {
179    let mut i = trunc_sat(x);
180    if i < 0 {
181        i += len;
182    }
183    if i < 0 || i >= len {
184        None
185    } else {
186        Some(i as usize)
187    }
188}
189
190/// Forward search start (`indexOf`/`includes`): from-end on negatives, floored at 0.
191fn fwd_from(x: f64, len: i32) -> i32 {
192    let mut fi = trunc_sat(x);
193    if fi < 0 {
194        fi += len;
195    }
196    fi.max(0)
197}
198
199/// Backward search start (`lastIndexOf`): from-end on negatives, `None` below 0,
200/// capped at `len-1`.
201fn last_from(x: f64, len: i32) -> Option<i32> {
202    let mut fi = trunc_sat(x);
203    if fi < 0 {
204        fi += len;
205    }
206    if fi < 0 {
207        return None;
208    }
209    Some(fi.min(len - 1))
210}
211
212// ---------------------------------------------------------------------------
213// Accessors
214// ---------------------------------------------------------------------------
215
216/// `at(index)`: one element read in place, `undefined` out of range.
217fn at(caller: &mut Caller<'_, StoreData>, receiver: &Val, index: f64) -> wasmtime::Result<Val> {
218    let storage = ArrayStorage::read(caller, receiver)?;
219    match at_index(index, storage.len as i32) {
220        Some(i) => storage.get(caller, i as u32),
221        None => super::undefined::value(caller),
222    }
223}
224
225/// `slice(start, end)`: only the kept range is read.
226fn slice(
227    caller: &mut Caller<'_, StoreData>,
228    receiver: &Val,
229    start: f64,
230    end: f64,
231) -> wasmtime::Result<Vec<Val>> {
232    let storage = ArrayStorage::read(caller, receiver)?;
233    let len = storage.len as i32;
234    let si = norm_clamp(start, len);
235    let count = (norm_clamp(end, len) - si).max(0);
236    storage.range(caller, si as u32, count as u32)
237}
238
239/// `concat(...others: T[][])`: this array's elements followed by every element
240/// of each `others` array.
241fn concat(
242    caller: &mut Caller<'_, StoreData>,
243    mut acc: Vec<Val>,
244    others: &Val,
245) -> wasmtime::Result<Vec<Val>> {
246    for sub in read_array(caller, others, "Array#concat")? {
247        acc.extend(read_array(caller, &sub, "Array#concat")?);
248    }
249    Ok(acc)
250}
251
252async fn index_of(
253    caller: &mut Caller<'_, StoreData>,
254    elements: Vec<Val>,
255    target: Val,
256    from: f64,
257) -> wasmtime::Result<f64> {
258    let len = elements.len() as i32;
259    let mut i = fwd_from(from, len);
260    while i < len {
261        let e = elements[i as usize];
262        if element_matches(caller, e, target).await? {
263            return Ok(i as f64);
264        }
265        i += 1;
266    }
267    Ok(-1.0)
268}
269
270async fn last_index_of(
271    caller: &mut Caller<'_, StoreData>,
272    elements: Vec<Val>,
273    target: Val,
274    from: f64,
275) -> wasmtime::Result<f64> {
276    let len = elements.len() as i32;
277    let Some(mut i) = last_from(from, len) else {
278        return Ok(-1.0);
279    };
280    while i >= 0 {
281        let e = elements[i as usize];
282        if element_matches(caller, e, target).await? {
283            return Ok(i as f64);
284        }
285        i -= 1;
286    }
287    Ok(-1.0)
288}
289
290async fn includes(
291    caller: &mut Caller<'_, StoreData>,
292    elements: Vec<Val>,
293    target: Val,
294    from: f64,
295) -> wasmtime::Result<bool> {
296    let len = elements.len() as i32;
297    let mut i = fwd_from(from, len);
298    while i < len {
299        let e = elements[i as usize];
300        // SameValueZero, unlike `indexOf`'s `===`: `NaN` finds `NaN`.
301        if element_matches(caller, e, target).await? || both_nan(caller, &e, &target)? {
302            return Ok(true);
303        }
304        i += 1;
305    }
306    Ok(false)
307}
308
309async fn join(
310    caller: &mut Caller<'_, StoreData>,
311    elements: Vec<Val>,
312    sep: Vec<u16>,
313) -> wasmtime::Result<Vec<u16>> {
314    if elements.is_empty() {
315        return Ok(Vec::new());
316    }
317    let mut acc = join_text(caller, elements[0]).await?;
318    for &e in &elements[1..] {
319        acc.extend_from_slice(&sep);
320        let s = join_text(caller, e).await?;
321        acc.extend_from_slice(&s);
322    }
323    Ok(acc)
324}
325
326// ---------------------------------------------------------------------------
327// Mutators (snapshot transforms preserve receiver identity)
328// ---------------------------------------------------------------------------
329
330fn push(caller: &mut Caller<'_, StoreData>, receiver: &Val, elem: Val) -> wasmtime::Result<f64> {
331    crate::runtime::array_storage::ArrayStorage::read(caller, receiver)?.push(caller, elem)
332}
333
334/// `pop()`: the last element, removed in place; `undefined` when empty.
335fn pop(caller: &mut Caller<'_, StoreData>, receiver: &Val) -> wasmtime::Result<Val> {
336    let popped = ArrayStorage::read(caller, receiver)?.pop(caller)?;
337    match popped {
338        Some(value) => Ok(value),
339        None => super::undefined::value(caller),
340    }
341}
342
343fn shift(
344    caller: &mut Caller<'_, StoreData>,
345    receiver: &Val,
346    mut elements: Vec<Val>,
347) -> wasmtime::Result<Val> {
348    if elements.is_empty() {
349        return super::undefined::value(caller);
350    }
351    let first = elements.remove(0);
352    replace_elements(caller, receiver, &elements)?;
353    Ok(first)
354}
355
356fn unshift(
357    caller: &mut Caller<'_, StoreData>,
358    receiver: &Val,
359    elements: Vec<Val>,
360    mut items: Vec<Val>,
361) -> wasmtime::Result<f64> {
362    items.extend(elements);
363    let n = items.len() as f64;
364    replace_elements(caller, receiver, &items)?;
365    Ok(n)
366}
367
368fn reverse(
369    caller: &mut Caller<'_, StoreData>,
370    receiver: &Val,
371    mut elements: Vec<Val>,
372) -> wasmtime::Result<Val> {
373    elements.reverse();
374    replace_elements(caller, receiver, &elements)?;
375    Ok(*receiver)
376}
377
378fn fill(
379    caller: &mut Caller<'_, StoreData>,
380    receiver: &Val,
381    mut elements: Vec<Val>,
382    value: Val,
383    start: f64,
384    end: f64,
385) -> wasmtime::Result<Val> {
386    let len = elements.len() as i32;
387    let si = norm_clamp(start, len);
388    let ei = norm_clamp(end, len);
389    for slot in elements.iter_mut().take(ei as usize).skip(si as usize) {
390        *slot = value;
391    }
392    replace_elements(caller, receiver, &elements)?;
393    Ok(*receiver)
394}
395
396fn copy_within(
397    caller: &mut Caller<'_, StoreData>,
398    receiver: &Val,
399    mut elements: Vec<Val>,
400    target: f64,
401    start: f64,
402    end: f64,
403) -> wasmtime::Result<Val> {
404    let len = elements.len() as i32;
405    let ti = norm_clamp(target, len);
406    let si = norm_clamp(start, len);
407    let ei = norm_clamp(end, len);
408    let mut count = (ei - si).max(0);
409    let rem = len - ti;
410    if count > rem {
411        count = rem;
412    }
413    // Copy the source span first so overlapping ranges stay memmove-correct.
414    let src: Vec<Val> = elements[si as usize..(si + count) as usize].to_vec();
415    for (k, v) in src.into_iter().enumerate() {
416        elements[ti as usize + k] = v;
417    }
418    replace_elements(caller, receiver, &elements)?;
419    Ok(*receiver)
420}
421
422/// The removed span and the post-splice element list — shared by `splice` and
423/// `toSpliced`.
424fn splice_parts(
425    elements: &[Val],
426    start: f64,
427    delete_count: f64,
428    items: Vec<Val>,
429) -> (Vec<Val>, Vec<Val>) {
430    let len = elements.len() as i32;
431    let si = norm_clamp(start, len);
432    let mut dc = trunc_sat(delete_count).max(0);
433    let rem = len - si;
434    if dc > rem {
435        dc = rem;
436    }
437    let (si, dc) = (si as usize, dc as usize);
438    let removed = elements[si..si + dc].to_vec();
439    let mut result = Vec::with_capacity(elements.len() - dc + items.len());
440    result.extend_from_slice(&elements[..si]);
441    result.extend(items);
442    result.extend_from_slice(&elements[si + dc..]);
443    (removed, result)
444}
445
446fn splice(
447    caller: &mut Caller<'_, StoreData>,
448    receiver: &Val,
449    elements: Vec<Val>,
450    start: f64,
451    delete_count: f64,
452    items: Vec<Val>,
453) -> wasmtime::Result<Val> {
454    let (removed, result) = splice_parts(&elements, start, delete_count, items);
455    // Built while the receiver still holds the removed elements: once they are
456    // swapped out nothing else does, and an allocation may collect.
457    let removed = build_array(caller, &removed)?;
458    replace_elements(caller, receiver, &result)?;
459    Ok(removed)
460}
461
462async fn sort(
463    caller: &mut Caller<'_, StoreData>,
464    receiver: &Val,
465    mut elements: Vec<Val>,
466    cmp: Option<Closure>,
467) -> wasmtime::Result<Val> {
468    sort_elems(caller, &mut elements, cmp.as_ref()).await?;
469    // Re-read the storage: the comparator may have pushed or popped.
470    ArrayStorage::read(caller, receiver)?.overwrite_prefix(caller, &elements)?;
471    Ok(*receiver)
472}
473
474// ---------------------------------------------------------------------------
475// Immutable variants (build a fresh result; receiver untouched)
476// ---------------------------------------------------------------------------
477
478fn to_reversed(mut elements: Vec<Val>) -> Vec<Val> {
479    elements.reverse();
480    elements
481}
482
483async fn to_sorted(
484    caller: &mut Caller<'_, StoreData>,
485    mut elements: Vec<Val>,
486    cmp: Option<Closure>,
487) -> wasmtime::Result<Vec<Val>> {
488    sort_elems(caller, &mut elements, cmp.as_ref()).await?;
489    Ok(elements)
490}
491
492/// Stable sort by the program's comparator, or by element `toString()` for the
493/// default order.
494async fn sort_elems(
495    caller: &mut Caller<'_, StoreData>,
496    elements: &mut Vec<Val>,
497    cmp: Option<&Closure>,
498) -> wasmtime::Result<()> {
499    let original_len = elements.len();
500    let mut defined_len = 0usize;
501    for index in 0..original_len {
502        let element = *elements
503            .get(index)
504            .ok_or_else(|| crate::runtime::host::fatal_host_error("sort: invalid element index"))?;
505        if !super::undefined::is_undefined(caller, &element)? {
506            let slot = elements.get_mut(defined_len).ok_or_else(|| {
507                crate::runtime::host::fatal_host_error("sort: invalid output index")
508            })?;
509            *slot = element;
510            defined_len += 1;
511        }
512    }
513    elements.truncate(defined_len);
514    match cmp {
515        Some(cmp) => merge_sort(caller, elements, cmp, |_, element| Ok(element)).await?,
516        None => sort::sort_by_string(caller, elements).await?,
517    }
518    if defined_len != original_len {
519        let undefined = super::undefined::value(caller)?;
520        elements
521            .try_reserve_exact(original_len - defined_len)
522            .map_err(crate::runtime::host::fatal_host_error)?;
523        elements.resize(original_len, undefined);
524    }
525    Ok(())
526}
527
528/// `None` when `index` is out of range — [`install`] raises the catchable
529/// `Error("index out of range")`.
530fn with(elements: &[Val], index: f64, value: Val) -> Option<Vec<Val>> {
531    let i = at_index(index, elements.len() as i32)?;
532    let mut new = elements.to_vec();
533    new[i] = value;
534    Some(new)
535}
536
537// ---------------------------------------------------------------------------
538// Higher-order methods (re-enter a guest closure per element)
539// ---------------------------------------------------------------------------
540
541/// An element callback, called with `(value, index, array)` as TypeScript
542/// types it — or `(value, index)` without an array, as `Array.from`'s `mapFn`.
543/// Its slot is erased, so it may be any function: it is passed only the
544/// arguments it reads, and the index is boxed only when it reads that.
545pub(in crate::runtime::prelude) struct ElementCallback<'a> {
546    f: &'a Closure,
547    reads: usize,
548    array: Option<Val>,
549}
550
551impl<'a> ElementCallback<'a> {
552    pub(in crate::runtime::prelude) fn new(
553        caller: &mut Caller<'_, StoreData>,
554        f: &'a Closure,
555        array: Option<Val>,
556    ) -> wasmtime::Result<Self> {
557        let reads = f.arguments_read(caller)?;
558        Ok(Self { f, reads, array })
559    }
560
561    /// Call on the element at `index`, after `leading` (`reduce`'s accumulator).
562    pub(in crate::runtime::prelude) async fn call(
563        &self,
564        caller: &mut Caller<'_, StoreData>,
565        leading: Option<Val>,
566        value: Val,
567        index: usize,
568    ) -> wasmtime::Result<Val> {
569        let mut args: Vec<Val> = leading.into_iter().chain([value]).collect();
570        if args.len() < self.reads {
571            args.push(box_number(caller, index as f64)?);
572        }
573        if let Some(array) = self.array
574            && args.len() < self.reads
575        {
576            args.push(array);
577        }
578        self.f.call_dynamic(caller, &args).await
579    }
580
581    pub(in crate::runtime::prelude) async fn test(
582        &self,
583        caller: &mut Caller<'_, StoreData>,
584        value: Val,
585        index: usize,
586    ) -> wasmtime::Result<bool> {
587        let result = self.call(caller, None, value, index).await?;
588        super::value::truthy(caller, &result)
589    }
590}
591
592/// `Array.prototype.forEach`: invoke `f` once per element, left to right.
593pub async fn for_each(
594    caller: &mut Caller<'_, StoreData>,
595    array: Val,
596    elements: Vec<Val>,
597    f: &Closure,
598) -> wasmtime::Result<()> {
599    let f = ElementCallback::new(caller, f, Some(array))?;
600    for (index, elem) in elements.into_iter().enumerate() {
601        f.call(caller, None, elem, index).await?;
602    }
603    Ok(())
604}
605
606async fn map(
607    caller: &mut Caller<'_, StoreData>,
608    array: Val,
609    elements: Vec<Val>,
610    f: &Closure,
611) -> wasmtime::Result<Vec<Val>> {
612    let f = ElementCallback::new(caller, f, Some(array))?;
613    let mut out = KeptValues::with_capacity(caller, elements.len())?;
614    for (index, elem) in elements.into_iter().enumerate() {
615        let mapped = f.call(caller, None, elem, index).await?;
616        out.push(caller, mapped)?;
617    }
618    Ok(out.values().to_vec())
619}
620
621async fn filter(
622    caller: &mut Caller<'_, StoreData>,
623    array: Val,
624    elements: Vec<Val>,
625    pred: &Closure,
626) -> wasmtime::Result<Vec<Val>> {
627    let pred = ElementCallback::new(caller, pred, Some(array))?;
628    let mut out = Vec::new();
629    for (index, elem) in elements.into_iter().enumerate() {
630        if pred.test(caller, elem, index).await? {
631            out.push(elem);
632        }
633    }
634    Ok(out)
635}
636
637/// `reduce`, or with `reverse` `reduceRight`: fold `(acc, value, index, array)`
638/// over the elements.
639async fn reduce(
640    caller: &mut Caller<'_, StoreData>,
641    array: Val,
642    elements: Vec<Val>,
643    f: &Closure,
644    mut acc: Val,
645    reverse: bool,
646) -> wasmtime::Result<Val> {
647    let f = ElementCallback::new(caller, f, Some(array))?;
648    let mut indexed: Vec<(usize, Val)> = elements.into_iter().enumerate().collect();
649    if reverse {
650        indexed.reverse();
651    }
652    // The next call boxes its index before it passes the accumulator on.
653    let kept_acc = KeptValue::new(caller)?;
654    for (index, elem) in indexed {
655        acc = f.call(caller, Some(acc), elem, index).await?;
656        kept_acc.set(caller, acc)?;
657    }
658    Ok(acc)
659}
660
661/// Index of the first (or, with `reverse`, last) element matching `pred`.
662async fn find_match(
663    caller: &mut Caller<'_, StoreData>,
664    array: Val,
665    elements: &[Val],
666    pred: &Closure,
667    reverse: bool,
668) -> wasmtime::Result<Option<usize>> {
669    let pred = ElementCallback::new(caller, pred, Some(array))?;
670    let order: Vec<usize> = if reverse {
671        (0..elements.len()).rev().collect()
672    } else {
673        (0..elements.len()).collect()
674    };
675    for i in order {
676        if pred.test(caller, elements[i], i).await? {
677            return Ok(Some(i));
678        }
679    }
680    Ok(None)
681}
682
683async fn some(
684    caller: &mut Caller<'_, StoreData>,
685    array: Val,
686    elements: Vec<Val>,
687    pred: &Closure,
688) -> wasmtime::Result<bool> {
689    let pred = ElementCallback::new(caller, pred, Some(array))?;
690    for (index, elem) in elements.into_iter().enumerate() {
691        if pred.test(caller, elem, index).await? {
692            return Ok(true);
693        }
694    }
695    Ok(false)
696}
697
698async fn every(
699    caller: &mut Caller<'_, StoreData>,
700    array: Val,
701    elements: Vec<Val>,
702    pred: &Closure,
703) -> wasmtime::Result<bool> {
704    let pred = ElementCallback::new(caller, pred, Some(array))?;
705    for (index, elem) in elements.into_iter().enumerate() {
706        if !pred.test(caller, elem, index).await? {
707            return Ok(false);
708        }
709    }
710    Ok(true)
711}
712
713/// Flatten without native recursion; each frame retains its unvisited siblings.
714/// Bound nesting independently of the requested flattening depth.
715fn flat_into(
716    caller: &mut Caller<'_, StoreData>,
717    elements: Vec<Val>,
718    depth: i32,
719    out: &mut Vec<Val>,
720) -> wasmtime::Result<()> {
721    let mut frames = vec![(elements.into_iter(), depth)];
722    while let Some((elements, depth)) = frames.last_mut() {
723        let Some(elem) = elements.next() else {
724            frames.pop();
725            continue;
726        };
727        let depth = *depth;
728        if depth > 0 && is_array(caller, &elem)? {
729            if frames.len() >= crate::runtime::MAX_VTABLE_WALK_DEPTH as usize {
730                return Err(crate::runtime::host::range_error(format!(
731                    "Array#flat exceeds {} levels of nesting; flatten fewer levels",
732                    crate::runtime::MAX_VTABLE_WALK_DEPTH,
733                )));
734            }
735            let sub = read_array(caller, &elem, "Array#flat")?;
736            frames
737                .try_reserve(1)
738                .map_err(crate::runtime::host::fatal_host_error)?;
739            frames.push((sub.into_iter(), depth - 1));
740        } else {
741            out.try_reserve(1)
742                .map_err(crate::runtime::host::fatal_host_error)?;
743            out.push(elem);
744        }
745    }
746    Ok(())
747}
748
749async fn flat_map(
750    caller: &mut Caller<'_, StoreData>,
751    array: Val,
752    elements: Vec<Val>,
753    f: &Closure,
754) -> wasmtime::Result<Vec<Val>> {
755    let f = ElementCallback::new(caller, f, Some(array))?;
756    let kept_mapped = KeptValue::new(caller)?;
757    let mut out = KeptValues::with_capacity(caller, elements.len())?;
758    for (index, elem) in elements.into_iter().enumerate() {
759        let mapped = f.call(caller, None, elem, index).await?;
760        // The callback may reuse the array it returns, so its elements are
761        // kept themselves; the array holds them while room is made.
762        kept_mapped.set(caller, mapped)?;
763        let flattened = read_array(caller, &mapped, "Array#flatMap callback result")?;
764        out.extend(caller, &flattened)?;
765    }
766    Ok(out.values().to_vec())
767}
768
769// ---------------------------------------------------------------------------
770// Iterators (keys / values / entries)
771// ---------------------------------------------------------------------------
772
773/// One step of an Array iterator: the boxed index + element at `pos`. Re-reads
774/// the live backing each call, so a `push` mid-iteration is observed. `payload`
775/// is the `$Array` wrapper itself (not its raw backing), passed through the
776/// shared cursor by [`make_index_iterator`].
777fn array_step(
778    caller: &mut Caller<'_, StoreData>,
779    payload: &Val,
780    pos: i32,
781) -> wasmtime::Result<Option<(Val, Val)>> {
782    let storage = crate::runtime::array_storage::ArrayStorage::read(caller, payload)?;
783    let backing = storage.backing;
784    if pos < 0 || pos as u32 >= storage.len {
785        return Ok(None);
786    }
787    let elem = backing.get(&mut *caller, pos as u32)?;
788    let idx = box_number(caller, pos as f64)?;
789    Ok(Some((idx, elem)))
790}
791
792pub fn values(caller: &mut Caller<'_, StoreData>, array: &Val) -> wasmtime::Result<Val> {
793    make_index_iterator(caller, *array, IterKind::Values, array_step)
794}
795
796fn keys(caller: &mut Caller<'_, StoreData>, array: &Val) -> wasmtime::Result<Val> {
797    make_index_iterator(caller, *array, IterKind::Keys, array_step)
798}
799
800fn entries(caller: &mut Caller<'_, StoreData>, array: &Val) -> wasmtime::Result<Val> {
801    make_index_iterator(caller, *array, IterKind::Entries, array_step)
802}
803
804// ---------------------------------------------------------------------------
805// `ArrayConstructor#from`
806// ---------------------------------------------------------------------------
807
808/// `ArrayConstructor#from(src, mapFn?) -> T[]`. Materializes any iterable
809/// (mirroring the Wasm `collect_iterable`): an `$Array` → its elements; a
810/// `$string` → code points; a `$Map` → `[key, value]` pairs via its `entries()`
811/// cursor; a `$Set` → its `values()` cursor; any other value → drive the
812/// iterator protocol (an `iterator()` method if present, else the value itself).
813/// `mapFn` (arity-1) applies per element on every path, interleaved with
814/// iteration like the Wasm body.
815pub(super) async fn from(
816    caller: &mut Caller<'_, StoreData>,
817    src: &Val,
818    map_fn: &Val,
819) -> wasmtime::Result<Val> {
820    use crate::runtime::prelude::closure;
821    use crate::runtime::prelude::collection::{is_a, object_field, string_code_points, unbox_bool};
822
823    let map_closure = if super::undefined::is_undefined(caller, map_fn)? {
824        None
825    } else {
826        Some(closure::read_callback(caller, map_fn, "Array.from mapFn")?)
827    };
828    let map_fn = match &map_closure {
829        Some(c) => Some(ElementCallback::new(caller, c, None)?),
830        None => None,
831    };
832    let intr = intrinsic_types(&mut *caller)?;
833    let mut out = KeptValues::with_capacity(caller, 0)?;
834
835    if is_a(caller, src, &intr.array)? {
836        // Drive the live backing like the `values` cursor (`array_step`): re-read
837        // length and element each step, so a `mapFn` that appends to the source
838        // mid-iteration is observed — snapshotting would drop those elements.
839        let mut pos: u32 = 0;
840        loop {
841            let storage = crate::runtime::array_storage::ArrayStorage::read(caller, src)?;
842            let backing = storage.backing;
843            if pos >= storage.len {
844                break;
845            }
846            out.reserve(caller, 1)?;
847            let elem = backing.get(&mut *caller, pos)?;
848            let mapped = apply_map(caller, &map_fn, elem, out.values().len()).await?;
849            out.push(caller, mapped)?;
850            pos += 1;
851        }
852        return build_array(caller, out.values());
853    }
854    if is_a(caller, src, &intr.string)? {
855        let cps = string_code_points(caller, src)?;
856        out.reserve(caller, cps.len())?;
857        for cp in cps {
858            let mapped = apply_map(caller, &map_fn, cp, out.values().len()).await?;
859            out.push(caller, mapped)?;
860        }
861        return build_array(caller, out.values());
862    }
863
864    // The iterator and each object its `next()` returns come from guest calls.
865    let kept_iterator = KeptValue::new(caller)?;
866    let kept_result = KeptValue::new(caller)?;
867    let it = if is_a(
868        caller,
869        src,
870        &super::map::map_backing_struct(caller.engine(), &intr)?,
871    )? {
872        super::map::entries(caller, src)?
873    } else if is_a(
874        caller,
875        src,
876        &super::set::set_backing_struct(caller.engine(), &intr)?,
877    )? {
878        super::set::values(caller, src)?
879    } else if let Some(iter_method) = object_field(caller, src, "iterator")? {
880        let c = closure::read(caller, &iter_method, "Array.from iterable")?;
881        c.call_with_receiver(caller, *src, &[]).await?
882    } else if object_field(caller, src, "next")?.is_none()
883        && let Some(length) = object_field(caller, src, "length")?
884    {
885        let length = array_like_length(caller, &length)?;
886        for index in 0..length {
887            out.reserve(caller, 1)?;
888            let hole = super::undefined::value(caller)?;
889            let mapped = apply_map(caller, &map_fn, hole, index).await?;
890            out.push(caller, mapped)?;
891        }
892        return build_array(caller, out.values());
893    } else {
894        *src
895    };
896    kept_iterator.set(caller, it)?;
897
898    let next = object_field(caller, &it, "next")?
899        .ok_or_else(|| wasmtime::Error::msg("Array.from: source is not iterable"))?;
900    let next_closure = closure::read(caller, &next, "Array.from iterator")?;
901    loop {
902        out.reserve(caller, 1)?;
903        let result = next_closure.call_with_receiver(caller, it, &[]).await?;
904        kept_result.set(caller, result)?;
905        let done = object_field(caller, &result, "done")?
906            .ok_or_else(|| wasmtime::Error::msg("Array.from: iterator result missing `done`"))?;
907        if unbox_bool(caller, &done)? {
908            break;
909        }
910        let value = object_field(caller, &result, "value")?
911            .ok_or_else(|| wasmtime::Error::msg("Array.from: iterator result missing `value`"))?;
912        let mapped = apply_map(caller, &map_fn, value, out.values().len()).await?;
913        out.push(caller, mapped)?;
914    }
915    build_array(caller, out.values())
916}
917
918/// An array-like's `length` as JavaScript's `ToLength` reads it for
919/// `Array.from`: truncated toward zero, with `NaN` and negatives as `0`. A
920/// length past the largest array is a `RangeError`.
921fn array_like_length(caller: &mut Caller<'_, StoreData>, length: &Val) -> wasmtime::Result<usize> {
922    let not_a_number = || wasmtime::Error::msg("Array.from: `length` is not a number");
923    let Val::AnyRef(Some(any)) = length else {
924        return Err(not_a_number());
925    };
926    let st = any.as_struct(&mut *caller)?.ok_or_else(not_a_number)?;
927    let Val::F64(bits) = st.field(&mut *caller, 1)? else {
928        return Err(not_a_number());
929    };
930    let length = f64::from_bits(bits);
931    if length.is_nan() || length <= 0.0 {
932        return Ok(0);
933    }
934    let length = length.trunc();
935    let invalid = || crate::runtime::host::range_error("Invalid array length");
936    if length > f64::from(u32::MAX) {
937        return Err(invalid());
938    }
939    usize::try_from(length as u32).map_err(|_| invalid())
940}
941
942async fn apply_map(
943    caller: &mut Caller<'_, StoreData>,
944    map_fn: &Option<ElementCallback<'_>>,
945    value: Val,
946    index: usize,
947) -> wasmtime::Result<Val> {
948    match map_fn {
949        Some(f) => f.call(caller, None, value, index).await,
950        None => Ok(value),
951    }
952}