Instead of building a .initializers closure array and calling into it from the constructor, inline directly into the constructor's own bytecode when every instance field has a plain name and a 0 initializer. Falls back to the existing .initializers behavior for any non-qualifying field, and caps at 8 inlinable fields.
Details
- Reviewers
yulia - Group Reviewers
spidermonkey-reviewers - Bugzilla Bug ID
- 2078425
- Merge Conflict Status
- Landing merges cleanly into the target branchMerges cleanly when merged against target branch tip 9a9ef08f919d, starting from 3c71a541b0e7.Last checked Fri, Oct 16, 5:39 PMDiff 1408129, based on 3c71a541b0e7
Diff Detail
- Repository
- rFIREFOXAUTOLAND firefox-autoland
- Branch
- HEAD
Event Timeline
(Reviewing Diff 1402379)
This patch shouldn't land as written. It stores parser atom indices in data that outlives the compilation that created them, so a lazily compiled constructor gets stale indices: that is a memory-safety and correctness problem. The emitter side also doesn't produce the intended savings yet.
| js/src/frontend/BytecodeEmitter.cpp | ||
|---|---|---|
| 10452 | This early return can never be taken. setupMemberInitializers() builds a new MemberInitializers from the class parse node and never calls setInlinedZeroFieldNames, so numInlinedZeroFields is always 0 here. The numInitializers == 0 case has already returned above. As written, the .initializers array and every initializer closure are still built, which defeats the allocation savings the patch is meant to deliver. Fix it by getting the inlined-field information from the constructor's FunctionBox or by computing it in setupMemberInitializers. Also finish the truncated comment on line 10451, remove its trailing whitespace, and fix the indentation of the if. | |
| js/src/frontend/Stencil.h | ||
| 1172 ↗ | (On Diff #1402379) | This adds 40 bytes to every ScriptStencilExtra, i.e. to every function in every stencil and in the XDR bytecode cache, even though only class constructors with fields use it. MemberInitializers itself grows from 4 bytes to more than 40 inside every SharedContext and PrivateScriptData. Move the inlined-field data out of line, for example into a side table keyed by script index or into the constructor's gcthings, so that scripts which don't use it pay nothing. |
| js/src/vm/SharedStencil.h | ||
| 822 | TaggedParserAtomIndex values only mean something inside the ParserAtomsTable of the compilation that made them. This array still gets copied into ScriptStencilExtra, then into BaseScript::memberInitializers_ (JSScript.cpp:2687), and then read back on delazification by ScopeContext::computeThisEnvironment (Stencil.cpp:531) and CompilationSyntaxParseCache (Stencil.cpp:1581). A lazily compiled constructor, or an arrow inside one that calls super(), is compiled against a new atom table. emitInitializeInstanceMembers then passes stale indices to emitAtomOp/emitGetPrivateName. That defines the wrong property, fails the private-name lookup, or reads past the end of the new table's entries (that vector is only bounds-checked in debug builds). Only the length-1/2 static and well-known atoms survive this. Fix it in one of two ways: re-intern the names when crossing the compilation boundary, the way displayAtom_ is handled with internInto; or never take the inlined path when member initializers come from a BaseScript/InputScope. | |
A couple of nits and questions, the general shape is looking good though!
| js/src/frontend/BytecodeEmitter.cpp | ||
|---|---|---|
| 10452 | nit: this looks like it isn't aligned correctly | |
| 11095 | This will return early true on line 11150 for a case where numInlinedZeroFields and numMemberInitializers are both set to 0. Is this intended behavior? It looks like it might unintentionally skip the code that follows this addition. | |
| js/src/frontend/Parser.cpp | ||
| 8059 | nit: Could this be hasInstanceDecorators (defaulting to false, with the conditions flipped where it’s used)? That avoids a negated name and mirrors classInitializedMembers, which makes the relationship easier to trace. | |
| 8061 | Is this sufficient to say that there are no decorators anywhere in the class? | |
| 8777 | nit: Since this is a bool, and is meant to answer a question, maybe the name should be 'isUnderCapacity'? Perhaps the name could be more specific about what that capacity is, for example something related to MaxInlinedZeroFields (maybe "hasMaxInlinedZeroFields" and then flip?) | |
| js/src/frontend/Stencil.h | ||
| 1192 ↗ | (On Diff #1402379) | should we be writing up to the max inlined fields? This will copy over a number of fields without data. We probably want the max to be serialized.numInlinedZeroFields? |
| 1203 ↗ | (On Diff #1402379) | same comment here, not sure if we want to write the max inlined zero fields. |
| js/src/vm/SharedStencil.h | ||
| 824 | maybe "isPrivateInlinedZeroField"? | |
| 858 | Is this necessary? I don't see it used. Maybe used in a later patch? | |
| 860 | nit: Maybe "hasOnlyInlinedZeroFields"? Something starting with "all" makes me think of getting a collection back. | |
Addressed Yulia's comments for now, will address reviewhelper-bot's stale atom index next, since that is a larger change.
| js/src/frontend/Parser.cpp | ||
|---|---|---|
| 8061 | It catches decorated instance fields and decorated methods, but not static fields or class decorators. I think that's enough for now since those only run when the class is defined (not on new). I checked with dis() that a class decorator and a static field decorator are still optimized, but a decorated instance field falls back. I also renamed the variable to hasInstanceDecorators | |
| js/src/jit-test/tests/fields/inline-zero-fields.js | ||
|---|---|---|
| 5 | This test is looking really good -- could you also add a test for the case where it is delazified? here is an example: https://searchfox.org/firefox-main/source/js/src/jit-test/tests/gc/bug-1590176.js -- but with inlined zero fields. | |
I removed the relazify guard in maybeRelazify as I initially thought constructors with inlined fields could be relazified and then recompiled without their field names. However, I've realized class constructors with fields can't be relazified so the guard never ran. The tests pass without it, and I also added a test for the delazify case