Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array/TypedArray initialization steps are redundant, inconsistent, and impractical to implement #3543

Open
gibson042 opened this issue Feb 26, 2025 · 0 comments

Comments

@gibson042
Copy link
Contributor

TypedArray, %TypedArray%.from, and Array.from have a lot of redundancy between their iterator vs. array-like consumption branches, but they are also unnecessarily divergent from each other.

API iterator input array-like input
new TypedArray(input)
  1. IteratorToList (no specified length limit but implementations don't even make it to 2**32)
  2. InitializeTypedArrayFromList
    1. AllocateTypedArrayBuffer with the requested length (throwing if greater than a threshold that is at most 2**53 - 1 but in actual implementations is much lower)
    2. For each element, ? Set
  1. InitializeTypedArrayFromArrayLike
    1. AllocateTypedArrayBuffer with length LengthOfArrayLike(input) (which clamps to non-negative integers ≤ 2**53 - 1)
    2. For each element, ? Get and ? Set
%TypedArray%.from(input)
  1. IteratorToList (no specified length limit but implementations don't even make it to 2**32)
  2. TypedArrayCreateFromConstructor with the requested length (receiver-aware but validates a TypedArray result)
  3. For each element, ? Set
  1. TypedArrayCreateFromConstructor with length LengthOfArrayLike(input)
  2. For each element, ? Get and ? Set
Array.from(input)
  1. ArrayCreate(0) or Construct(C) (receiver-aware)
  2. GetIteratorFromMethod
  3. For each iterator result, do
    1. If k ≥ 2**53 - 1, throw (an actual Array will never reach this point; a non-Array can in principle but actual implementations are non-conformant for e.g. Array.from.call(new Proxy({}, { defineProperty: () => true }), (function*(){ for(;;) yield 0; })()) well below even 2**32)
    2. ? IteratorStepValue
    3. If DONE, ? Set "length" (for an Array, this will throw for length ≥ 2**32) and return
    4. ? CreateDataPropertyOrThrow (for an Array, [[DefineOwnProperty]] requires also updating length up to 2**32 - 1 before switching to OrdinaryDefineOwnProperty)
  1. ArrayCreate(LengthOfArrayLike(input)) or Construct(LengthOfArrayLike(C)) (receiver-aware, and ArrayCreate rejects length ≥ 2**32)
  2. For each element, ? Get and ? CreateDataPropertyOrThrow (for an Array, [[DefineOwnProperty]] requires also updating length up to 2**32 - 1 before switching to OrdinaryDefineOwnProperty)
  3. ? Set "length" (for an Array, this will throw for length ≥ 2**32)

The two TypedArray APIs are very similar to each other, and the difference between branches inside of each is basically that iterator input is "fully" consumed up front to determine a length for allocation before entering a ? Set loop while non-iterator input is used to allocate with length LengthOfArrayLike before entering a ? Get + ? Set loop (i.e., the former performs all [fallible] reads before any [fallible] write, while the latter performs element reads and writes in [fallible] pairs before advancing). It would be nice to impose a threshold on the former (presumably implementation-defined but normatively capped at 2**53 - 1, corresponding with the eventual constraints of AllocateTypedArrayBuffer anyway), which (if desired) could also be used to consolidate the branches into something like

  1. If usingIterator is not undefined, then
    1. Let limit be TypedArraySizeLimit(C).
    2. Let arrayLike be ? IteratorToArrayLike(? GetIteratorFromMethod(source, usingIterator), limit).
  2. Else,
    1. NOTE: source is not an iterable object, so assume it is already an array-like object.
    2. Let arrayLike be ? ToObject(source).
  3. NOTE: When usingIterator is not undefined, arrayLike is not observable from ECMAScript code and each of the following calls upon it will neither call user code nor return an abrupt completion.
  4. Let len be ? LengthOfArrayLike(arrayLike).
  5. Let O be ? TypedArrayCreateFromConstructor(C, « 𝔽(len) »).
  6. Let k be 0.
  7. Repeat, while k < len,
    1. Let Pk be ! ToString(𝔽(k)).
    2. Let kValue be ? Get(arrayLike, Pk).
    3. If mapping is true, then
      1. Let mappedValue be ? Call(mapper, thisArg, « kValue, 𝔽(k) »).
    4. Else,
      1. Let mappedValue be kValue.
    5. Perform ? Set(O, Pk, mappedValue, true).
    6. Set k to k + 1.

Array.from could be left alone, or (because the iterator branch already operates by read/write pairs) similarly consolidated via use of a non-observable iterator. I prefer leaving it for now, but at any rate not applying normative changes (unless we actually want to support the currently non-conformant behavior that never reaches 2**53 - 1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant