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

Investigate having read-only properties instead of accessor properties #2834

Closed
ptomato opened this issue May 2, 2024 · 8 comments
Closed

Comments

@ptomato
Copy link
Collaborator

ptomato commented May 2, 2024

In the investigation in #2786 we confirmed that public builtin functions are quite expensive in V8. One idea that surfaced during today's champions meeting, to reduce the number of builtins, would be to replace the following accessor prototype properties with readonly own data properties:

  • PlainDate, PlainDateTime, ZonedDateTime: calendarId, era, eraYear, year, month, monthCode, day, dayOfWeek, dayOfYear, weekOfYear, yearOfWeek, daysInWeek, daysInMonth, daysInYear, monthsInYear, inLeapYear
  • PlainTime, PlainDateTime, ZonedDateTime: hour, minute, second, millisecond, microsecond, nanosecond
  • Instant, ZonedDateTime: epochSeconds, epochMilliseconds, epochMicroseconds, epochNanoseconds
  • ZonedDateTime: timeZoneId, hoursInDay, offsetNanoseconds, offset
  • Duration: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, sign, blank
  • PlainYearMonth: calendarId, era, eraYear, year, month, monthCode, daysInYear, daysInMonth, monthsInYear, inLeapYear
  • PlainMonthDay: calendarId, monthCode, day

This would remove 103 builtins in total, about 1/3 of the builtins currently existing in Temporal.

Some of these were previously required to be accessor properties, because they would need to call calendar or time zone methods. With custom calendars and time zones being removed in #2826, it would now be possible for these to be own data properties. (If we wanted to add custom calendars and time zones in a future proposal, these would need to be converted back to accessor properties. I'm assuming that would be a web-compatible change. If not, then each constructor would need to make a bunch of calendar/time zone calls up front, which would not be very performant.)

To ensure that Temporal objects remained immutable, each type's constructor would need to define these own data properties, and they'd need to be non-writable and non-configurable.

Pros:

  • Hugely reducing the number of builtins which would be beneficial for (at least) V8.
  • If the own properties were also enumerable, Temporal objects would become spreadable into plain objects. This is something that we really wanted in the past, but dropped because it didn't seem feasible under the constraints that we had. (It is the reason that the with(), withPlainDate(), withPlainTime(), etc. methods exist.) This would change those constraints. It might be possible to remove those methods: instead of pdt.withPlainTime(pt) you could do PlainDateTime.from({...pdt, ...pt}). (Note: consider how to reconcile the calendars in withPlainDate)

Cons:

  • Worse runtime performance. Constructors would need to compute and define a lot of own properties, most of which might never be read.
  • Currently, Temporal constructors can't fail if they are called with the proper new.target. I could imagine that there'd be some way to make the property definitions fail by messing with Object.prototype.
  • Harder to re-add custom calendars and time zones in the future.
  • Deviating from the usual ECMA-262 pattern of storing data in internal slots and exposing it with getters is unusual.
  • Less flexibility for monkeypatching.

NOTE: There is currently no decision to go ahead with this. It might be a terrible idea. This issue thread is for discussing and investigating whether this is feasible.

@ljharb
Copy link
Member

ljharb commented May 2, 2024

Accessors allow for more robust code patterns that extract the accessor and call-bind it, directly exposing the values of internal slots even if the prototype is later modified.

Having a frozen property on a branded object that has some brand-checking mechanism would suffice, but it wouldn't be consistent with the rest of the language.

@littledan
Copy link
Member

I vaguely like the idea of continuing to use accessors, but I don't have an extremely strong argument. We've been moving in the accessor direction with ES6 (e.g. for RegExp flags), and this is aligned with how the web platform does things (WebIDL attributes are exposed as accessors). It feels somehow "safer" to me (more precisely, simpler to prove the safety) if the internal algorithms are only referencing internal slots and not frozen JS properties, even if in reality it is all the same amount safe (given that those algorithms would refuse to run if the brand isn't right).

@justingrant
Copy link
Collaborator

  • Worse runtime performance. Constructors would need to compute and define a lot of own properties, most of which might never be read.

Can engines optimize this overhead away? I'm not suggesting we should make this change, but was curious.

@anba
Copy link
Contributor

anba commented May 2, 2024

Can engines optimize this overhead away? I'm not suggesting we should make this change, but was curious.

SpiderMonkey has a thing called "resolve hook", which allows to lazily compute data properties. But I don't think we're eager to be forced to have to use this escape hatch, because it leads to multiple other issues:

@ptomato
Copy link
Collaborator Author

ptomato commented May 27, 2024

I mentioned during the last meeting that I've soured on this idea. Aside from the cons I mentioned above and the implementation difficulties that Anba brought up...

I'm not a fan of the potential for bugs introduced by things like PlainDateTime.from({ ...plainDateTime, ...plainDate }) as an idiom to replace withPlainDate (which we plan to remove). It's not obvious here that this is creating a new PlainDateTime in the ISO calendar, ignoring any calendars on plainDateTime and plainDate.

We could work around that by renaming calendarId to calendar. But then you get a potential conflict between calendars and it's not obvious that plainDate's calendar would win unless you realize that the calendar property is being spread. Also we named that property calendarId for a reason.

There are also extra problems if we were to use spreading as a wholesale replacement for with(), like PlainDate.from({ ...plainDate, month: 6 }) — this looks like it replaces the month with June, but actually it throws if the month wasn't already June, because the monthCode: "M06" property is spread from plainDate.

So I don't recommend this. It might be feasible, but we agreed to focus on subsetting, not redesigning. It'd require too much redesigning, even if there weren't practical implementation and performance concerns.

@gibson042
Copy link
Collaborator

gibson042 commented May 27, 2024

Not to push back on the "subset vs. redesign" consideration, but note that spreading only captures enumerable properties—I believe that if this were to be pursued, it would make sense for those to be just ISO/calendar/time fields, allowing spreading to stand in specifically for getISOFields or similar.

@ptomato
Copy link
Collaborator Author

ptomato commented Jun 5, 2024

Agreed, but we'd still have to address the calendar vs calendarId discrepancy somehow. And we'd have enumerable isoYear/isoMonth/isoDay properties on every object that would rarely get used except for spreading, but clogging up people's IDE autocomplete popups and causing confusion about which one to use.

@ptomato
Copy link
Collaborator Author

ptomato commented Jun 13, 2024

After the slate of removals that was approved on 2024-06-12, we are not going to pursue this unless it is really an obstacle for implementations to ship.

@ptomato ptomato closed this as completed Jun 13, 2024
@ptomato ptomato closed this as not planned Won't fix, can't repro, duplicate, stale Jun 13, 2024
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

6 participants