Make listing/mapping typecasts more PE-friendly (#1837)

This rewrites the listing/mapping typecast nodes to be more friendly
to partial-evaluation.

This is because the compiler bails out of deeply recursive calls.
This commit is contained in:
Daniel Chao
2026-09-02 10:00:59 -07:00
committed by GitHub
parent adee565a8c
commit 9f28c1cb11
3 changed files with 25 additions and 27 deletions
@@ -51,7 +51,7 @@ public abstract class ElementOrEntryNode extends RegularMemberNode {
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
var result = bodyNode.executeGeneric(frame);
return VmUtils.shouldRunTypeCheck(frame)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null)
: result;
}
@@ -62,7 +62,7 @@ public abstract class ElementOrEntryNode extends RegularMemberNode {
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
var result = bodyNode.executeGeneric(frame);
return VmUtils.shouldRunTypeCheck(frame)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null)
: result;
}
@@ -55,37 +55,35 @@ public abstract class VmListingOrMapping extends VmObject {
this.typeCheckOwner = typeCheckOwner;
}
// Recursively executes type casts between `owner` and `this` and returns the resulting value.
// Executes type casts for every parent between `owner` and `this` and returns the resulting
// value.
public final Object executeTypeCasts(
Object value,
VmObjectLike owner,
IndirectCallNode callNode,
// if non-null, a stack frame for this member is inserted if a type cast fails
@Nullable ObjectMember member,
// Next type cast to be performed by the caller.
// Avoids repeating the same type cast in some cases.
@Nullable ListingOrMappingTypeCastNode nextTypeCastNode) {
var newNextTypeCastNode = typeCastNode != null ? typeCastNode : nextTypeCastNode;
Object result;
if (this == owner) {
result = value;
} else {
assert parent != null;
result =
((VmListingOrMapping) parent)
.executeTypeCasts(value, owner, callNode, member, newNextTypeCastNode);
}
if (typeCastNode == null || typeCastNode == nextTypeCastNode) return result;
var callTarget = typeCastNode.getCallTarget();
try {
return callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result);
} catch (VmException e) {
CompilerDirectives.transferToInterpreter();
if (member != null) {
VmUtils.insertStackFrame(member, callTarget, e);
@Nullable ObjectMember member) {
var result = value;
VmObject parent = this;
ListingOrMappingTypeCastNode prevTypeCastNode = null;
while (parent != null && parent != owner) {
var obj = (VmListingOrMapping) parent;
if (obj.typeCastNode != null && obj.typeCastNode != prevTypeCastNode) {
var callTarget = obj.typeCastNode.getCallTarget();
try {
result = callNode.call(callTarget, obj.typeCheckReceiver, obj.typeCheckOwner, result);
prevTypeCastNode = obj.typeCastNode;
} catch (VmException e) {
CompilerDirectives.transferToInterpreter();
if (member != null) {
VmUtils.insertStackFrame(member, callTarget, e);
}
throw e;
}
}
throw e;
parent = parent.parent;
}
return result;
}
@Override
@@ -396,7 +396,7 @@ public final class VmUtils {
&& owner instanceof VmListingOrMapping) {
// `owner instanceof VmListingOrMapping` guards against
// PropertiesRenderer amending VmDynamic with VmListing (hack?)
result = listingOrMapping.executeTypeCasts(constantValue, owner, callNode, member, null);
result = listingOrMapping.executeTypeCasts(constantValue, owner, callNode, member);
}
receiver.setCachedValue(memberKey, result);