Switch inferred new {} expressions in method args to using frame slots (#1857)

This changes the logic for inferring `new {}` expressions in method
arguments to using regular frame slots, instead of auxiliary slots.

Overall, this is a performance improvement: no looking up auxiliary slot
numbers, which makes this code much more PE friendly.

* At parse time, get or create a frame slot that holds the method
* Introduce `ArgInfo` record (preserve primitive int) over using `Pair`
* Remove unneeded `isImplicitNewExpr` method; this is not needed because
  we can just keep track of the method slot on the frame descriptor
  builder.
This commit is contained in:
Daniel Chao authored and GitHub committed 2026-09-22 12:12:57 -07:00
1 parent 7ed88e250b
commit d5a7dc021e
40 files changed
+422 -202

No files matched your search

@@ -528,14 +528,21 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var exprs = type.getExprs();
var constraints = new TypeConstraintNode[exprs.size()];
for (var i = 0; i < constraints.length; i++) {
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
var initialState = scope.frameDescriptorBuilder.state();
var expr = visitExpr(exprs.get(i));
var writesFrameSlotVars =
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
// if a constraint expression writes to frame slots (only known case: is a `let` expr),
// create a new root node and execute the constraint within this root node
// e.g. String(let (x = this) x.length > 5)
if (writesFrameSlotVars) {
var needsFrameSlots = scope.frameDescriptorBuilder.state() > initialState;
// If a constraint expression writes to frame slots, create a new root node and execute
// the constraint within this root node.
//
// This can happen if the constraint contains a let expression, or if it contains a
// method call argument with an inferred `new {}`, e.g.
//
// * String(let (x = this) x.length > 5)
// * String(isFoo(new {}))
//
// We only need to do this if we are in a typealias because they get inlined into their
// usage site (they don't influence the frame descriptor of where they are inlined).
if (needsFrameSlots && symbolTable.isInTypeAliasScope) {
expr = getExprWithinCustomThis(scope, expr);
}
constraints[i] = TypeConstraintNodeGen.create(expr.getSourceSection(), expr);
@@ -894,55 +901,45 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return new InvokeQualifiedObjectMethodNode(
sourceSection,
identifier,
argInfo.getFirst(),
argInfo.arguments,
needsConst,
getModuleNode,
argInfo.getSecond());
argInfo.methodSlot);
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeQualifiedClassMethodNode(
sourceSection,
identifier,
argInfo.getFirst(),
argInfo.arguments,
needsConst,
getModuleNode,
argInfo.getSecond());
argInfo.methodSlot);
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
identifier,
argInfo.getFirst(),
argInfo.arguments,
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
argInfo.getSecond(),
argInfo.methodSlot,
getModuleNode,
GetClassNodeGen.create(null));
}
if (method.isObjectMethod()) {
return new InvokeLexicalObjectMethodNode(
sourceSection,
identifier,
levelsUp,
argInfo.getFirst(),
needsConst,
argInfo.getSecond());
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeLexicalClassMethodNode(
sourceSection,
identifier,
levelsUp,
argInfo.getFirst(),
needsConst,
argInfo.getSecond());
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
identifier,
argInfo.getFirst(),
argInfo.arguments,
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
argInfo.getSecond(),
argInfo.methodSlot,
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
GetClassNodeGen.create(null));
} else if (resolution instanceof ImplicitBaseMethod) {
@@ -969,20 +966,22 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
createSourceSection(expr),
method,
new ConstantValueNode(baseModule),
argInfo.getFirst(),
argInfo.getSecond());
argInfo.arguments,
argInfo.methodSlot);
}
} else if (resolution instanceof ImplicitThisMethod) {
var isCustomThis = scope.isCustomThisScope();
var needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThis;
var argInfo = visitArgumentList(argList);
var arguments = argInfo.arguments;
var methodSlot = argInfo.methodSlot;
return InvokeMethodVirtualNodeGen.create(
sourceSection,
org.pkl.core.runtime.Identifier.get(name),
argInfo.getFirst(),
arguments,
MemberLookupMode.IMPLICIT_THIS,
needsConst,
argInfo.getSecond(),
methodSlot,
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThis),
GetClassNodeGen.create(null));
} else {
@@ -1081,7 +1080,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var parent = expr.parent();
var scope = symbolTable.getCurrentScope();
// keep in sync with isImplicitNewExpr
while (parent instanceof IfExpr ifExpr
&& (ifExpr.getThen() == child || ifExpr.getEls() == child)
|| parent instanceof TraceExpr
@@ -1122,8 +1120,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
// - generic methods: pkl.base#Pair(), etc.
// these will throw cannotInferParent at runtime
var argIndex = argumentList.getArguments().indexOf(child);
var methodSlot = symbolTable.getCurrentScope().frameDescriptorBuilder.getOrAddMethodSlot();
inferredParentNode =
InferParentWithinMethodArgumentNodeGen.create(sourceSection, language, argIndex);
InferParentWithinMethodArgumentNodeGen.create(
sourceSection, language, argIndex, methodSlot);
} else {
throw exceptionBuilder()
.evalError("cannotInferParent")
@@ -1160,7 +1160,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var argInfo = visitArgumentList(argCtx);
return InvokeSuperMethodNodeGen.create(
sourceSection, memberName, argInfo.getFirst(), needsConst, argInfo.getSecond());
sourceSection, memberName, argInfo.arguments, needsConst, argInfo.methodSlot);
}
// superproperty call
@@ -1340,13 +1340,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
descriptorBuilder,
scope -> {
var exprNode = visitExpr(expr.getExpr());
var parameterTypeNodes = doVisitParameterTypes(params);
var functionNode =
new UnresolvedFunctionNode(
language,
scope.buildFrameDescriptor(),
new Lambda(sourceSection, scope.getQualifiedName()),
paramCount,
doVisitParameterTypes(params),
parameterTypeNodes,
null,
exprNode);
@@ -1479,18 +1480,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) {
var keyNode =
symbolTable.enterEagerGenerator(
(scp) ->
symbolTable.enterCustomThisScope(
scope -> {
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
var expr = visitExpr(ctx.getPred());
var writesFrameSlotVars =
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
if (writesFrameSlotVars) {
return getExprWithinCustomThis(scope, expr);
}
return expr;
}));
(scp) -> symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred())));
var member =
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
var isFrameStored =
@@ -2179,6 +2169,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.build();
}
}
var parameterTypes = doVisitParameterTypes(paramListCtx);
var typeAnnotationNode = visitTypeAnnotation(entry.getTypeAnnotation());
return new UnresolvedMethodNode(
language,
@@ -2192,8 +2184,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
scope.getQualifiedName(),
paramCount,
typeParameters,
doVisitParameterTypes(paramListCtx),
visitTypeAnnotation(entry.getTypeAnnotation()),
parameterTypes,
typeAnnotationNode,
isMethodReturnTypeChecked,
bodyNode);
});
@@ -2329,32 +2321,21 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
@Override
public Pair<ExpressionNode[], Boolean> visitArgumentList(ArgumentList argumentList) {
public ArgInfo visitArgumentList(ArgumentList argumentList) {
var args = argumentList.getArguments();
var res = new ExpressionNode[args.size()];
var argsRequireInference = false;
for (int i = 0; i < res.length; i++) {
var arguments = new ExpressionNode[args.size()];
var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder;
var initialState = frameDescriptorBuilder.state();
for (var i = 0; i < arguments.length; i++) {
var expr = args.get(i);
res[i] = visitExpr(expr);
argsRequireInference = argsRequireInference || isImplicitNewExpr(expr);
arguments[i] = visitExpr(expr);
}
return Pair.of(res, argsRequireInference);
}
private static boolean isImplicitNewExpr(Expr expr) {
// keep in sync with doVisitNewExprWithInferredParent
if (expr instanceof NewExpr newExpr && newExpr.getType() == null) {
return true;
} else if (expr instanceof IfExpr ifExpr) {
return isImplicitNewExpr(ifExpr.getThen()) || isImplicitNewExpr(ifExpr.getEls());
} else if (expr instanceof TraceExpr traceExpr) {
return isImplicitNewExpr(traceExpr.getExpr());
} else if (expr instanceof ParenthesizedExpr parenthesizedExpr) {
return isImplicitNewExpr(parenthesizedExpr.getExpr());
} else if (expr instanceof LetExpr letExpr) {
return isImplicitNewExpr(letExpr.getExpr());
}
return false;
var argsRequiresInference = frameDescriptorBuilder.state() > initialState;
var methodSlot =
argsRequiresInference
? symbolTable.getCurrentScope().frameDescriptorBuilder.getMethodSlot()
: -1;
return new ArgInfo(arguments, methodSlot);
}
@Override
@@ -2439,9 +2420,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
parametersDescriptorAndBindings == null
? new FrameSlotVariable[0]
: parametersDescriptorAndBindings.second;
var parametersFrameDescriptorBuilder =
parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.first;
return symbolTable.enterObjectScope(
bindings,
parametersFrameDescriptorBuilder,
(scope) -> {
addObjectNamesToScope(scope, body);
var objectMembers = body.getMembers();
@@ -2451,11 +2435,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
var sourceSection = createSourceSection(body.parent());
// must visit parameter types before building the parameters frame descriptor, since
// constraints (e.g. containing `let` expressions) may add further slots to it
var parameterTypes = doVisitParameterTypes(body);
var parametersDescriptor =
parametersDescriptorAndBindings == null
? null
: parametersDescriptorAndBindings.first.build();
var parameterTypes = doVisitParameterTypes(body);
var members = EconomicMaps.<Object, ObjectMember>create();
var elements = new ArrayList<ObjectMember>();
@@ -2496,7 +2482,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|| memberCtx instanceof ObjectSpread;
// bail out and create GeneratorObjectLiteralNode instead
// (but can't we easily reuse members/elements/keyNodes/values?)
return doVisitGeneratorObjectBody(body, parentNode);
return doVisitGeneratorObjectBody(
body, parentNode, parametersDescriptor, parameterTypes);
}
var currentScope = symbolTable.getCurrentScope();
@@ -2677,6 +2664,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.build();
}
}
var typeAnnotation = visitTypeAnnotation(typeAnn);
ExpressionNode bodyNode;
if (body != null && !body.isEmpty()) { // foo { ... }
@@ -2713,7 +2701,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
scope.buildFrameDescriptor(),
modifiers,
bodyNode,
visitTypeAnnotation(typeAnn))
typeAnnotation)
: VmUtils.createObjectProperty(
language,
sourceSection,
@@ -2836,6 +2824,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
scope.getName(),
scope.getQualifiedName());
var body = visitExpr(expr);
var typeNode = visitTypeAnnotation(typeAnnotation);
var parameterTypeNodes = doVisitParameterTypes(paramList);
var node =
new ObjectMethodNode(
language,
@@ -2843,8 +2833,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
member,
body,
paramList.getParameters().size(),
doVisitParameterTypes(paramList),
visitTypeAnnotation(typeAnnotation));
parameterTypeNodes,
typeNode);
member.initMemberNode(node);
return member;
@@ -2852,10 +2842,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
private GeneratorObjectLiteralNode doVisitGeneratorObjectBody(
ObjectBody body, ExpressionNode parentNode) {
var parametersDescriptorBuilderAndFrameSlotVariables =
createFrameDescriptorBuilderAndSlotVariables(body);
var parameterTypes = doVisitParameterTypes(body);
ObjectBody body,
ExpressionNode parentNode,
@Nullable FrameDescriptor parametersDescriptor,
UnresolvedTypeNode[] parameterTypes) {
var memberNodes = doVisitGeneratorMemberNodes(body.getMembers());
var currentScope = symbolTable.getCurrentScope();
//noinspection ConstantConditions
@@ -2864,9 +2854,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
language,
currentScope.getQualifiedName(),
currentScope.isCustomThisScope(),
parametersDescriptorBuilderAndFrameSlotVariables == null
? null
: parametersDescriptorBuilderAndFrameSlotVariables.first.build(),
parametersDescriptor,
parameterTypes,
memberNodes,
parentNode);
@@ -2944,10 +2932,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
InvokeMethodVirtualNodeGen.create(
sourceSection,
functionName,
argInfo.getFirst(),
argInfo.arguments,
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
argInfo.getSecond(),
argInfo.methodSlot,
PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver),
GetClassNodeGen.create(null)));
}
@@ -2956,10 +2944,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return InvokeMethodVirtualNodeGen.create(
sourceSection,
functionName,
argInfo.getFirst(),
argInfo.arguments,
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
argInfo.getSecond(),
argInfo.methodSlot,
receiver,
GetClassNodeGen.create(null));
}
@@ -3340,4 +3328,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
return false;
}
public record ArgInfo(ExpressionNode[] arguments, int methodSlot) {}
}
@@ -211,9 +211,14 @@ public final class SymbolTable {
}
public <T> T enterObjectScope(
FrameSlotVariable[] bindings, Function<ObjectScope, T> nodeFactory) {
return doEnter(
new ObjectScope(currentScope, bindings, currentScope.frameDescriptorBuilder), nodeFactory);
FrameSlotVariable[] bindings,
@Nullable FrameDescriptorBuilder parametersFrameDescriptorBuilder,
Function<ObjectScope, T> nodeFactory) {
var frameDescriptorBuilder =
parametersFrameDescriptorBuilder != null
? parametersFrameDescriptorBuilder
: currentScope.frameDescriptorBuilder;
return doEnter(new ObjectScope(currentScope, bindings, frameDescriptorBuilder), nodeFactory);
}
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
@@ -861,6 +866,9 @@ public final class SymbolTable {
// A generator scope that is resolved eagerly and one level above
public static final class EagerGeneratorScope extends Scope {
private static FrameDescriptorBuilder getFrameDescriptorBuilder(Scope parent) {
if (parent instanceof ObjectScope objectScope && objectScope.hasParams()) {
return parent.frameDescriptorBuilder;
}
var grandParent = parent.parent;
assert grandParent != null;
return grandParent.frameDescriptorBuilder;
@@ -32,8 +32,8 @@ public abstract sealed class AbstractInvokeLexicalMethodNode
int levelsUp,
ExpressionNode[] argumentNodes,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, methodName, argumentNodes, needsConst, argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
this.levelsUp = levelsUp;
}
@@ -48,8 +48,8 @@ public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, argumentNodes, argsRequireInference);
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
this.methodName = methodName;
this.needsConst = needsConst;
this.isConstChecked = false;
@@ -15,44 +15,32 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.runtime.VmUtils;
public abstract class AbstractInvokeMethodNode extends ExpressionNode {
@Children protected final ExpressionNode[] argumentNodes;
protected final boolean argsRequireInference;
protected final int methodSlot;
public AbstractInvokeMethodNode(
SourceSection sourceSection, ExpressionNode[] argumentNodes, boolean argsRequireInference) {
SourceSection sourceSection, ExpressionNode[] argumentNodes, int methodSlot) {
super(sourceSection);
this.argumentNodes = argumentNodes;
this.argsRequireInference = argsRequireInference;
}
@TruffleBoundary
private int getMethodSlot(FrameDescriptor frameDescriptor) {
// can't store the slot id as this node may be called from different root nodes
// (see constraints14 snippet)
return frameDescriptor.findOrAddAuxiliarySlot(VmUtils.METHOD_FRAME_SLOT_ID);
this.methodSlot = methodSlot;
}
@ExplodeLoop
protected Object[] evalArgs(
VirtualFrame frame, @Nullable Method method, Object owner, @Nullable Object receiver) {
int methodSlot = -1;
Object prevMethod = null;
if (argsRequireInference) {
methodSlot = getMethodSlot(frame.getFrameDescriptor());
prevMethod = frame.getAuxiliarySlot(methodSlot);
frame.setAuxiliarySlot(methodSlot, method);
if (methodSlot > -1) {
prevMethod = frame.getObject(methodSlot);
frame.setObject(methodSlot, method);
}
var args = new Object[2 + argumentNodes.length];
@@ -64,8 +52,8 @@ public abstract class AbstractInvokeMethodNode extends ExpressionNode {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
} finally {
if (argsRequireInference) {
frame.setAuxiliarySlot(methodSlot, prevMethod);
if (methodSlot > -1) {
frame.setObject(methodSlot, prevMethod);
}
}
@@ -32,8 +32,8 @@ public abstract sealed class AbstractInvokeQualifiedMethodNode
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
boolean argsRequireInference) {
super(sourceSection, methodName, argumentNodes, needsConst, argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
this.getReceiverNode = getReceiverNode;
}
@@ -16,41 +16,30 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmUtils;
public abstract class InferParentWithinMethodArgumentNode
extends AbstractInferParentFromMethodNode {
private final int argIndex;
private final int methodSlot;
public InferParentWithinMethodArgumentNode(
SourceSection sourceSection, VmLanguage language, int argIndex) {
SourceSection sourceSection, VmLanguage language, int argIndex, int methodSlot) {
super(sourceSection, language);
this.argIndex = argIndex;
}
@TruffleBoundary
private int getMethodSlot(FrameDescriptor frameDescriptor) {
var methodSlot = frameDescriptor.getAuxiliarySlots().get(VmUtils.METHOD_FRAME_SLOT_ID);
if (methodSlot == null) {
// used in intrinsic constructor e.g. pkl.base#List()
throw exceptionBuilder().evalError("cannotInferParent").build();
}
return methodSlot;
this.methodSlot = methodSlot;
}
@Override
protected Method getMethod(VirtualFrame frame) {
var method = (Method) frame.getAuxiliarySlot(getMethodSlot(frame.getFrameDescriptor()));
var method = (Method) frame.getObject(methodSlot);
if (method == null) {
// used in FunctionN.apply()
CompilerDirectives.transferToInterpreter();
@@ -32,8 +32,8 @@ public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMet
int levelsUp,
ExpressionNode[] argumentNodes,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
}
@Override
@@ -31,8 +31,8 @@ public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMe
int levelsUp,
ExpressionNode[] argumentNodes,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
}
@Override
@@ -35,8 +35,8 @@ public final class InvokeMethodDirectNode extends AbstractInvokeMethodNode {
ClassMethod method,
ExpressionNode receiverNode,
ExpressionNode[] argumentNodes,
boolean argsRequireInference) {
super(sourceSection, argumentNodes, argsRequireInference);
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
this.method = method;
this.owner = method.getOwner();
this.receiverNode = receiverNode;
@@ -53,8 +53,8 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, argumentNodes, argsRequireInference);
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
this.methodName = methodName;
this.lookupMode = lookupMode;
this.needsConst = needsConst;
@@ -65,8 +65,8 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
Identifier methodName,
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
boolean argsRequireInference) {
this(sourceSection, methodName, argumentNodes, lookupMode, false, argsRequireInference);
int methodSlot) {
this(sourceSection, methodName, argumentNodes, lookupMode, false, methodSlot);
}
/**
@@ -146,14 +146,7 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new InvokeMethodVirtualNodeWrapper(
sourceSection,
methodName,
argumentNodes,
lookupMode,
needsConst,
argsRequireInference,
this,
probe);
sourceSection, methodName, argumentNodes, lookupMode, needsConst, methodSlot, this, probe);
}
private void checkConst(ClassMethod method) {
@@ -29,14 +29,8 @@ public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifie
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
boolean argsRequireInference) {
super(
sourceSection,
methodName,
argumentNodes,
needsConst,
getReceiverNode,
argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
}
@Override
@@ -31,14 +31,8 @@ public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifi
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
boolean argsRequireInference) {
super(
sourceSection,
methodName,
argumentNodes,
needsConst,
getReceiverNode,
argsRequireInference);
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
}
@Override
@@ -36,8 +36,8 @@ public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode {
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst,
boolean argsRequireInference) {
super(sourceSection, argumentNodes, argsRequireInference);
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
this.needsConst = needsConst;
assert !methodName.isLocalMethod();
@@ -86,7 +86,7 @@ public abstract class ToStringNode extends UnaryExpressionNode {
Identifier.TO_STRING,
new ExpressionNode[] {},
MemberLookupMode.EXPLICIT_RECEIVER,
false,
-1,
null,
null);
}
@@ -1401,7 +1401,7 @@ public abstract class TypeNode extends PklNode {
vmListing,
EconomicMaps.emptyMap(),
vmListing.getLength(),
getValueTypeCastNode(),
getValueTypeCastNode(frame.getFrameDescriptor()),
VmUtils.getReceiver(frame),
VmUtils.getOwner(frame));
}
@@ -1459,7 +1459,7 @@ public abstract class TypeNode extends PklNode {
vmMapping.getEnclosingFrame(),
vmMapping,
EconomicMaps.emptyMap(),
getValueTypeCastNode(),
getValueTypeCastNode(frame.getFrameDescriptor()),
VmUtils.getReceiver(frame),
VmUtils.getOwner(frame));
}
@@ -1518,12 +1518,12 @@ public abstract class TypeNode extends PklNode {
return valueTypeNode;
}
protected ListingOrMappingTypeCastNode getValueTypeCastNode() {
protected ListingOrMappingTypeCastNode getValueTypeCastNode(FrameDescriptor descriptor) {
if (valueTypeCastNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
valueTypeCastNode =
new ListingOrMappingTypeCastNode(
language, new FrameDescriptor(), valueTypeNode, getRootNode().getName());
language, descriptor, valueTypeNode, getRootNode().getName());
}
return valueTypeCastNode;
}
@@ -20,7 +20,10 @@ import com.oracle.truffle.api.frame.FrameSlotKind;
import java.util.Arrays;
import org.jspecify.annotations.Nullable;
/** A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also gives us the current size. */
/**
* A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also helps track size, identifiers,
* and other metadata.
*/
public class FrameDescriptorBuilder {
private @Nullable Identifier[] names;
@@ -29,6 +32,8 @@ public class FrameDescriptorBuilder {
private final FrameDescriptor.Builder underlying;
private static final int DEFAULT_CAPACITY = 8;
private int methodSlot = -1;
private int state = 0;
public FrameDescriptorBuilder() {
this(DEFAULT_CAPACITY);
@@ -50,10 +55,36 @@ public class FrameDescriptorBuilder {
ensureCapacity(1);
names[size] = name;
size++;
state++;
var slot = underlying.addSlot(kind, name, info);
return new FrameSlotVariable(name.toString(), slot);
}
/**
* A monotonically increasing number representing the "virtual" state of the frame descriptor; a
* higher number means that something tried to modify the descriptor (but did not necessarily
* change the actual descriptor).
*/
public int state() {
return state;
}
public int getOrAddMethodSlot() {
state++;
if (methodSlot != -1) {
return methodSlot;
}
ensureCapacity(1);
names[size] = null;
size++;
methodSlot = underlying.addSlot(FrameSlotKind.Object, null, null);
return methodSlot;
}
public int getMethodSlot() {
return methodSlot;
}
public FrameDescriptor build() {
return underlying.build();
}
@@ -84,14 +84,6 @@ public final class VmUtils {
}
};
public static final Object METHOD_FRAME_SLOT_ID =
new Object() {
@Override
public String toString() {
return "method";
}
};
private static final Engine PKL_ENGINE =
Engine.newBuilder("pkl").option("engine.WarnInterpreterOnly", "false").build();
@@ -1,30 +1,27 @@
// let expr inside custom this scopes
typealias Foo = String(let (x = this) this == x)
typealias Foo2 = String(let (x = "foo") this.startsWith(x), let (y = "o") this.endsWith(y))
typealias Foo3 = String(let (x = "foo") this.startsWith(x))(let (y = "o") this.endsWith(y))
res1: Foo = "foo"
res2: Foo2 = "foo"
res3: Foo3 = "foo"
hidden bar {
new {
name = "fooey"
}
new {
name = "quxxey"
}
}
res4 = (bar) {
res1 = (bar) {
[[let (x = "foo") this.name.startsWith(x)]] {
name = "bob"
}
[[let (y = "qux") this.name.startsWith(y)]] {
name = "bob"
}
}
res5 = (bar) {
res2 = (bar) {
[[let (x = this) x.name.startsWith("foo")]] {
name = "bob"
}
}
function isPositive(num: Int(let (q = 0) this > q)) = true
res3: Int(isPositive(this)) = 5
@@ -0,0 +1,11 @@
foo {
new {
prop = 1
}
}
res = (foo) {
[[this.prop is Int(let (q = 0) this > q)]] {
prop = 2
}
}
@@ -0,0 +1,8 @@
res1 {
local x: Int(let (q = 0) this > q) = 5
z = x
}
res2 = f()
function f(): Int(let (q = 0) this > q) = 5
@@ -0,0 +1,33 @@
local f = (a, b) -> new Dynamic {
x = a
y = b
}
res =
(f) { p, q: Int(let (z = 0) this > z) ->
x = p
y = q
}.apply("hi", 9)
local g = (a, b: Int(let (z = 0) this > z)) -> new Dynamic {
x = a
y = b
}
res2 = g.apply("hi", 9)
res3 = new Dynamic {
local function h(a, b: Int(let (z = 0) this > z)) = new Dynamic {
x = a
y = b
}
`out` = h("hi", 9)
}
res4 =
(f) { p, q: Int(let (z = 0) this > z) ->
when (true) {
x = p
y = q
}
}.apply("hi", 9)
@@ -0,0 +1,12 @@
// let exprs inside typealiases turn into their own root node
typealias Foo = String(let (x = this) this == x)
typealias Foo2 = String(let (x = "foo") this.startsWith(x), let (y = "o") this.endsWith(y))
typealias Foo3 = String(let (x = "foo") this.startsWith(x))(let (y = "o") this.endsWith(y))
res1: Foo = "foo"
res2: Foo2 = "foo"
res3: Foo3 = "foo"
@@ -0,0 +1,15 @@
class Foo {
bar: Int
}
const function chk2(i: Int): Boolean = i > 0
const function mk(f: Foo): Listing<Int> = new {
f.bar
}
class C {
xs: Listing<Int(chk2(this))> = mk(new { bar = 2 })
}
c: C = new {}
@@ -0,0 +1,10 @@
class Foo {
bar: Int
}
const function isOk(f: Foo): Boolean = f.bar > 0
const function isPos(i: Int): Boolean = i > 0
typealias T = Int(isOk(new { bar = 1 }), isPos(this), isOk(new { bar = 2 }))
x: T = 5
@@ -0,0 +1,20 @@
class Foo {
bar: Int
}
const function isOk(f: Foo): Boolean = f.bar > 0
const function and(a: Boolean, b: Boolean) = a && b
function m(p: Int(isOk(new { bar = 1 }))): Int = p
function n(
// outer method call does not have implicit new, but inner method call does
p: Int(
and(
isOk(new { bar = 1 }),
true,
),
),
): Int = p
res1 = m(3)
res2 = n(3)
@@ -0,0 +1,17 @@
class Foo {
bar: Int
}
const function isOk(f: Foo): Boolean = f.bar > 0
const function isPos(num: Int) = num > 0
foo {
new {
prop = 1
}
}
res = (foo) {
[[this.prop is Int(isOk(new { bar = 1 }), isPos(this), isOk(new { bar = 2 }))]] {
prop = 2
}
}
@@ -0,0 +1,29 @@
class Foo {
bar: Int
}
const function isOk(f: Foo): Boolean = f.bar > 0
const function and(a: Boolean, b: Boolean) = a && b
typealias Okay = Int(isOk(new { bar = 1 }))
typealias Okay2 =
Int(
// no inferred new arg
and(
// nested call has inferred new arg
isOk(new { bar = 1 }),
true,
),
)
typealias Okay3 =
Int(
// no inferred new arg
isOk(new Foo { bar = 1 }),
// inferred new arg
isOk(new { bar = 1 }),
)
res1: Okay = 3
res2: Okay2 = 3
res3: Okay3 = 3
@@ -0,0 +1,19 @@
class Foo {
bar: Int
}
const function isOk2(f: Foo, n: Int): Boolean = f.bar > 0 && n > 0
local f = (a, b) -> new Dynamic { y = a; w = b }
res =
(f) { p, q ->
when (isOk2(new { bar = 1 }, p)) {
z = p
}
}.apply(1, 2)
res2 =
(f) {
when (isOk2(new { bar = 1 }, 2)) {
z = 2
}
}.apply(1, 2)
@@ -1,13 +1,17 @@
res1 = "foo"
res2 = "foo"
res3 = "foo"
res4 {
res1 {
new {
name = "bob"
}
new {
name = "bob"
}
}
res5 {
res2 {
new {
name = "bob"
}
new {
name = "quxxey"
}
}
res3 = 5
@@ -0,0 +1,10 @@
foo {
new {
prop = 1
}
}
res {
new {
prop = 2
}
}
@@ -0,0 +1,4 @@
res1 {
z = 5
}
res2 = 5
@@ -0,0 +1,18 @@
res {
x = "hi"
y = 9
}
res2 {
x = "hi"
y = 9
}
res3 {
`out` {
x = "hi"
y = 9
}
}
res4 {
x = "hi"
y = 9
}
@@ -0,0 +1,3 @@
res1 = "foo"
res2 = "foo"
res3 = "foo"
@@ -0,0 +1,5 @@
c {
xs {
2
}
}
@@ -0,0 +1 @@
x = 5
@@ -0,0 +1,2 @@
res1 = 3
res2 = 3
@@ -0,0 +1,10 @@
foo {
new {
prop = 1
}
}
res {
new {
prop = 2
}
}
@@ -0,0 +1,3 @@
res1 = 3
res2 = 3
res3 = 3
@@ -0,0 +1,10 @@
res {
y = 1
w = 2
z = 1
}
res2 {
y = 1
w = 2
z = 2
}