DEV Community

Serah Nderi
Serah Nderi

Posted on

Mid-Internship Progress Report: Achievements and Goals Ahead

What's Done, What's Changed, and What's Next

Photo by Lauren Mancke on Unsplash

In my last post I went in depth on my work at Mozilla, specifically my implementation of TC39 proposals and my current work on the Range Proposal. What was remaining was setting up BigIntsupport and setting a proper Prototype for the iterator returned by Iterator.range.

My implementation followed the specification, ie - create a separate prototype that inherits from the iterator, along with the necessary implementations for toString and next.

Background

Initially, I had an IteratorRangeGenerator* function (ie, step 18 of the Range proposal), that when called doesn't execute immediately, but returns a generator object which follows the iterator protocol. Inside the generator function you have yield statements which represents where the function suspends its execution and provides value back to the caller.

The generator will pause at each yield, and will not continue until the next method is called on the generator object that is created.
The NumericRangeIteratorPrototype is the object that holds the iterator prototype for the Numeric range iterator.

  • The next() method is added to the NumericRangeIteratorPrototype, when you call the next() method on an object created from NumericRangeIteratorPrototype, it doesn't directly return a value, but it makes the generator yield the next value in the series, effectively resuming the suspended generator.

  • The first time you invoke next() on the generator object created via IteratorRangeGenerator, the generator will run up to the first yield statement and return the first value.

  • When you invoke next() again, the NumericRangeIteratorNext() will be called. This method uses GeneratorResume(this), which means the generator will pick up right where it left off, continuing to iterate the next yield statement or until iteration ends.

Generator Alternative

After discussions with my mentors, I transitioned from a generator-based implementation to a more efficient slot-based approach. This change involved defining slots to store the state necessary for computing the next value. The reasons included:

  • Efficiency: Directly managing iteration state is faster than relying on generator functions.
  • Simplified Implementation: A slot-based approach eliminates the need for generator-specific handling, making the code more maintainable.
  • Better Alignment with Other Iterators: Existing built-in iterators such as StringIteratorPrototype and ArrayIteratorPrototype do not use generators in their implementations.

Final Implementation

The final approach stores state in reserved slots rather than relying on generator suspension. This change better fits SpiderMonkey's Iterator architecture and aligns with other built-in iterators such as StringIteratorPrototype and ArrayIteratorPrototype.

By moving away from generators, the implementation now:

  • Operates purely on numbers without additional complexity.
  • Avoids cleanup operations, simplifying state management.
  • Uses a dedicated prototype, ensuring consistency with other iterators.

Adding BigInt Support

Implementing BigInt support was straightforward from a specification perspective, but I encountered two blockers:
1. Handling Infinity Checks Correctly

The specification ensures that start is either a Number or a BigInt in steps 3.a and 4.a. However, step 5 states:

If start is +∞ or -∞, throw a RangeError.

Despite following this, my implementation still threw an error stating that start must be finite. After investigating, I found that the issue stemmed from using a self-hosted isFinite function.

The specification requires isFinite to throw a TypeError for BigInt, but the self-hosted Number_isFinite returns false instead. This turned out to be more of an implementation issue than a specification issue.

See Github discussion here.

Fix: Explicitly check that start is a number before calling isFinite:

// Step 5: If start is +∞ or -∞, throw a RangeError.
if (typeof start === 'number' && !Number_isFinite(start)) {
  ThrowRangeError(JSMSG_ITERATOR_RANGE_START_INFINITY);
}
Enter fullscreen mode Exit fullscreen mode

After making this adjustment, the implementation correctly handled infinite values.

2. Floating Point Precision Errors
When testing floating-point sequences, I encountered an issue where some decimal values were not represented exactly due to JavaScript's floating-point precision limitations. This caused incorrect test results.

There's a GitHub issue discussing this in depth. I implemented an approximatelyEqual function to compare values within a small margin of error.

Fix: Using approximatelyEqual in tests:

const resultFloat2 = Array.from(Iterator.range(0, 1, 0.2));
approximatelyEqual(resultFloat2, [0, 0.2, 0.4, 0.6, 0.8]);
Enter fullscreen mode Exit fullscreen mode

This function ensures that minor precision errors do not cause test failures, improving floating-point range calculations.


This project has been an incredible learning experience, allowing me to deep-dive into SpiderMonkey's internals and improve my understanding of TC39 proposals. I'm excited to continue refining this work and collaborating with the team to ensure an optimal implementation for Iterator.range.

Stay tuned for further updates!

You may also like:

Navigating TC39 Proposals: From Error Handling to Iterator.range

Decoding Open Source: Vocabulary I've Learned on My Outreachy Journey

Want a Remote Internship Working on Free Software?

Top comments (2)

Collapse
 
kate_rasheed_ba482a073848 profile image
Kate Rasheed

Beautifully written with illustrations. Weldone Serah.

Collapse
 
mundianderi profile image
Serah Nderi

Thank you.