Skip to content

Refinement of the Scalarization Process for Value Objects during Parsing and PEA#3

Draft
MichaelHaas99 wants to merge 59 commits into
mh/GR-57655from
mh/GR-57655-PEA
Draft

Refinement of the Scalarization Process for Value Objects during Parsing and PEA#3
MichaelHaas99 wants to merge 59 commits into
mh/GR-57655from
mh/GR-57655-PEA

Conversation

@MichaelHaas99

@MichaelHaas99 MichaelHaas99 commented Feb 8, 2026

Copy link
Copy Markdown
Owner

The main bullet points of this PR are:

  • make the Scalarization node floating and anchor it when necessary, no location identity is necessary
  • canonicalize Scalarization nodes
  • during PEA scalarize new occuring value objects as early as possible
  • no need anymore to replace nodes in the map with an InlineType node during parsing to propagate it down the IR
  • remove the InlineTypePlaceholder node
  • introduce a floating Valhalla object equals node (probably in the next PR)
  • tracking laravlness per field can be simplified to waiting for a constructor call or FinalFieldBarrier node for inlined constructors, so only a boolean state per object field should be scalarized

We need a way to propagate the Scalarization nodes for method arguments during inlining or inserted during merge processing in PEA through the graph. If that is not easily possible, I think we should move to the implementation of C2 and remove all changes from PEA. My solution would be:

Value objects from outside a compilation unit can be encountered with the following nodes with stamp value object:
Parameter (non OSR) nodes
LoadStatic node
LoadField node
Invoke node

They are non-larval and can be scalarized. Value objects within a compilation unit are normally virtual during PEA but will be materialized in case the constructor is not inlined. So value objects can also be scalarized if they are receiver of an Invoke node with a constructor call. If all constructors are inlined, it is virtual anyway during PEA.
What I mean with scalarization during PEA is that we alias the corresponding node with a virtual alias. The field values are directly loaded below the definition of the node.

A special case are OSR arguments as they may still be larval.
In Graal they are marked with the EntryProxy node and replace by an OSRLocal node after parsing. So how can we scalarize, or in other words know that they are non-larval?
We associate an alias to the Pi node having the OSRLocal node as its input with an object state being materialized and tracking the larval state. Whenever we encounter a node that requires the node to be non-larval and we have not encountered a constructor call or StoreField node in between, so the object state indicates larval, we know that the OSR node must have been non-larval from the beginning onwoards. As the Scalarization node is floating we can set the scalarized values in all object states, as we scalarize directly at the beginning.
When we reach a StoreField node we set the field to initialized in the object state.

Scalarizarion nodes should be made floating and anchored at the earliest possible position. We should rely on location identities as this is not necessary and allows GVN. Value objects are immutable, so it does not make any difference when a field is read, as long as it is non-larval.

As a consequence the input to a Scalarization node inserted during parsing should always be virtual.

@MichaelHaas99 MichaelHaas99 marked this pull request as draft February 8, 2026 12:12
obj = closure.getObjectState(this, cacheValue);
if (obj != null) {
assert !obj.isVirtual();
assert obj.isMaterialized();

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

only necessary due to a bug?

tool.createVirtualObject(virtualObject, state, Collections.emptyList(), getNodeSourcePosition(), false, this.oop, this.nonNull, isAllocatedOrNull);
for (int i = 0; i < state.length; i++) {
ValueNode entry = tool.getEntry(virtualObject, i);
ValueNode newEntry = tool.scalarize(entry);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

pass in the type of the value object to the scalarize method as well

* non-larval.
*/
if (!StampTool.isNullableInlineType(materialized, null) || isLarval()) {
entries = null;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

don't delete entries for value object, as they are strict and final

protected void scalarize(ValueNode node, PartialEscapeBlockState state, GraphEffectList effects, FixedNode position, GuardingNode guard){
tool.reset(state, node, position, effects);
VirtualInstanceNode newNode = scalarizeValueObject(node, state, true, guard);
this.addVirtualAlias(newNode, node);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

do a sanity check here, for some calls an alias should already exist

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Put the scalarized field values into all states where this virtual object is also present, and also into the merge state if it is present.

ValueNode entry = entryState[i];
if (StampTool.isNullableInlineType(entry, tool.getValhallaOptionsProvider())) {
VirtualInstanceNode newNode = scalarizeValueObject(entry, state, true, visited, guard, stopAtVirtual);
if(newNode != null) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

create an alias for materialized objects, such that we can scalarize them at any time.

@MichaelHaas99

MichaelHaas99 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

As we scalarize a value object as early as possible, if we land in a circle where we can't scalarize any further, we should create an alias for the remaining value object. So a virtual object pointing to a materialized object state. This allows us to scalarize the value object at the next possibility, we will only need to fill in the object state with the field values. The point for doing this, is to have an alias available. We should only do this for nodes that are ReadMultiValue nodes as the result from the current scalarization.

@MichaelHaas99 MichaelHaas99 Feb 9, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

A value object without an alias shouldn't be possible anymore. Throw an exception in this case as sanity check.

@MichaelHaas99

MichaelHaas99 commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

Can we maybe make the scalarization procedure in PEReadEliminationClosure more generic, e.g. we already generalize the behaviour for MultiValue nodes.
To do so we should mark larval nodes with an interface so: NewInstance, OSRLocal.
All non-larval nodes with a value class stamp can be scalarized at the first appearance.

@MichaelHaas99 MichaelHaas99 changed the title PEA update for Valhalla Refinement of the Scalarization Process for Value Objects during Parsing and PEA Feb 10, 2026
associateAlias(piNode, state, effects, lastFixedNode.next());
}
} else if (node instanceof ParameterNode param) {
if (StampTool.isNullableInlineType(param, tool.getValhallaOptionsProvider())) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

TODO: we are not allowed to scalarize here if we compile a constructor method

…ems if the value is part of a mapping before.
Alias new allocated objects.
Fix error with effects, don't use the index directly but the predecessor function.
Don't scalarize ReadMultiValue nodes as they can be scheduled above an invoke and cause a missing object state in the virtual mapping.
Add sanity checks to check if an alias is associated to a value object.
Only insert new nodes into scalarizationAliases map.
Update isLarval for Pi nodes.
Ask for alias on LoadField object.
Remove PEA in entry point.
Associate alias to loop phis before first processing.
In case a NewInstance node was not virtualized, make the object state larval.
…irtualize will always be called. Move scalarization into virtualize method.
…ation will handle them then. getAliasAndResolve should return the materialized value for non-value objects.
…ation will handle them then. getAliasAndResolve should return the materialized value for non-value objects. Add a verification after PEA that checks that LoadField node are removed and Scalarization nodes have no guard. Make Invoke, FinalFieldBarrier and Membar node Guarding nodes.
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.

1 participant