Skip to content

Reduce heap space used by built in slots.#2445

Open
aardvark179 wants to merge 10 commits into
mozilla:masterfrom
aardvark179:aardvark179-slot-descriptors
Open

Reduce heap space used by built in slots.#2445
aardvark179 wants to merge 10 commits into
mozilla:masterfrom
aardvark179:aardvark179-slot-descriptors

Conversation

@aardvark179

@aardvark179 aardvark179 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

In production we're seen some cases where we have an extremely high number of function objects. The move away from IdScriptableObjects causes a significant increase in memory footprint as every function normally has 3 or 4 slots ('length', 'name', 'arity', and 'arguments'). The BuiltInSlot objects each had a shallow size of 56, and had to be help in (in our case) a ThreadSafeEmbeddedSlotMap which added the cost of that object, an array, and a stamped lock to the cost.

This PR separates our slot hierarchy into standard slots and compact slots, and introduces small immutable slot maps that can be used for functions. Compact slots have descriptors which can be shared between many slots. We change all the uses of built in slots to use these descriptors, and the creation of the normal properties in BaseFunction to create a small immutable slot map.

This gets the overhead for function properties down to 160 bytes which is a significant improvement. I believe we can improve this in future work by only generating slot objects when required by our APIs and get the footprint down to at most 32 bytes per function, and extend this immutable slot map concept to the majority of built in prototypes as well.

@aardvark179 aardvark179 changed the title WIP to reduce heap space used by built in slots. Reduce heap space used by built in slots. Jul 14, 2026
@aardvark179
aardvark179 marked this pull request as ready for review July 14, 2026 15:12

@gbrail gbrail left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just a few small questions, I see where this is going -- thanks!

DONTENUM | READONLY,
BaseFunction::nameGetter,
BaseFunction::nameSetter);
Slot<Scriptable> lengthSlot = LENGTH_DESCRIPTOR.createSlot(this, DONTENUM | READONLY);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to have the flags (DONTENUM | READONLY etc) in the descriptor rather than in the "createSlot" method itself? I imagine in most cases where we do this we will never change those flags, so you have less code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a good question. Flags are mutable without replacing a slot in the same way a value can be, which is why I've kept them out of the descriptor half for now. I think it's going to take a few rounds and some more experimentation to work out what the right split is going to end up being.

}
}

setMap(new ImmutableSmallSlotMap<>(lengthSlot, nameSlot, aritySlot, argsSlot));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What are the implications of making this an "immutable" map? Unless I missed something it sounds like it's kind of like making instances of this class and it's subclasses non-extendable, although by throwing a Java rather than a JavaScript exception. JavaScript code loves to muck with the properties of just about everything -- is that really going to work out?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Making this immutable means that any change to the slots causes the whole slot map to be replaced. This allows for thread safe versions to skip having a lock as they can do a CAS of the map.

Slot<Scriptable> lengthSlot = LENGTH_DESCRIPTOR.createSlot(this, DONTENUM | READONLY);
Slot<Scriptable> nameSlot = NAME_DESCRIPTOR.createSlot(this, DONTENUM | READONLY);
Slot<Scriptable> aritySlot = null;
Slot<Scriptable> argsSlot = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a way to do this with some sort of a Builder pattern instead of creating Slot instances directly? I tend to think of Slot as an implementation detail of ScriptableObject although maybe we're past that point already!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think a builder is a good idea, but I'm tempted to go one stage further and say we should build a map descriptor and make the map from that. I think it could then be extended to be used by class descriptors.

@aardvark179
aardvark179 force-pushed the aardvark179-slot-descriptors branch from 64d53e3 to b367743 Compare July 22, 2026 10:05
@aardvark179

Copy link
Copy Markdown
Contributor Author

Okay, created a descriptor API and a builder for that. Although attributes aren't yet part of slot descriptors themselves I have put them into the map descriptor. I'll experiment with how this looks if I try expanding it for our class descriptors as that will force passing values in as well just the owner.

@aardvark179
aardvark179 force-pushed the aardvark179-slot-descriptors branch from b367743 to 4f9a593 Compare July 22, 2026 10:12
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

Successfully merging this pull request may close these issues.

2 participants