Skip to content
This repository has been archived by the owner on Oct 24, 2023. It is now read-only.

Account for resize-in-the-middle for TA methods #75

Merged
merged 1 commit into from
Nov 1, 2021

Conversation

syg
Copy link
Collaborator

@syg syg commented Sep 17, 2021

Closes #73

At a high-level:

  • Lengths are cached at the beginning of methods.
  • Only in-bounds elements are copied.

Together, this means that the result TAs have the snapshotted length at
the beginning of the method. If the source TA's buffer shrunk due to
the species constructor, the elements between [newShrunkLen,
originalLen) in the result TA remain 0.

This is an alternative solution to #74. In #74, elements between
[newShrunkLen, originalLen) are set to the result of OOB [[Get]]s on the
source TA. While doing so is maximally consistent with the Array
versions of these methods, it turns out to be really weird for
floating-point TAs. For OOB [[Get]]s on TAs returns undefined, and
setting undefined in Float32Arrays and Float64Arrays coerces to NaN.
This means extra complexity and code implementations have to do for an
edge case (resize-in-the-middle) that's bad practice anyway.

@syg
Copy link
Collaborator Author

syg commented Sep 17, 2021

This came out of a discussion with @marjakh, who convinced me the NaN behavior with floats is too weird for not a lot of gain.

PTAL @marjakh and @jugglinmike. I plan to present the two alternative semantics to TC39 next plenary, leaning towards this one for its simplicity. @jugglinmike, incidentally this would make #72 much simpler to fix. Just make sure to only copy the in-bounds portion of the source buffer, no need to do the weird Get-and-Set dance we chatted about.

I believe this behavior is superior to the one in #74 despite not being consistent with the Array versions for several reasons:

  • Arrays are heterogenous, so undefined resulting from OOB loads passes through to the result Array without coercion.
  • Arrays can have holes, so a .length-mutation-in-the-middle can result in prototype loads, which is terrible but possible. TAs don't have holes, so all OOB loads will be undefined. If we adopt the behavior in Account for resize-in-the-middle in TA methods #74, people might still reasonably assume that the OOB get-and-set undefined dance is unobservable and will be coerced to 0 in all cases, and they would be wrong.
  • Very easy to miss the undefined-to-NaN coercion in implementations.
  • This PR is simpler.

@syg
Copy link
Collaborator Author

syg commented Sep 17, 2021

Concretely, the difference in behavior between #74 and this PR is:

// Adopted from a test written by @marjakh 

const rab = new ArrayBuffer(4 * 8, { maxByteLength: 1024 });
const ta = new Float64Array(rab, 0, 4);
for (let i = 0; i < 4; ++i) {
  ta[i] = i;
}

class MyArray extends Uint32Array {
  constructor(...params) {
    super(...params);
   }

  static get [Symbol.species]() {
    rab.resize(2 * 8);
    return Uint32Array;
  }
};

const a = new MyArray(rab);
const sliced = a.slice();

assert(a.length === 2); // Since it's length-tracking
assert(sliced.length === 4); // Since snapshotted length
assert(sliced[0] === 0);
assert(sliced[1] === 1);

if (hasPR74Behavior) {
  // This is because the slice loop does the equivalent of sliced[2] = a[2].
  assert(Number.isNaN(sliced[2]));
  assert(Number.isNaN(sliced[3]));
}

if (hasPR75Behavior) {
  // This is because the slice loop stops at index 1 due to resize.
  assert(sliced[2] === 0);
  assert(sliced[3] === 0);
}

Copy link
Contributor

@jugglinmike jugglinmike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your argument about why this approach is preferable. I'll wait to update gh-72 until we get consensus at TC39. Same goes for the additions to Test262 that @marjakh has requested.

@marjakh
Copy link

marjakh commented Sep 20, 2021

Curiously, your illustrative example has the same bug I originally had in my test and which confused me for several hours. :)

With the code in your example:
const a = new MyArray(rab); // this calls MyArray ctor
const sliced = a.slice(); // this calls MyArray ctor

and since the MyArray ctor resizes unconditionally, the array is already resized before we call slice().

(Also FYI @jugglinmike , in case you'll later write the test262 test based on this example.)

@syg
Copy link
Collaborator Author

syg commented Sep 20, 2021

Curiously, your illustrative example has the same bug I originally had in my test and which confused me for several hours. :)

Oh wow yeah, let me make the example more sneaky...

Edit: Changed it to resize inside a Symbol.species getter instead of in the constructor.

@syg
Copy link
Collaborator Author

syg commented Oct 25, 2021

This reached consensus at the October 2021 TC39.

Closes #73

At a high-level:

- Lengths are cached at the beginning of methods.
- Only in-bounds elements are copied.

Together, this means that the result TAs have the snapshotted length at
the beginning of the method. If the source TA's buffer shrunk due to
the species constructor, the elements between [newShrunkLen,
originalLen) in the result TA remain 0.

This is an alternative solution to #74. In #74, elements between
[newShrunkLen, originalLen) are set to the result of OOB [[Get]]s on the
source TA.  While doing so is maximally consistent with the Array
versions of these methods, it turns out to be really weird for
floating-point TAs. For OOB [[Get]]s on TAs returns `undefined`, and
setting `undefined` in Float32Arrays and Float64Arrays coerces to NaN.
This means extra complexity and code implementations have to do for an
edge case (resize-in-the-middle) that's bad practice anyway.
@syg syg force-pushed the proto-methods-resize-fix-alt branch from 6289d75 to adb0e3e Compare November 1, 2021 22:12
@syg
Copy link
Collaborator Author

syg commented Nov 1, 2021

Rebased.

@syg syg merged commit 4ca48c4 into master Nov 1, 2021
@syg syg deleted the proto-methods-resize-fix-alt branch November 1, 2021 22:12
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

slice & other TypedArray.prototype funcs: shouldn't we reload the length?
3 participants