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

Normative: re-use IteratorResult objects in some iterator helpers #3489

Closed
wants to merge 1 commit into from

Conversation

michaelficarra
Copy link
Member

This re-uses IteratorResult objects when it's possible to just pass the object through. There are currently 3 cases of this: take/drop/filter. See related PR for the iterator sequencing proposal, which also has this opportunity: tc39/proposal-iterator-sequencing#18

I actually don't like doing this for filter since filter observes the value, which would mean triggering a value getter both for filtering and by the eventual consumer.

@michaelficarra michaelficarra added normative change Affects behavior required to correctly evaluate some ECMAScript source text needs consensus This needs committee consensus before it can be eligible to be merged. labels Nov 20, 2024
@michaelficarra michaelficarra force-pushed the re-use-IteratorResult-objects branch from 62486f2 to fde82f1 Compare November 20, 2024 18:27
@nicolo-ribaudo
Copy link
Member

which would mean triggering a value getter both for filtering and by the eventual consumer.

Why do you consider this to only be bad for the .value getter and not for the .done getter, which would be double-triggered by all those methods?

@michaelficarra
Copy link
Member Author

I think an expensive value getter is more likely than an expensive done getter.

@michaelficarra
Copy link
Member Author

michaelficarra commented Nov 21, 2024

@nicolo-ribaudo Also, someone might be surprised that the resulting iterator yields values for which the predicate would return false. But we might just want to say 🤷‍♂️ getters admit weirdness.

edit: I've added a slide to the upcoming presentation covering interactions with getters.

@dminor
Copy link

dminor commented Nov 21, 2024

@anba Do you have an opinion on this change?

@michaelficarra michaelficarra added the needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 label Nov 21, 2024
@anba
Copy link
Contributor

anba commented Nov 22, 2024

This change is possibly difficult to implement for JSC and SM, because both use self-hosted JS for their implementation and the proposed changes can't be easily implemented in JS. (yield* can't be used, because it performs one final IteratorValue when done, which is user-detectable.)

I guess this also applies to tc39/proposal-iterator-sequencing#18.

@michaelficarra
Copy link
Member Author

because both use self-hosted JS for their implementation and the proposed changes can't be easily implemented in JS

Do you mean that they can't be easily implemented in JS at all, or do you mean using generators in JS? It seems manual iteration in JS should work fine.

yield* can't be used, because it performs one final IteratorValue when done, which is user-detectable

I'm open to also requiring the final IteratorValue to fully match a simpler generators/yield* implementation. One extra unnecessary property access at the end seems to be worth the savings of a whole extra object per item yielded.

@michaelficarra
Copy link
Member Author

Ping @anba. It'd be great to have your feedback on the above points before plenary next week.

@anba
Copy link
Contributor

anba commented Dec 2, 2024

Do you mean that they can't be easily implemented in JS at all, or do you mean using generators in JS? It seems manual iteration in JS should work fine.

When using generators. It's probably less of a problem when using normal functions instead of generators, but then it's necessary to duplicate the complete generator state machinery.

I'm open to also requiring the final IteratorValue to fully match a simpler generators/yield* implementation. One extra unnecessary property access at the end seems to be worth the savings of a whole extra object per item yielded.

I'm not sure about the performance impact when avoiding a single object allocation (per iteration). Local testing showed mixed results, but it's a bit difficult to estimate general performance changes, because I was only testing on SpiderMonkey. Getting results for a possible performance improvement is going to be difficult anyway, because:

  1. SM doesn't fully support generators in its top tier compiler.
  2. JSC doesn't pass through iterator objects in yield*.
  3. V8 doesn't use generators at all for their Iterator Helpers implementation.

For example let's see what will change for Iterator.prototype.drop. The current SM implementation uses a normal for-of loop for the iteration:

for (var value of allowContentIterWithNext(iterator, nextMethod)) {
  if (remaining-- <= 0) {
    yield value;
  }
}

allowContentIterWithNext is an internal helper to be able to use for-of loops without duplicate property lookups. It's more or less similar to:

function allowContentIterWithNext(iterator, nextMethod) {
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      return nextMethod.call(iterator);
    },
    return() {
      // Elided for simplicity.
    },
  };
}

Rewriting the for-of loop using yield* will look like this in SM:

function iteratorNext() {
  while (remaining > 0) {
    var result = callContentFunction(nextMethod, iterator);
    if (!IsObject(result)) {
      ThrowTypeError(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next");
    }
    if (result.done) {
      return {done: true, value: undefined};
    }
    remaining -= 1;
  }
  return callContentFunction(nextMethod, iterator);
}

yield* allowContentIterWithNext(iterator, iteratorNext);

Changes:

  1. An additional indirection through the new closure iteratorNext. That means, unless the compiler can fully inline iteratorNext, additional call overhead and overhead when accessing the iterator, nextMethod, and remaining variables.
  2. Manual calls to next, including type checks and error reporting when remaining > 0.
  3. And in my opinion a more complicated implementation.

@michaelficarra
Copy link
Member Author

At plenary today, we have decided not to do this, setting precedent for other helpers on Iterator.prototype.

@michaelficarra michaelficarra deleted the re-use-IteratorResult-objects branch December 2, 2024 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs consensus This needs committee consensus before it can be eligible to be merged. needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 normative change Affects behavior required to correctly evaluate some ECMAScript source text
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants