Overhaul implementation of for-generators (#844)

Motivation:
* fix known bugs and limitations of for-generators
* improve code health by removing complex workarounds

Changes:
* simplify AstBuilder code related to for-generators
  * track for-generators via `SymbolTable.enterForGenerator()`
  * add `RestoreForBindingsNode` during initial AST construction
    instead of calling `MemberNode.replaceBody()` later on
  * simplify some unnecessarily complex code
* remove workarounds and band-aids such as:
  * `isInIterable`
  * `executeAndSetEagerly`
  * adding dummy slots in `AmendFunctionNode`
* overhaul implementation of for-generators
  * store keys and values of for-generator iterations in regular instead of auxiliary frame slots
    * set them via `TypeNode.executeAndSet()`
    * `ResolveVariableNode` no longer needs to search auxiliary slots
    * `Read(Enclosing)AuxiliarySlot` is no longer needed
  * at the start of each for-generator iteration, create a new `VirtualFrame`
    that is a copy of the current frame (arguments + slots)
    and stores the iteration key and value in additional slots.
  * execute for-generator iteration with the newly created frame
    * `childNode.execute(newFrame)`
    * Pkl objects created during the iteration will materialize this frame
  * store newly created frames in `owner.extraStorage`
    if their for-generator slots may be accessed when a generated member is executed
    * resolving variable names to for-generator variables at parse time would make this analysis more precise
  * when a generated member is executed,
	  * retrieve the corresponding frame stored in `owner.extraStorage`
	  * copy the retrieved frame's for-generator slots into slots of the current frame

Result:
* for-generators are implemented in a correct, reasonably simple, and reasonably efficient way
  * complexity is fully contained within package `generator` and `AstBuilder`
* for-generator keys and values can be accessed from all nested scopes:
  * key and value expressions of generated members
  * condition expressions of nested when-generators
  * iterable expressions of nested for-generators
* for-generator keys and values can be accessed from within objects created by the expressions listed above
* sibling for-generators can use the same key/value variable names
* parent/child for-generators can use the same key/value variable names
* fixes https://github.com/apple/pkl/issues/741
This commit is contained in:
odenix
2025-01-28 14:06:42 -08:00
committed by GitHub
parent 11169d6691
commit 90df0662af
57 changed files with 923 additions and 1153 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@ package org.pkl.core.ast;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.source.SourceSection;
import java.util.function.Function;
import org.pkl.core.ast.member.DefaultPropertyBodyNode;
import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.runtime.VmLanguage;
@@ -43,10 +42,6 @@ public abstract class MemberNode extends PklRootNode {
return bodyNode;
}
public final void replaceBody(Function<ExpressionNode, ExpressionNode> replacer) {
bodyNode = insert(replacer.apply(bodyNode));
}
protected final VmExceptionBuilder exceptionBuilder() {
return new VmExceptionBuilder().withSourceSection(getHeaderSection());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,9 +55,6 @@ public final class VmModifier {
public static final int GLOB = 0x1000;
// To be removed when https://github.com/apple/pkl/issues/741 is fixed
public static final int IS_IN_ITERABLE = 0x100000;
// modifier sets
public static final int NONE = 0;
@@ -137,10 +134,6 @@ public final class VmModifier {
return (modifiers & ENTRY) != 0;
}
public static boolean isInIterable(int modifiers) {
return (modifiers & IS_IN_ITERABLE) != 0;
}
public static boolean isType(int modifiers) {
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.ParserRuleContext;
@@ -40,7 +39,6 @@ import org.pkl.core.TypeParameter.Variance;
import org.pkl.core.ast.*;
import org.pkl.core.ast.builder.SymbolTable.AnnotationScope;
import org.pkl.core.ast.builder.SymbolTable.ClassScope;
import org.pkl.core.ast.builder.SymbolTable.EntryScope;
import org.pkl.core.ast.builder.SymbolTable.Scope;
import org.pkl.core.ast.expression.binary.*;
import org.pkl.core.ast.expression.generator.*;
@@ -852,20 +850,20 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
@Override
public GeneratorPropertyNode visitObjectProperty(ObjectPropertyContext ctx) {
checkHasNoForGenerator(ctx, "forGeneratorCannotGenerateProperties");
checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateProperties");
var member = doVisitObjectProperty(ctx);
return GeneratorPropertyNodeGen.create(member);
}
@Override
public GeneratorMemberNode visitObjectMethod(ObjectMethodContext ctx) {
checkHasNoForGenerator(ctx, "forGeneratorCannotGenerateMethods");
checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateMethods");
var member = doVisitObjectMethod(ctx);
return GeneratorPropertyNodeGen.create(member);
}
private void checkHasNoForGenerator(ParserRuleContext ctx, String errorMessageKey) {
if (symbolTable.getCurrentScope().getForGeneratorVariables().isEmpty()) {
private void checkNotInsideForGenerator(ParserRuleContext ctx, String errorMessageKey) {
if (!symbolTable.getCurrentScope().isForGeneratorScope()) {
return;
}
var forExprCtx = ctx.getParent();
@@ -880,12 +878,19 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
@Override
public GeneratorMemberNode visitMemberPredicate(MemberPredicateContext ctx) {
var keyNodeAndMember = doVisitMemberPredicate(ctx);
var keyNode = keyNodeAndMember.first;
var member = keyNodeAndMember.second;
insertWriteForGeneratorVarsToFrameSlotsNode(member.getMemberNode());
if (ctx.err1 == null && ctx.err2 == null) {
throw missingDelimiter("]]", ctx.k.stop.getStopIndex() + 1);
} else if (ctx.err1 != null
&& (ctx.err2 == null || ctx.err1.getStartIndex() != ctx.err2.getStartIndex() - 1)) {
// There shouldn't be any whitespace between the first and second ']'.
throw wrongDelimiter("]]", "]", ctx.err1.getStartIndex());
}
return GeneratorPredicateMemberNodeGen.create(keyNode, member);
var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.k));
var member = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody());
var isFrameStored =
member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope();
return GeneratorPredicateMemberNodeGen.create(keyNode, member, isFrameStored);
}
@Override
@@ -893,43 +898,23 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
var keyNodeAndMember = doVisitObjectEntry(ctx);
var keyNode = keyNodeAndMember.first;
var member = keyNodeAndMember.second;
insertWriteForGeneratorVarsToFrameSlotsNode(member.getMemberNode());
return GeneratorEntryNodeGen.create(keyNode, member);
var isFrameStored =
member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope();
return GeneratorEntryNodeGen.create(keyNode, member, isFrameStored);
}
@Override
public GeneratorMemberNode visitObjectSpread(ObjectSpreadContext ctx) {
var scope = symbolTable.getCurrentScope();
var visitingIterable = scope.isVisitingIterable();
scope.setVisitingIterable(true);
var expr = visitExpr(ctx.expr());
scope.setVisitingIterable(visitingIterable);
return GeneratorSpreadNodeGen.create(createSourceSection(ctx), expr, ctx.QSPREAD() != null);
}
private void insertWriteForGeneratorVarsToFrameSlotsNode(@Nullable MemberNode memberNode) {
if (memberNode == null) return; // member has constant value
var descriptor = memberNode.getFrameDescriptor();
var forGeneratorVars = symbolTable.getCurrentScope().getForGeneratorVariables();
if (forGeneratorVars.isEmpty()) {
return; // node is not within a for generator
}
var slots = new int[forGeneratorVars.size()];
var i = 0;
for (var variable : forGeneratorVars) {
slots[i] = descriptor.findOrAddAuxiliarySlot(variable);
i++;
}
memberNode.replaceBody((bodyNode) -> new WriteForVariablesNode(slots, bodyNode));
}
@Override
public GeneratorElementNode visitObjectElement(ObjectElementContext ctx) {
var member = doVisitObjectElement(ctx);
insertWriteForGeneratorVarsToFrameSlotsNode(member.getMemberNode());
return GeneratorElementNodeGen.create(member);
var isFrameStored =
member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope();
return GeneratorElementNodeGen.create(member, isFrameStored);
}
private GeneratorMemberNode[] doVisitForWhenBody(ObjectBodyContext ctx) {
@@ -953,18 +938,6 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
return new GeneratorWhenNode(sourceSection, visitExpr(ctx.e), thenNodes, elseNodes);
}
private int pushForGeneratorVariableContext(ParameterContext ctx) {
var currentScope = symbolTable.getCurrentScope();
var slot = currentScope.pushForGeneratorVariableContext(ctx);
if (slot == -1) {
throw exceptionBuilder()
.evalError("duplicateDefinition", ctx.typedIdentifier().Identifier().getText())
.withSourceSection(createSourceSection(ctx))
.build();
}
return slot;
}
private static boolean isIgnored(@Nullable ParameterContext param) {
return param != null && param.UNDERSCORE() != null;
}
@@ -972,53 +945,68 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
@Override
public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) {
checkClosingDelimiter(ctx.err, ")", ctx.e.stop);
var sourceSection = createSourceSection(ctx);
int keyVariableSlot;
int valueVariableSlot;
UnresolvedTypeNode unresolvedKeyTypeNode;
UnresolvedTypeNode unresolvedValueTypeNode;
var keyParameter = ctx.t2 == null ? null : ctx.t1;
var valueParameter = ctx.t2 == null ? ctx.t1 : ctx.t2;
var keyTypedIdentifier = keyParameter == null ? null : keyParameter.typedIdentifier();
var valueTypedIdentifier = valueParameter == null ? null : valueParameter.typedIdentifier();
var keyIdentifier =
keyTypedIdentifier == null ? null : toIdentifier(keyTypedIdentifier.Identifier());
var valueIdentifier =
valueTypedIdentifier == null ? null : toIdentifier(valueTypedIdentifier.Identifier());
if (valueIdentifier != null && valueIdentifier == keyIdentifier) {
throw exceptionBuilder()
.evalError("duplicateDefinition", valueIdentifier)
.withSourceSection(createSourceSection(valueTypedIdentifier.Identifier()))
.build();
}
var currentScope = symbolTable.getCurrentScope();
var ignoreT1 = isIgnored(ctx.t1);
var ignoreT2 = ctx.t2 == null ? ignoreT1 : isIgnored(ctx.t2);
if (ctx.t2 != null) {
keyVariableSlot = ignoreT1 ? -1 : pushForGeneratorVariableContext(ctx.t1);
valueVariableSlot = ignoreT2 ? -1 : pushForGeneratorVariableContext(ctx.t2);
unresolvedKeyTypeNode =
ignoreT1 ? null : visitTypeAnnotation(ctx.t1.typedIdentifier().typeAnnotation());
unresolvedValueTypeNode =
ignoreT2 ? null : visitTypeAnnotation(ctx.t2.typedIdentifier().typeAnnotation());
} else {
keyVariableSlot = -1;
valueVariableSlot = ignoreT1 ? -1 : pushForGeneratorVariableContext(ctx.t1);
unresolvedKeyTypeNode = null;
unresolvedValueTypeNode =
ignoreT1 ? null : visitTypeAnnotation(ctx.t1.typedIdentifier().typeAnnotation());
var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder();
var memberDescriptorBuilder = currentScope.newForGeneratorMemberDescriptorBuilder();
var keySlot = -1;
var valueSlot = -1;
if (keyIdentifier != null) {
keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
}
var scope = symbolTable.getCurrentScope();
var visitingIterable = scope.isVisitingIterable();
scope.setVisitingIterable(true);
if (valueIdentifier != null) {
valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
}
var unresolvedKeyTypeNode =
keyTypedIdentifier == null
? null
: visitTypeAnnotation(keyTypedIdentifier.typeAnnotation());
var unresolvedValueTypeNode =
valueTypedIdentifier == null
? null
: visitTypeAnnotation(valueTypedIdentifier.typeAnnotation());
// if possible, initialize immediately to avoid later insert
var keyTypeNode =
unresolvedKeyTypeNode == null && keySlot != -1
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(keySlot)
: null;
// if possible, initialize immediately to avoid later insert
var valueTypeNode =
unresolvedValueTypeNode == null && valueSlot != -1
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(valueSlot)
: null;
var iterableNode = visitExpr(ctx.e);
scope.setVisitingIterable(visitingIterable);
var memberNodes = doVisitForWhenBody(ctx.objectBody());
if (keyVariableSlot != -1) {
currentScope.popForGeneratorVariable();
}
if (valueVariableSlot != -1) {
currentScope.popForGeneratorVariable();
}
//noinspection ConstantConditions
var memberNodes =
symbolTable.enterForGenerator(
generatorDescriptorBuilder,
memberDescriptorBuilder,
scope -> doVisitForWhenBody(ctx.objectBody()));
return GeneratorForNodeGen.create(
sourceSection,
keyVariableSlot,
valueVariableSlot,
createSourceSection(ctx),
generatorDescriptorBuilder.build(),
iterableNode,
unresolvedKeyTypeNode,
unresolvedValueTypeNode,
memberNodes,
ctx.t2 != null && !ignoreT1,
!ignoreT2);
keyTypeNode,
valueTypeNode);
}
private void checkSpaceSeparatedObjectMembers(ObjectBodyContext objectBodyContext) {
@@ -1200,15 +1188,13 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
}
private ObjectMember doVisitObjectElement(ObjectElementContext ctx) {
var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope();
return symbolTable.enterEntry(
null,
scope -> {
var elementNode = visitExpr(ctx.expr());
var modifier =
scope.isVisitingIterable()
? VmModifier.ELEMENT | VmModifier.IS_IN_ITERABLE
: VmModifier.ELEMENT;
var modifier = VmModifier.ELEMENT;
var member =
new ObjectMember(
createSourceSection(ctx),
@@ -1220,6 +1206,9 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
if (elementNode instanceof ConstantNode constantNode) {
member.initConstantValue(constantNode);
} else {
if (isForGeneratorScope) {
elementNode = new RestoreForBindingsNode(elementNode);
}
member.initMemberNode(
ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, elementNode));
@@ -1229,21 +1218,6 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
});
}
private Pair<ExpressionNode, ObjectMember> doVisitMemberPredicate(MemberPredicateContext ctx) {
if (ctx.err1 == null && ctx.err2 == null) {
throw missingDelimiter("]]", ctx.k.stop.getStopIndex() + 1);
} else if (ctx.err1 != null
&& (ctx.err2 == null || ctx.err1.getStartIndex() != ctx.err2.getStartIndex() - 1)) {
// There shouldn't be any whitespace between the first and second ']'.
throw wrongDelimiter("]]", "]", ctx.err1.getStartIndex());
}
var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.k));
return symbolTable.enterEntry(
keyNode, objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()));
}
private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntryContext ctx) {
checkClosingDelimiter(ctx.err1, "]", ctx.k.stop);
if (ctx.err2 != null) {
@@ -1253,46 +1227,54 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
}
var keyNode = visitExpr(ctx.k);
return symbolTable.enterEntry(
keyNode, objectMemberInserter(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()));
var member = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody());
return Pair.of(keyNode, member);
}
private Function<EntryScope, Pair<ExpressionNode, ObjectMember>> objectMemberInserter(
private ObjectMember doVisitObjectEntryBody(
SourceSection sourceSection,
ExpressionNode keyNode,
@Nullable ExprContext valueCtx,
List<? extends ObjectBodyContext> objectBodyCtxs) {
return scope -> {
var modifier =
scope.isVisitingIterable()
? VmModifier.ENTRY | VmModifier.IS_IN_ITERABLE
: VmModifier.ENTRY;
var member =
new ObjectMember(
sourceSection, keyNode.getSourceSection(), modifier, null, scope.getQualifiedName());
var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope();
return symbolTable.enterEntry(
keyNode,
scope -> {
var modifier = VmModifier.ENTRY;
var member =
new ObjectMember(
sourceSection,
keyNode.getSourceSection(),
modifier,
null,
scope.getQualifiedName());
if (valueCtx != null) { // ["key"] = value
var valueNode = visitExpr(valueCtx);
if (valueNode instanceof ConstantNode constantNode) {
member.initConstantValue(constantNode);
} else {
if (isForGeneratorScope) {
valueNode = new RestoreForBindingsNode(valueNode);
}
member.initMemberNode(
ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, valueNode));
}
} else { // ["key"] { ... }
var objectBody =
doVisitObjectBody(
objectBodyCtxs,
new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode()));
if (isForGeneratorScope) {
objectBody = new RestoreForBindingsNode(objectBody);
}
member.initMemberNode(
ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, objectBody));
}
if (valueCtx != null) { // ["key"] = value
var valueNode = visitExpr(valueCtx);
if (valueNode instanceof ConstantNode constantNode) {
member.initConstantValue(constantNode);
} else {
member.initMemberNode(
ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, valueNode));
}
} else { // ["key"] { ... }
var objectBody =
doVisitObjectBody(
objectBodyCtxs,
new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode()));
member.initMemberNode(
ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, objectBody));
}
return Pair.of(keyNode, member);
};
return member;
});
}
@Override
@@ -1350,10 +1332,6 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
result += modifier;
}
if (symbolTable.getCurrentScope().isVisitingIterable()) {
result += VmModifier.IS_IN_ITERABLE;
}
// flag modifier combinations that are never valid right away
if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) {
@@ -1986,7 +1964,6 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
visitArgumentList(argCtx),
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
symbolTable.getCurrentScope().isVisitingIterable(),
PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver),
GetClassNodeGen.create(null)));
}
@@ -1999,7 +1976,6 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
visitArgumentList(argCtx),
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
symbolTable.getCurrentScope().isVisitingIterable(),
receiver,
GetClassNodeGen.create(null));
}
@@ -2074,11 +2050,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
}
return InvokeSuperMethodNodeGen.create(
sourceSection,
memberName,
symbolTable.getCurrentScope().isVisitingIterable(),
visitArgumentList(argCtx),
needsConst);
sourceSection, memberName, visitArgumentList(argCtx), needsConst);
}
// superproperty call
@@ -2136,8 +2108,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
isBaseModule,
scope.isCustomThisScope(),
scope.getConstLevel(),
scope.getConstDepth(),
scope.isVisitingIterable());
scope.getConstDepth());
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,14 @@ package org.pkl.core.ast.builder;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameDescriptor.Builder;
import com.oracle.truffle.api.frame.FrameSlotKind;
import java.util.*;
import java.util.function.Function;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ConstantNode;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.generator.GeneratorMemberNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.parser.Lexer;
import org.pkl.core.parser.antlr.PklParser.ParameterContext;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.VmDataSize;
@@ -35,8 +34,6 @@ import org.pkl.core.util.Nullable;
public final class SymbolTable {
private Scope currentScope;
public static Object FOR_GENERATOR_VARIABLE = new Object();
public SymbolTable(ModuleInfo moduleInfo) {
currentScope = new ModuleScope(moduleInfo);
}
@@ -99,6 +96,19 @@ public final class SymbolTable {
nodeFactory);
}
public <T> T enterForGenerator(
FrameDescriptor.Builder frameDescriptorBuilder,
FrameDescriptor.Builder memberDescriptorBuilder,
Function<ForGeneratorScope, T> nodeFactory) {
return doEnter(
new ForGeneratorScope(
currentScope,
currentScope.qualifiedName,
frameDescriptorBuilder,
memberDescriptorBuilder),
nodeFactory);
}
public <T> T enterLambda(
FrameDescriptor.Builder frameDescriptorBuilder, Function<LambdaScope, T> nodeFactory) {
@@ -128,9 +138,11 @@ public final class SymbolTable {
Function<EntryScope, T> nodeFactory) {
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
return doEnter(
new EntryScope(currentScope, qualifiedName, FrameDescriptor.newBuilder()), nodeFactory);
var builder =
currentScope instanceof ForGeneratorScope forScope
? forScope.memberDescriptorBuilder
: FrameDescriptor.newBuilder();
return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory);
}
public <T> T enterCustomThisScope(Function<CustomThisScope, T> nodeFactory) {
@@ -166,12 +178,10 @@ public final class SymbolTable {
private final @Nullable Scope parent;
private final @Nullable Identifier name;
private final String qualifiedName;
private final Deque<Identifier> forGeneratorVariables = new ArrayDeque<>();
private int lambdaCount = 0;
private int entryCount = 0;
private final FrameDescriptor.Builder frameDescriptorBuilder;
private final ConstLevel constLevel;
private boolean isVisitingIterable;
private Scope(
@Nullable Scope parent,
@@ -188,7 +198,6 @@ public final class SymbolTable {
parent != null && parent.constLevel.biggerOrEquals(constLevel)
? parent.constLevel
: constLevel;
this.isVisitingIterable = parent != null && parent.isVisitingIterable;
}
public final @Nullable Scope getParent() {
@@ -212,6 +221,30 @@ public final class SymbolTable {
return frameDescriptorBuilder.build();
}
/**
* Returns a new descriptor builder that contains the same slots as the current scope's frame
* descriptor.
*/
public FrameDescriptor.Builder newFrameDescriptorBuilder() {
return newFrameDescriptorBuilder(buildFrameDescriptor());
}
/** Returns a new descriptor builder for a {@link GeneratorMemberNode} in the current scope. */
public FrameDescriptor.Builder newForGeneratorMemberDescriptorBuilder() {
return this instanceof ForGeneratorScope forScope
? newFrameDescriptorBuilder(forScope.buildMemberDescriptor())
: FrameDescriptor.newBuilder();
}
private static FrameDescriptor.Builder newFrameDescriptorBuilder(FrameDescriptor descriptor) {
var builder = FrameDescriptor.newBuilder();
for (var i = 0; i < descriptor.getNumberOfSlots(); i++) {
builder.addSlot(
descriptor.getSlotKind(i), descriptor.getSlotName(i), descriptor.getSlotInfo(i));
}
return builder;
}
public @Nullable TypeParameter getTypeParameter(String name) {
return null;
}
@@ -253,35 +286,11 @@ public final class SymbolTable {
return depth;
}
/**
* Adds the for generator variable to the frame descriptor.
*
* <p>Returns {@code -1} if a for-generator variable already exists with this name.
*/
public int pushForGeneratorVariableContext(ParameterContext ctx) {
var variable = Identifier.localProperty(ctx.typedIdentifier().Identifier().getText());
if (forGeneratorVariables.contains(variable)) {
return -1;
}
var slot =
frameDescriptorBuilder.addSlot(FrameSlotKind.Illegal, variable, FOR_GENERATOR_VARIABLE);
forGeneratorVariables.addLast(variable);
return slot;
}
public void popForGeneratorVariable() {
forGeneratorVariables.removeLast();
}
public Deque<Identifier> getForGeneratorVariables() {
return forGeneratorVariables;
}
private String getNextLambdaName() {
return "<function#" + (++skipLambdaScopes().lambdaCount) + ">";
}
private String getNextEntryName(@Nullable ExpressionNode keyNode) {
protected String getNextEntryName(@Nullable ExpressionNode keyNode) {
if (keyNode instanceof ConstantNode constantNode) {
var value = constantNode.getValue();
if (value instanceof String) {
@@ -336,17 +345,13 @@ public final class SymbolTable {
return this instanceof LexicalScope;
}
public final boolean isForGeneratorScope() {
return this instanceof ForGeneratorScope;
}
public ConstLevel getConstLevel() {
return constLevel;
}
public void setVisitingIterable(boolean isVisitingIterable) {
this.isVisitingIterable = isVisitingIterable;
}
public boolean isVisitingIterable() {
return isVisitingIterable;
}
}
private interface LexicalScope {}
@@ -413,6 +418,30 @@ public final class SymbolTable {
}
}
public static final class ForGeneratorScope extends Scope implements LexicalScope {
private final FrameDescriptor.Builder memberDescriptorBuilder;
public ForGeneratorScope(
Scope parent,
String qualifiedName,
FrameDescriptor.Builder frameDescriptorBuilder,
FrameDescriptor.Builder memberDescriptorBuilder) {
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
this.memberDescriptorBuilder = memberDescriptorBuilder;
}
public FrameDescriptor buildMemberDescriptor() {
return memberDescriptorBuilder.build();
}
@Override
protected String getNextEntryName(@Nullable ExpressionNode keyNode) {
var parent = getParent();
assert parent != null;
return parent.getNextEntryName(keyNode);
}
}
public static final class PropertyScope extends Scope {
public PropertyScope(
Scope parent,

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.member.FunctionNode;
import org.pkl.core.ast.member.UnresolvedFunctionNode;
import org.pkl.core.runtime.VmFunction;
@@ -57,7 +56,7 @@ public final class LetExprNode extends ExpressionNode {
callNode = insert(DirectCallNode.create(functionNode.getCallTarget()));
if (isCustomThisScope) {
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
}
@@ -71,6 +70,6 @@ public final class LetExprNode extends ExpressionNode {
var value = valueNode.executeGeneric(frame);
return callNode.call(function.getThisValue(), function, false, value);
return callNode.call(function.getThisValue(), function, value);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,57 +19,50 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmListing;
import org.pkl.core.util.EconomicMaps;
@ImportStatic(BaseModule.class)
public abstract class GeneratorElementNode extends GeneratorMemberNode {
private final ObjectMember element;
protected GeneratorElementNode(ObjectMember element) {
super(element.getSourceSection());
protected GeneratorElementNode(ObjectMember element, boolean isFrameStored) {
super(element.getSourceSection(), isFrameStored);
this.element = element;
}
@Specialization
@SuppressWarnings("unused")
protected void evalDynamic(VmDynamic parent, ObjectData data) {
addElement(data);
protected void evalDynamic(VirtualFrame frame, VmDynamic parent, ObjectData data) {
data.addElement(frame, element, this);
}
@Specialization
@SuppressWarnings("unused")
protected void evalListing(VmListing parent, ObjectData data) {
addElement(data);
protected void evalListing(VirtualFrame frame, VmListing parent, ObjectData data) {
data.addElement(frame, element, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "parent == getDynamicClass()")
protected void evalDynamicClass(VmClass parent, ObjectData data) {
addElement(data);
protected void evalDynamicClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addElement(frame, element, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "parent == getListingClass()")
protected void evalListingClass(VmClass parent, ObjectData data) {
addElement(data);
protected void evalListingClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addElement(frame, element, this);
}
@Fallback
@SuppressWarnings("unused")
void fallback(Object parent, ObjectData data) {
void fallback(VirtualFrame frame, Object parent, ObjectData data) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("objectCannotHaveElement", parent).build();
}
private void addElement(ObjectData data) {
long index = data.length;
EconomicMaps.put(data.members, index, element);
data.length += 1;
data.persistForBindings(index);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,15 +25,14 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.VmException.ProgramValue;
import org.pkl.core.util.EconomicMaps;
@ImportStatic(BaseModule.class)
public abstract class GeneratorEntryNode extends GeneratorMemberNode {
@Child private ExpressionNode keyNode;
private final ObjectMember member;
protected GeneratorEntryNode(ExpressionNode keyNode, ObjectMember member) {
super(member.getSourceSection());
protected GeneratorEntryNode(ExpressionNode keyNode, ObjectMember member, boolean isFrameStored) {
super(member.getSourceSection(), isFrameStored);
this.keyNode = keyNode;
this.member = member;
}
@@ -84,7 +83,7 @@ public abstract class GeneratorEntryNode extends GeneratorMemberNode {
private void addRegularEntry(VirtualFrame frame, ObjectData data) {
var key = keyNode.executeGeneric(frame);
doAdd(key, data);
data.addMember(frame, key, member, this);
}
private void addListingEntry(VirtualFrame frame, ObjectData data, int parentLength) {
@@ -108,15 +107,6 @@ public abstract class GeneratorEntryNode extends GeneratorMemberNode {
.build();
}
doAdd(index, data);
}
private void doAdd(Object key, ObjectData data) {
if (EconomicMaps.put(data.members, key, member) != null) {
CompilerDirectives.transferToInterpreter();
throw duplicateDefinition(key, member);
}
data.persistForBindings(key);
data.addMember(frame, index, member, this);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,62 +16,51 @@
package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.Fallback;
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.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import java.util.*;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.ast.type.VmTypeMismatchException;
import org.pkl.core.runtime.*;
import org.pkl.core.util.LateInit;
import org.pkl.core.util.Nullable;
import org.pkl.core.util.Pair;
public abstract class GeneratorForNode extends GeneratorMemberNode {
private final int keySlot;
private final int valueSlot;
private final FrameDescriptor generatorDescriptor;
@Child private ExpressionNode iterableNode;
@Child private @Nullable UnresolvedTypeNode unresolvedKeyTypeNode;
@Child private @Nullable UnresolvedTypeNode unresolvedValueTypeNode;
@Children private final GeneratorMemberNode[] childNodes;
@Child private @Nullable TypeNode keyTypeNode;
@Child @LateInit private TypeNode valueTypeNode;
@Child private @Nullable TypeNode valueTypeNode;
public GeneratorForNode(
SourceSection sourceSection,
int keySlot,
int valueSlot,
FrameDescriptor generatorDescriptor,
ExpressionNode iterableNode,
// null if for-generator doesn't bind key or `keyTypeNode` is passed instead of this node
@Nullable UnresolvedTypeNode unresolvedKeyTypeNode,
// null if for-generator doesn't bind value or `valueTypeNode` is passed instead of this node
@Nullable UnresolvedTypeNode unresolvedValueTypeNode,
// If this node can be constructed at parse time,
// it should be passed instead of `unresolvedKeyTypeNode`.
GeneratorMemberNode[] childNodes,
boolean hasKeyIdentifier,
boolean hasValueIdentifier) {
super(sourceSection);
this.keySlot = keySlot;
this.valueSlot = valueSlot;
@Nullable TypeNode keyTypeNode,
// If this node can be constructed at parse time,
// it should be passed instead of `unresolvedValueTypeNode`.
@Nullable TypeNode valueTypeNode) {
super(sourceSection, false);
this.generatorDescriptor = generatorDescriptor;
this.iterableNode = iterableNode;
this.unresolvedKeyTypeNode = unresolvedKeyTypeNode;
this.unresolvedValueTypeNode = unresolvedValueTypeNode;
this.childNodes = childNodes;
// initialize now if possible to save later insert()
if (unresolvedKeyTypeNode == null && hasKeyIdentifier) {
keyTypeNode =
new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(keySlot);
}
if (unresolvedValueTypeNode == null && hasValueIdentifier) {
valueTypeNode =
new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(valueSlot);
}
this.keyTypeNode = keyTypeNode;
this.valueTypeNode = valueTypeNode;
}
protected abstract void executeWithIterable(
@@ -79,6 +68,7 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Override
public final void execute(VirtualFrame frame, Object parent, ObjectData data) {
initialize(frame);
executeWithIterable(frame, parent, data, iterableNode.executeGeneric(frame));
}
@@ -99,41 +89,33 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmList iterable) {
initTypeNodes(frame);
long idx = 0;
for (Object element : iterable) {
executeIteration(frame, parent, data, idx++, element);
}
resetFrameSlots(frame);
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmMap iterable) {
initTypeNodes(frame);
for (var entry : iterable) {
executeIteration(frame, parent, data, VmUtils.getKey(entry), VmUtils.getValue(entry));
}
resetFrameSlots(frame);
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmSet iterable) {
initTypeNodes(frame);
long idx = 0;
for (var element : iterable) {
executeIteration(frame, parent, data, idx++, element);
}
resetFrameSlots(frame);
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmMap iterable) {
for (var entry : iterable) {
executeIteration(frame, parent, data, VmUtils.getKey(entry), VmUtils.getValue(entry));
}
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmSet iterable) {
long idx = 0;
for (var element : iterable) {
executeIteration(frame, parent, data, idx++, element);
}
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmIntSeq iterable) {
initTypeNodes(frame);
var length = iterable.getLength();
for (long key = 0, value = iterable.start; key < length; key++, value += iterable.step) {
executeIteration(frame, parent, data, key, value);
}
resetFrameSlots(frame);
}
@Fallback
@@ -147,84 +129,55 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
.build();
}
@SuppressWarnings("ForLoopReplaceableByForEach")
private void doEvalObject(VirtualFrame frame, VmObject iterable, Object parent, ObjectData data) {
initTypeNodes(frame);
var members = evaluateMembers(iterable);
for (int i = 0; i < members.size(); i++) {
var member = members.get(i);
executeIteration(frame, parent, data, member.first, member.second);
}
resetFrameSlots(frame);
}
private void resetFrameSlots(VirtualFrame frame) {
if (keySlot != -1) {
frame.clear(keySlot);
}
if (valueSlot != -1) {
frame.clear(valueSlot);
}
}
private void initTypeNodes(VirtualFrame frame) {
if (unresolvedKeyTypeNode != null) {
CompilerDirectives.transferToInterpreter();
keyTypeNode = insert(unresolvedKeyTypeNode.execute(frame)).initWriteSlotNode(keySlot);
unresolvedKeyTypeNode = null;
}
if (unresolvedValueTypeNode != null) {
CompilerDirectives.transferToInterpreter();
valueTypeNode = insert(unresolvedValueTypeNode.execute(frame)).initWriteSlotNode(valueSlot);
unresolvedValueTypeNode = null;
}
}
/**
* Evaluate members upfront to make sure that `childNode.execute()` is not behind a Truffle
* boundary.
*/
@TruffleBoundary
private List<Pair<Object, Object>> evaluateMembers(VmObject object) {
var members = new ArrayList<Pair<Object, Object>>();
object.forceAndIterateMemberValues(
iterable.forceAndIterateMemberValues(
(key, member, value) -> {
members.add(Pair.of(member.isProp() ? key.toString() : key, value));
var convertedKey = member.isProp() ? key.toString() : key;
// TODO: Executing iteration behind a Truffle boundary is bad for performance.
// This and similar cases will be fixed in an upcoming PR that replaces method
// `(forceAnd)iterateMemberValues` with cursor-based external iterators.
executeIteration(frame, parent, data, convertedKey, value);
return true;
});
return members;
}
@ExplodeLoop
private void executeIteration(
VirtualFrame frame, Object parent, ObjectData data, Object key, Object value) {
try {
if (keyTypeNode != null) {
keyTypeNode.executeAndSet(frame, key);
}
if (valueTypeNode != null) {
valueTypeNode.executeAndSet(frame, value);
}
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();
// GraalJS uses the same implementation technique here:
// https://github.com/oracle/graaljs/blob/44a11ce6e87/graal-js/src/com.oracle.truffle.js/
// src/com/oracle/truffle/js/nodes/function/IterationScopeNode.java#L86-L88
var newFrame =
Truffle.getRuntime().createVirtualFrame(frame.getArguments(), generatorDescriptor);
// the locals in `frame` (if any) are function arguments and/or outer for-generator bindings
VmUtils.copyLocals(frame, 0, newFrame, 0, frame.getFrameDescriptor().getNumberOfSlots());
if (keyTypeNode != null) {
keyTypeNode.executeAndSet(newFrame, key);
}
Object[] prevBindings = null;
if (keyTypeNode != null && valueTypeNode != null) {
prevBindings = data.addForBinding(key, value);
} else if (valueTypeNode != null) {
prevBindings = data.addForBinding(value);
} else if (keyTypeNode != null) {
prevBindings = data.addForBinding(key);
if (valueTypeNode != null) {
valueTypeNode.executeAndSet(newFrame, value);
}
for (var childNode : childNodes) {
childNode.execute(frame, parent, data);
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < childNodes.length; i++) {
childNodes[i].execute(newFrame, parent, data);
}
}
data.resetForBindings(prevBindings);
private void initialize(VirtualFrame frame) {
if (unresolvedKeyTypeNode != null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var keySlot = frame.getFrameDescriptor().getNumberOfSlots();
keyTypeNode = insert(unresolvedKeyTypeNode.execute(frame)).initWriteSlotNode(keySlot);
generatorDescriptor.setSlotKind(keySlot, keyTypeNode.getFrameSlotKind());
unresolvedKeyTypeNode = null;
}
if (unresolvedValueTypeNode != null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var valueSlot = frame.getFrameDescriptor().getNumberOfSlots() + (keyTypeNode != null ? 1 : 0);
valueTypeNode = insert(unresolvedValueTypeNode.execute(frame)).initWriteSlotNode(valueSlot);
generatorDescriptor.setSlotKind(valueSlot, valueTypeNode.getFrameSlotKind());
unresolvedValueTypeNode = null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,23 @@
package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.CompilerDirectives.ValueType;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import java.util.Arrays;
import org.graalvm.collections.EconomicMap;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmException;
import org.pkl.core.runtime.VmException.ProgramValue;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.Nullable;
import org.pkl.core.runtime.VmUtils;
public abstract class GeneratorMemberNode extends PklNode {
protected GeneratorMemberNode(SourceSection sourceSection) {
final boolean isFrameStored;
protected GeneratorMemberNode(SourceSection sourceSection, boolean isFrameStored) {
super(sourceSection);
this.isFrameStored = isFrameStored;
}
public abstract void execute(VirtualFrame frame, Object parent, ObjectData data);
@@ -54,79 +52,39 @@ public abstract class GeneratorMemberNode extends PklNode {
}
@Idempotent
protected boolean checkIsValidTypedProperty(VmClass clazz, ObjectMember member) {
if (member.isLocal() || clazz.hasProperty(member.getName())) return true;
@SuppressWarnings("SameReturnValue")
protected final boolean checkIsValidTypedProperty(VmClass clazz, ObjectMember member) {
if (member.isLocal()) return true;
var memberName = member.getName();
var classProperty = clazz.getProperty(memberName);
if (classProperty != null && !classProperty.isConstOrFixed()) return true;
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.cannotFindProperty(clazz.getPrototype(), member.getName(), false, false)
.withSourceSection(member.getHeaderSection())
.build();
}
/**
* <code>
* x = new Mapping { for (i in IntSeq(1, 3)) for (key, value in Map(4, "Pigeon", 6, "Barn Owl")) [i *
* key] = value.reverse() }
* </code>
*
* <p>The above code results in - 1 MemberNode for `value.reverse()` - 1 ObjectMember for `[i *
* key] = value.reverse()` - 1 ObjectData.members map with 6 identical ObjectMember values keyed
* by `i * key` - 1 ObjectData.forBindings map with 6 distinct arrays keyed by `i * key` Each
* array contains three elements, namely the current values for `i`, `key`, and `value`. - 1
* VmMapping whose `members` field holds `ObjectData.members` and whose `extraStorage` field holds
* `ObjectData.forBindings`. - 3 `FrameSlot`s for `i`, `key`, and `value`
*/
@ValueType
public static final class ObjectData {
// member count is exact iff every for/when body has exactly one member
ObjectData(int minMemberCount, int length) {
this.members = EconomicMaps.create(minMemberCount);
this.length = length;
}
final EconomicMap<Object, ObjectMember> members;
// For-bindings keyed by object member key.
// (There is only one ObjectMember instance per lexical member definition,
// hence can't store a member's for-bindings there.)
final EconomicMap<Object, Object[]> forBindings = EconomicMap.create();
int length;
private Object @Nullable [] currentForBindings;
@TruffleBoundary
Object @Nullable [] addForBinding(Object value) {
var result = currentForBindings;
if (currentForBindings == null) {
currentForBindings = new Object[] {value};
} else {
currentForBindings = Arrays.copyOf(currentForBindings, currentForBindings.length + 1);
currentForBindings[currentForBindings.length - 1] = value;
if (classProperty == null) {
var exception =
exceptionBuilder()
.cannotFindProperty(clazz.getPrototype(), memberName, false, false)
.build();
if (member.getHeaderSection().isAvailable()) {
exception
.getInsertedStackFrames()
.put(
getRootNode().getCallTarget(),
VmUtils.createStackFrame(member.getHeaderSection(), member.getQualifiedName()));
}
return result;
throw exception;
}
@TruffleBoundary
Object @Nullable [] addForBinding(Object key, Object value) {
var result = currentForBindings;
if (currentForBindings == null) {
currentForBindings = new Object[] {key, value};
} else {
currentForBindings = Arrays.copyOf(currentForBindings, currentForBindings.length + 2);
currentForBindings[currentForBindings.length - 2] = key;
currentForBindings[currentForBindings.length - 1] = value;
}
return result;
}
void persistForBindings(Object key) {
EconomicMaps.put(forBindings, key, currentForBindings);
}
void resetForBindings(Object @Nullable [] bindings) {
currentForBindings = bindings;
assert classProperty.isConstOrFixed();
var errMsg =
classProperty.isConst() ? "cannotAssignConstProperty" : "cannotAssignFixedProperty";
var exception = exceptionBuilder().evalError(errMsg, memberName).build();
if (member.getHeaderSection().isAvailable()) {
exception
.getInsertedStackFrames()
.put(
getRootNode().getCallTarget(),
VmUtils.createStackFrame(member.getHeaderSection(), member.getQualifiedName()));
}
throw exception;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.generator.GeneratorMemberNode.ObjectData;
import org.pkl.core.ast.expression.literal.AmendFunctionNode;
import org.pkl.core.ast.expression.literal.ObjectLiteralNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
@@ -73,34 +72,31 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
@Specialization(guards = "checkObjectCannotHaveParameters()")
protected VmDynamic evalDynamic(VirtualFrame frame, VmDynamic parent) {
var data = createData(frame, parent, parent.getLength());
var result = new VmDynamic(frame.materialize(), parent, data.members, data.length);
result.setExtraStorage(data.forBindings);
return result;
var data = executeChildren(frame, parent, parent.getLength());
var result = new VmDynamic(frame.materialize(), parent, data.members(), data.length());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = "checkObjectCannotHaveParameters()")
protected VmTyped evalTyped(VirtualFrame frame, VmTyped parent) {
VmUtils.checkIsInstantiable(parent.getVmClass(), getParentNode());
var data = createData(frame, parent, 0);
assert data.forBindings.isEmpty();
return new VmTyped(frame.materialize(), parent, parent.getVmClass(), data.members);
var data = executeChildren(frame, parent, 0);
assert data.hasNoGeneratorFrames();
return new VmTyped(frame.materialize(), parent, parent.getVmClass(), data.members());
}
@Specialization(guards = "checkListingCannotHaveParameters()")
protected VmListing evalListing(VirtualFrame frame, VmListing parent) {
var data = createData(frame, parent, parent.getLength());
var result = new VmListing(frame.materialize(), parent, data.members, data.length);
result.setExtraStorage(data.forBindings);
return result;
var data = executeChildren(frame, parent, parent.getLength());
var result = new VmListing(frame.materialize(), parent, data.members(), data.length());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = "checkMappingCannotHaveParameters()")
protected VmMapping evalMapping(VirtualFrame frame, VmMapping parent) {
var data = createData(frame, parent, 0);
var result = new VmMapping(frame.materialize(), parent, data.members);
result.setExtraStorage(data.forBindings);
return result;
var data = executeChildren(frame, parent, 0);
var result = new VmMapping(frame.materialize(), parent, data.members());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = "checkObjectCannotHaveParameters()")
@@ -110,7 +106,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
protected Object evalFunction(
protected VmFunction evalFunction(
VirtualFrame frame,
VmFunction parent,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
@@ -120,41 +116,34 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
}
@Specialization(guards = {"parent == getDynamicClass()", "checkObjectCannotHaveParameters()"})
protected VmDynamic evalDynamicClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
var data = createData(frame, parent, 0);
protected VmDynamic evalDynamicClass(VirtualFrame frame, VmClass parent) {
var data = executeChildren(frame, parent, 0);
var result =
new VmDynamic(frame.materialize(), parent.getPrototype(), data.members, data.length);
result.setExtraStorage(data.forBindings);
return result;
new VmDynamic(frame.materialize(), parent.getPrototype(), data.members(), data.length());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = {"parent == getMappingClass()", "checkMappingCannotHaveParameters()"})
protected VmMapping evalMappingClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
var data = createData(frame, parent, 0);
var result = new VmMapping(frame.materialize(), parent.getPrototype(), data.members);
result.setExtraStorage(data.forBindings);
return result;
protected VmMapping evalMappingClass(VirtualFrame frame, VmClass parent) {
var data = executeChildren(frame, parent, 0);
var result = new VmMapping(frame.materialize(), parent.getPrototype(), data.members());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = {"parent == getListingClass()", "checkListingCannotHaveParameters()"})
protected VmListing evalListingClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
var data = createData(frame, parent, 0);
protected VmListing evalListingClass(VirtualFrame frame, VmClass parent) {
var data = executeChildren(frame, parent, 0);
var result =
new VmListing(frame.materialize(), parent.getPrototype(), data.members, data.length);
result.setExtraStorage(data.forBindings);
return result;
new VmListing(frame.materialize(), parent.getPrototype(), data.members(), data.length());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = {"isTypedObjectClass(parent)", "checkObjectCannotHaveParameters()"})
protected VmTyped evalTypedObjectClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
protected VmTyped evalTypedObjectClass(VirtualFrame frame, VmClass parent) {
VmUtils.checkIsInstantiable(parent, getParentNode());
var data = createData(frame, parent, 0);
assert data.forBindings.isEmpty();
return new VmTyped(frame.materialize(), parent.getPrototype(), parent, data.members);
var data = executeChildren(frame, parent, 0);
assert data.hasNoGeneratorFrames();
return new VmTyped(frame.materialize(), parent.getPrototype(), parent, data.members());
}
@Fallback
@@ -200,9 +189,9 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
}
@ExplodeLoop
private ObjectData createData(VirtualFrame frame, Object parent, int parentLength) {
var data = new ObjectData(memberNodes.length, parentLength);
for (GeneratorMemberNode memberNode : memberNodes) {
private ObjectData executeChildren(VirtualFrame frame, Object parent, int parentLength) {
var data = new ObjectData(parentLength);
for (var memberNode : memberNodes) {
memberNode.execute(frame, parent, data);
}
return data;

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,8 +34,9 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
@CompilationFinal private int customThisSlot = -1;
protected GeneratorPredicateMemberNode(ExpressionNode predicateNode, ObjectMember member) {
super(member.getSourceSection());
protected GeneratorPredicateMemberNode(
ExpressionNode predicateNode, ObjectMember member, boolean isFrameStored) {
super(member.getSourceSection(), isFrameStored);
this.predicateNode = predicateNode;
this.member = member;
}
@@ -110,7 +111,7 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
try {
var isApplicable = predicateNode.executeBoolean(frame);
if (isApplicable) doAdd(key, data);
if (isApplicable) data.addMember(frame, key, this.member, this);
} catch (UnexpectedResultException e) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
@@ -134,13 +135,4 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
}
}
private void doAdd(Object key, ObjectData data) {
if (EconomicMaps.put(data.members, key, member) != null) {
CompilerDirectives.transferToInterpreter();
throw duplicateDefinition(key, member);
}
data.persistForBindings(key);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,67 +20,67 @@ import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.*;
import org.pkl.core.util.EconomicMaps;
@ImportStatic({BaseModule.class, GeneratorObjectLiteralNode.class})
public abstract class GeneratorPropertyNode extends GeneratorMemberNode {
protected final ObjectMember member;
protected GeneratorPropertyNode(ObjectMember member) {
super(member.getSourceSection());
super(member.getSourceSection(), false);
this.member = member;
assert member.isProp();
}
@Specialization
@SuppressWarnings("unused")
protected void evalDynamic(VmDynamic parent, ObjectData data) {
addProperty(data);
protected void evalDynamic(VirtualFrame frame, VmDynamic parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "checkIsValidTypedProperty(parent.getVmClass(), member)")
protected void evalTyped(VmTyped parent, ObjectData data) {
addProperty(data);
protected void evalTyped(VirtualFrame frame, VmTyped parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "checkIsValidMappingProperty()")
protected void evalMapping(VmMapping parent, ObjectData data) {
addProperty(data);
protected void evalMapping(VirtualFrame frame, VmMapping parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "checkIsValidListingProperty()")
protected void evalListing(VmListing parent, ObjectData data) {
addProperty(data);
protected void evalListing(VirtualFrame frame, VmListing parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = "parent == getDynamicClass()")
protected void evalDynamicClass(VmClass parent, ObjectData data) {
addProperty(data);
protected void evalDynamicClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = {"parent == getMappingClass()", "checkIsValidMappingProperty()"})
protected void evalMappingClass(VmClass parent, ObjectData data) {
addProperty(data);
protected void evalMappingClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(guards = {"parent == getListingClass()", "checkIsValidListingProperty()"})
protected void evalListingClass(VmClass parent, ObjectData data) {
addProperty(data);
protected void evalListingClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isTypedObjectClass(parent)", "checkIsValidTypedProperty(parent, member)"})
protected void evalTypedObjectClass(VmClass parent, ObjectData data) {
addProperty(data);
protected void evalTypedObjectClass(VirtualFrame frame, VmClass parent, ObjectData data) {
data.addProperty(frame, member, this);
}
@Fallback
@@ -116,11 +116,4 @@ public abstract class GeneratorPropertyNode extends GeneratorMemberNode {
.withSourceSection(member.getHeaderSection())
.build();
}
private void addProperty(ObjectData data) {
if (EconomicMaps.put(data.members, member.getName(), member) == null) return;
CompilerDirectives.transferToInterpreter();
throw duplicateDefinition(member.getName(), member);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import static org.pkl.core.runtime.BaseModule.getListingClass;
import static org.pkl.core.runtime.BaseModule.getMappingClass;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
@@ -29,9 +28,11 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.Iterators.TruffleIterator;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmCollection;
import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmException;
import org.pkl.core.runtime.VmException.ProgramValue;
import org.pkl.core.runtime.VmIntSeq;
import org.pkl.core.runtime.VmListing;
@@ -41,8 +42,6 @@ import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmObject;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.MutableLong;
@ImportStatic(BaseModule.class)
public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
@@ -51,7 +50,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
public GeneratorSpreadNode(
SourceSection sourceSection, ExpressionNode iterableNode, boolean nullable) {
super(sourceSection);
super(sourceSection, false);
this.iterableNode = iterableNode;
this.nullable = nullable;
}
@@ -84,78 +83,82 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
@Specialization(guards = "!iterable.isTyped()")
@SuppressWarnings("unused")
protected void eval(VmDynamic parent, ObjectData data, VmObject iterable) {
doEvalDynamic(data, iterable);
protected void eval(VirtualFrame frame, VmDynamic parent, ObjectData data, VmObject iterable) {
doEvalDynamic(frame, data, iterable);
}
@Specialization(guards = "!iterable.isTyped()")
@SuppressWarnings("unused")
protected void eval(VmListing parent, ObjectData data, VmObject iterable) {
doEvalListing(data, iterable);
protected void eval(VirtualFrame frame, VmListing parent, ObjectData data, VmObject iterable) {
doEvalListing(frame, data, iterable);
}
@Specialization(guards = "!iterable.isTyped()")
@SuppressWarnings("unused")
protected void eval(VmMapping parent, ObjectData data, VmObject iterable) {
doEvalMapping(data, iterable);
protected void eval(VirtualFrame frame, VmMapping parent, ObjectData data, VmObject iterable) {
doEvalMapping(frame, data, iterable);
}
@Specialization(guards = {"parent == getDynamicClass()", "!iterable.isTyped()"})
@SuppressWarnings("unused")
protected void evalDynamicClass(VmClass parent, ObjectData data, VmObject iterable) {
doEvalDynamic(data, iterable);
protected void evalDynamicClass(
VirtualFrame frame, VmClass parent, ObjectData data, VmObject iterable) {
doEvalDynamic(frame, data, iterable);
}
@Specialization(guards = {"parent == getListingClass()", "!iterable.isTyped()"})
@SuppressWarnings("unused")
protected void evalListingClass(VmClass parent, ObjectData data, VmObject iterable) {
doEvalListing(data, iterable);
protected void evalListingClass(
VirtualFrame frame, VmClass parent, ObjectData data, VmObject iterable) {
doEvalListing(frame, data, iterable);
}
@Specialization(guards = {"parent == getMappingClass()", "!iterable.isTyped()"})
@SuppressWarnings("unused")
protected void evalMappingClass(VmClass parent, ObjectData data, VmObject iterable) {
doEvalMapping(data, iterable);
protected void evalMappingClass(
VirtualFrame frame, VmClass parent, ObjectData data, VmObject iterable) {
doEvalMapping(frame, data, iterable);
}
@Specialization(guards = {"isTypedObjectClass(parent)", "!iterable.isTyped()"})
protected void evalTypedClass(VmClass parent, ObjectData data, VmObject iterable) {
doEvalTyped(parent, data, iterable);
protected void evalTypedClass(
VirtualFrame frame, VmClass parent, ObjectData data, VmObject iterable) {
doEvalTyped(frame, parent, data, iterable);
}
@Specialization(guards = {"!iterable.isTyped()"})
protected void eval(VmTyped parent, ObjectData data, VmObject iterable) {
doEvalTyped(parent.getVmClass(), data, iterable);
protected void eval(VirtualFrame frame, VmTyped parent, ObjectData data, VmObject iterable) {
doEvalTyped(frame, parent.getVmClass(), data, iterable);
}
@Specialization
protected void eval(VmObject parent, ObjectData data, VmMap iterable) {
doEvalMap(parent.getVmClass(), data, iterable);
protected void eval(VirtualFrame frame, VmObject parent, ObjectData data, VmMap iterable) {
doEvalMap(frame, parent.getVmClass(), data, iterable);
}
@Specialization
protected void eval(VmClass parent, ObjectData data, VmMap iterable) {
doEvalMap(parent, data, iterable);
protected void eval(VirtualFrame frame, VmClass parent, ObjectData data, VmMap iterable) {
doEvalMap(frame, parent, data, iterable);
}
@Specialization
protected void eval(VmObject parent, ObjectData data, VmCollection iterable) {
doEvalCollection(parent.getVmClass(), data, iterable);
protected void eval(VirtualFrame frame, VmObject parent, ObjectData data, VmCollection iterable) {
doEvalCollection(frame, parent.getVmClass(), data, iterable);
}
@Specialization
protected void eval(VmClass parent, ObjectData data, VmCollection iterable) {
doEvalCollection(parent, data, iterable);
protected void eval(VirtualFrame frame, VmClass parent, ObjectData data, VmCollection iterable) {
doEvalCollection(frame, parent, data, iterable);
}
@Specialization
protected void eval(VmObject parent, ObjectData data, VmIntSeq iterable) {
doEvalIntSeq(parent.getVmClass(), data, iterable);
protected void eval(VirtualFrame frame, VmObject parent, ObjectData data, VmIntSeq iterable) {
doEvalIntSeq(frame, parent.getVmClass(), data, iterable);
}
@Specialization
protected void eval(VmClass parent, ObjectData data, VmIntSeq iterable) {
doEvalIntSeq(parent, data, iterable);
protected void eval(VirtualFrame frame, VmClass parent, ObjectData data, VmIntSeq iterable) {
doEvalIntSeq(frame, parent, data, iterable);
}
@Fallback
@@ -174,64 +177,55 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
throw builder.build();
}
protected void doEvalDynamic(ObjectData data, VmObject iterable) {
var length = new MutableLong(data.length);
protected void doEvalDynamic(VirtualFrame frame, ObjectData data, VmObject iterable) {
iterable.forceAndIterateMemberValues(
(key, member, value) -> {
if (member.isElement()) {
EconomicMaps.put(data.members, length.getAndIncrement(), createMember(member, value));
data.addElement(frame, createMember(member, value), this);
} else {
if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) {
duplicateMember(key, member);
}
data.addMember(frame, key, createMember(member, value), this);
}
return true;
});
data.length = (int) length.get();
}
private void doEvalMapping(ObjectData data, VmObject iterable) {
private void doEvalMapping(VirtualFrame frame, ObjectData data, VmObject iterable) {
iterable.forceAndIterateMemberValues(
(key, member, value) -> {
if (member.isElement() || member.isProp()) {
cannotHaveMember(BaseModule.getMappingClass(), member);
}
if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) {
duplicateMember(key, member);
}
data.addMember(frame, key, createMember(member, value), this);
return true;
});
}
private void doEvalListing(ObjectData data, VmObject iterable) {
var length = new MutableLong(data.length);
private void doEvalListing(VirtualFrame frame, ObjectData data, VmObject iterable) {
iterable.forceAndIterateMemberValues(
(key, member, value) -> {
if (member.isEntry() || member.isProp()) {
cannotHaveMember(getListingClass(), member);
}
EconomicMaps.put(data.members, length.getAndIncrement(), createMember(member, value));
data.addElement(frame, createMember(member, value), this);
return true;
});
data.length = (int) length.get();
}
private void doEvalTyped(VmClass clazz, ObjectData data, VmObject iterable) {
private void doEvalTyped(VirtualFrame frame, VmClass clazz, ObjectData data, VmObject iterable) {
iterable.forceAndIterateMemberValues(
(key, member, value) -> {
if (member.isElement() || member.isEntry()) {
cannotHaveMember(clazz, member);
}
checkTypedProperty(clazz, member);
if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) {
duplicateMember(key, member);
}
checkIsValidTypedProperty(clazz, member);
data.addProperty(frame, createMember(member, value), this);
return true;
});
}
// handles both `List` and `Set`
private void doEvalCollection(VmClass parent, ObjectData data, VmCollection iterable) {
private void doEvalCollection(
VirtualFrame frame, VmClass parent, ObjectData data, VmCollection iterable) {
if (isTypedObjectClass(parent) || parent == getMappingClass()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
@@ -241,10 +235,10 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
.withProgramValue("Value", iterable)
.build();
}
spreadIterable(data, iterable);
spreadIterable(frame, data, iterable);
}
private void doEvalMap(VmClass parent, ObjectData data, VmMap iterable) {
private void doEvalMap(VirtualFrame frame, VmClass parent, ObjectData data, VmMap iterable) {
if (isTypedObjectClass(parent) || parent == getListingClass()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
@@ -255,13 +249,12 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
}
for (var entry : iterable) {
var member = VmUtils.createSyntheticObjectEntry("", VmUtils.getValue(entry));
if (EconomicMaps.put(data.members, VmUtils.getKey(entry), member) != null) {
duplicateMember(VmUtils.getKey(entry), member);
}
data.addMember(frame, VmUtils.getKey(entry), member, this);
}
}
private void doEvalIntSeq(VmClass parent, ObjectData data, VmIntSeq iterable) {
private void doEvalIntSeq(
VirtualFrame frame, VmClass parent, ObjectData data, VmIntSeq iterable) {
if (isTypedObjectClass(parent) || parent == getMappingClass()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
@@ -270,7 +263,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
.withProgramValue("Value", iterable)
.build();
}
spreadIterable(data, iterable);
spreadIterable(frame, data, iterable);
}
private void cannotHaveMember(VmClass clazz, ObjectMember member) {
@@ -295,7 +288,8 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
throw exception;
}
private void duplicateMember(Object key, ObjectMember member) {
@Override
protected VmException duplicateDefinition(Object key, ObjectMember member) {
CompilerDirectives.transferToInterpreter();
var exception =
exceptionBuilder()
@@ -331,51 +325,12 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
return result;
}
@TruffleBoundary
private void spreadIterable(ObjectData data, Iterable<?> iterable) {
var length = data.length;
for (var elem : iterable) {
var index = length++;
var member = VmUtils.createSyntheticObjectElement(String.valueOf(index), elem);
EconomicMaps.put(data.members, (long) index, member);
}
data.length = length;
}
protected void checkTypedProperty(VmClass clazz, ObjectMember member) {
if (member.isLocal()) return;
var memberName = member.getName();
var classProperty = clazz.getProperty(memberName);
if (classProperty == null) {
CompilerDirectives.transferToInterpreter();
var exception =
exceptionBuilder()
.cannotFindProperty(clazz.getPrototype(), memberName, false, false)
.build();
if (member.getHeaderSection().isAvailable()) {
exception
.getInsertedStackFrames()
.put(
getRootNode().getCallTarget(),
VmUtils.createStackFrame(member.getHeaderSection(), member.getQualifiedName()));
}
throw exception;
}
if (classProperty.isConstOrFixed()) {
CompilerDirectives.transferToInterpreter();
var errMsg =
classProperty.isConst() ? "cannotAssignConstProperty" : "cannotAssignFixedProperty";
var exception = exceptionBuilder().evalError(errMsg, memberName).build();
if (member.getHeaderSection().isAvailable()) {
exception
.getInsertedStackFrames()
.put(
getRootNode().getCallTarget(),
VmUtils.createStackFrame(member.getHeaderSection(), member.getQualifiedName()));
}
throw exception;
private void spreadIterable(VirtualFrame frame, ObjectData data, Iterable<?> iterable) {
var iterator = new TruffleIterator<>(iterable);
while (iterator.hasNext()) {
var elem = iterator.next();
var member = VmUtils.createSyntheticObjectElement(String.valueOf(data.length()), elem);
data.addElement(frame, member, this);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ public final class GeneratorWhenNode extends GeneratorMemberNode {
GeneratorMemberNode[] thenNodes,
GeneratorMemberNode[] elseNodes) {
super(sourceSection);
super(sourceSection, false);
this.conditionNode = conditionNode;
this.thenNodes = thenNodes;
this.elseNodes = elseNodes;

View File

@@ -0,0 +1,93 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.UnmodifiableEconomicMap;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.VmObject;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.EconomicMaps;
/** Data collected by {@link GeneratorObjectLiteralNode} to generate a `VmObject`. */
public final class ObjectData {
/** The object's members. */
private final EconomicMap<Object, ObjectMember> members;
/**
* The frames that were active when `members` were generated. Only a subset of members have their
* frames stored ({@link GeneratorMemberNode#isFrameStored}). Frames are stored in
* `owner.extraStorage` and retrieved by `RestoreForBindingsNode` when members are executed
*/
private final EconomicMap<Object, MaterializedFrame> generatorFrames;
/** The object's number of elements. */
private int length;
ObjectData(int parentLength) {
// optimize for memory usage by not estimating minimum size
members = EconomicMaps.create();
generatorFrames = EconomicMaps.create();
length = parentLength;
}
UnmodifiableEconomicMap<Object, ObjectMember> members() {
return members;
}
int length() {
return length;
}
boolean hasNoGeneratorFrames() {
return generatorFrames.isEmpty();
}
void addElement(VirtualFrame frame, ObjectMember member, GeneratorMemberNode node) {
addMember(frame, (long) length, member, node);
length += 1;
}
void addProperty(VirtualFrame frame, ObjectMember member, GeneratorMemberNode node) {
addMember(frame, member.getName(), member, node);
}
void addMember(VirtualFrame frame, Object key, ObjectMember member, GeneratorMemberNode node) {
if (EconomicMaps.put(members, key, member) != null) {
CompilerDirectives.transferToInterpreter();
throw node.duplicateDefinition(key, member);
}
if (node.isFrameStored) {
EconomicMaps.put(generatorFrames, key, frame.materialize());
}
}
<T extends VmObject> T storeGeneratorFrames(T object) {
object.setExtraStorage(generatorFrames);
return object;
}
static MaterializedFrame getGeneratorFrame(VirtualFrame frame) {
@SuppressWarnings("unchecked")
var map = (EconomicMap<Object, MaterializedFrame>) VmUtils.getOwner(frame).getExtraStorage();
var result = EconomicMaps.get(map, VmUtils.getMemberKey(frame));
assert result != null;
return result;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;
/**
* Restores for-generator variable bindings when a member generated by a for-generator is executed.
*/
public final class RestoreForBindingsNode extends ExpressionNode {
private @Child ExpressionNode child;
public RestoreForBindingsNode(ExpressionNode child) {
super(child.getSourceSection());
this.child = child;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
var generatorFrame = ObjectData.getGeneratorFrame(frame);
var numSlots = frame.getFrameDescriptor().getNumberOfSlots();
// This value is constant and could be a constructor argument.
var startSlot = generatorFrame.getFrameDescriptor().getNumberOfSlots() - numSlots;
assert startSlot >= 0;
// Copy locals that are for-generator variables into this frame.
// Slots before `startSlot` (if any) are function arguments
// and must not be copied to preserve scoping rules.
VmUtils.copyLocals(generatorFrame, startSlot, frame, 0, numSlots);
return child.executeGeneric(frame);
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.graalvm.collections.UnmodifiableEconomicMap;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.EconomicMaps;
public final class WriteForVariablesNode extends ExpressionNode {
private final int[] auxiliarySlots;
@Child private ExpressionNode childNode;
public WriteForVariablesNode(int[] auxiliarySlots, ExpressionNode childNode) {
this.auxiliarySlots = auxiliarySlots;
this.childNode = childNode;
}
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var extraStorage = VmUtils.getOwner(frame).getExtraStorage();
assert extraStorage instanceof UnmodifiableEconomicMap;
@SuppressWarnings("unchecked")
var forBindings = (UnmodifiableEconomicMap<Object, Object[]>) extraStorage;
var bindings = EconomicMaps.get(forBindings, VmUtils.getMemberKey(frame));
assert bindings != null;
assert bindings.length == auxiliarySlots.length;
for (var i = 0; i < auxiliarySlots.length; i++) {
frame.setAuxiliarySlot(auxiliarySlots[i], bindings[i]);
}
return childNode.executeGeneric(frame);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,8 +26,6 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.PklRootNode;
import org.pkl.core.ast.SimpleRootNode;
import org.pkl.core.ast.builder.SymbolTable;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
import org.pkl.core.ast.member.FunctionNode;
import org.pkl.core.ast.member.Lambda;
@@ -40,10 +38,7 @@ public final class AmendFunctionNode extends PklNode {
private final PklRootNode initialFunctionRootNode;
@CompilationFinal private int customThisSlot = -1;
public AmendFunctionNode(
ObjectLiteralNode hostNode,
TypeNode[] parameterTypeNodes,
FrameDescriptor hostFrameDescriptor) {
public AmendFunctionNode(ObjectLiteralNode hostNode, TypeNode[] parameterTypeNodes) {
super(hostNode.getSourceSection());
isCustomThisScope = hostNode.isCustomThisScope;
@@ -61,39 +56,7 @@ public final class AmendFunctionNode extends PklNode {
} else {
parameterSlots = new int[0];
}
var hasForGenVars = false;
for (var i = 0; i < hostFrameDescriptor.getNumberOfSlots(); i++) {
var slotInfo = hostFrameDescriptor.getSlotInfo(i);
// Copy for-generator variables from the outer frame descriptor into inner lambda.
//
// We need to do this because at parse time within AstBuilder, we inject for-generator
// variables into the frame descriptor of the containing root node.
// The expectation is that when GeneratorForNode executes, it writes for-generator variables
// into these slots.
//
// In the case of an amend function node, AstBuilder can't determine out that there is another
// frame (e.g. with `new Mixin { ... }` syntax), so it injects for-generator vars into the
// wrong frame.
//
// As a remedy, we simply copy outer variables into this frame if there are any for generator
// variables.
//
// We need to preserve the frame slot index, so we insert dummy identifiers
// for other slots that aren't for generator variables.
if (slotInfo != null && slotInfo.equals(SymbolTable.FOR_GENERATOR_VARIABLE)) {
if (!hasForGenVars) {
hasForGenVars = true;
for (var j = 0; j < i; j++) {
builder.addSlot(FrameSlotKind.Illegal, Identifier.DUMMY, null);
}
}
builder.addSlot(
hostFrameDescriptor.getSlotKind(i), hostFrameDescriptor.getSlotName(i), null);
} else if (hasForGenVars) {
builder.addSlot(FrameSlotKind.Illegal, Identifier.DUMMY, null);
}
}
var objectToAmendSlot = builder.addSlot(FrameSlotKind.Object, new Object(), null);
var objectToAmendSlot = builder.addSlot(FrameSlotKind.Object, null, null);
var frameDescriptor = builder.build();
var subsequentFunctionRootNode =
@@ -138,7 +101,7 @@ public final class AmendFunctionNode extends PklNode {
public VmFunction execute(VirtualFrame frame, VmFunction functionToAmend) {
if (isCustomThisScope && customThisSlot == -1) {
CompilerDirectives.transferToInterpreterAndInvalidate();
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
return new VmFunction(
frame.materialize(),
@@ -184,8 +147,7 @@ public final class AmendFunctionNode extends PklNode {
var arguments = new Object[frameArguments.length];
arguments[0] = functionToAmend.getThisValue();
arguments[1] = functionToAmend;
arguments[2] = false;
System.arraycopy(frameArguments, 3, arguments, 3, frameArguments.length - 3);
System.arraycopy(frameArguments, 2, arguments, 2, frameArguments.length - 2);
var valueToAmend = callNode.call(functionToAmend.getCallTarget(), arguments);
if (!(valueToAmend instanceof VmFunction newFunctionToAmend)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.member.FunctionNode;
import org.pkl.core.ast.member.UnresolvedFunctionNode;
import org.pkl.core.runtime.VmFunction;
@@ -48,7 +47,7 @@ public final class FunctionLiteralNode extends ExpressionNode {
CompilerDirectives.transferToInterpreterAndInvalidate();
functionNode = unresolvedFunctionNode.execute(frame);
if (isCustomThisScope) {
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,7 +66,7 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
parametersDescriptor == null
? new TypeNode[0]
: VmUtils.resolveParameterTypes(frame, parametersDescriptor, parameterTypes);
return new AmendFunctionNode(this, resolvedParameterTypes, frame.getFrameDescriptor());
return new AmendFunctionNode(this, resolvedParameterTypes);
}
@Idempotent

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@ public final class InvokeMethodDirectNode extends ExpressionNode {
private final VmObjectLike owner;
@Child private ExpressionNode receiverNode;
@Children private final ExpressionNode[] argumentNodes;
private final boolean isInIterable;
@Child private DirectCallNode callNode;
@@ -36,14 +35,12 @@ public final class InvokeMethodDirectNode extends ExpressionNode {
SourceSection sourceSection,
ClassMethod method,
ExpressionNode receiverNode,
ExpressionNode[] argumentNodes,
boolean isInIterable) {
ExpressionNode[] argumentNodes) {
super(sourceSection);
this.owner = method.getOwner();
this.receiverNode = receiverNode;
this.argumentNodes = argumentNodes;
this.isInIterable = isInIterable;
callNode = DirectCallNode.create(method.getCallTarget(sourceSection));
}
@@ -51,12 +48,11 @@ public final class InvokeMethodDirectNode extends ExpressionNode {
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = receiverNode.executeGeneric(frame);
args[1] = owner;
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(args);

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,33 +33,29 @@ public final class InvokeMethodLexicalNode extends ExpressionNode {
private final int levelsUp;
@Child private DirectCallNode callNode;
private final boolean isInIterable;
InvokeMethodLexicalNode(
SourceSection sourceSection,
CallTarget callTarget,
int levelsUp,
ExpressionNode[] argumentNodes,
boolean isInIterable) {
ExpressionNode[] argumentNodes) {
super(sourceSection);
this.levelsUp = levelsUp;
this.argumentNodes = argumentNodes;
callNode = DirectCallNode.create(callTarget);
this.isInIterable = isInIterable;
}
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
var enclosingFrame = getEnclosingFrame(frame);
args[0] = VmUtils.getReceiver(enclosingFrame);
args[1] = VmUtils.getOwner(enclosingFrame);
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(args);

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,31 +45,27 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@Children private final ExpressionNode[] argumentNodes;
private final MemberLookupMode lookupMode;
private final boolean needsConst;
private final boolean isInIterable;
protected InvokeMethodVirtualNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
boolean needsConst,
boolean isInIterable) {
boolean needsConst) {
super(sourceSection);
this.methodName = methodName;
this.argumentNodes = argumentNodes;
this.lookupMode = lookupMode;
this.needsConst = needsConst;
this.isInIterable = isInIterable;
}
protected InvokeMethodVirtualNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
boolean isInIterable) {
this(sourceSection, methodName, argumentNodes, lookupMode, false, isInIterable);
MemberLookupMode lookupMode) {
this(sourceSection, methodName, argumentNodes, lookupMode, false);
}
/**
@@ -89,12 +85,11 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = receiver.getThisValue();
args[1] = receiver;
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(args);
@@ -109,12 +104,11 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@SuppressWarnings("unused") VmClass receiverClass,
@Exclusive @Cached("create()") IndirectCallNode callNode) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = receiver.getThisValue();
args[1] = receiver;
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(receiver.getCallTarget(), args);
@@ -130,12 +124,11 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@Cached("resolveMethod(receiverClass)") ClassMethod method,
@Cached("create(method.getCallTarget(sourceSection))") DirectCallNode callNode) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = receiver;
args[1] = method.getOwner();
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(args);
@@ -150,12 +143,11 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@Exclusive @Cached("create()") IndirectCallNode callNode) {
var method = resolveMethod(receiverClass);
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = receiver;
args[1] = method.getOwner();
args[2] = isInIterable;
for (var i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
// Deprecation should not report here (getCallTarget(sourceSection)), as this happens for each

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,18 +30,15 @@ import org.pkl.core.runtime.VmUtils;
public abstract class InvokeSuperMethodNode extends ExpressionNode {
private final Identifier methodName;
@Children private final ExpressionNode[] argumentNodes;
private final boolean isInIterable;
private final boolean needsConst;
protected InvokeSuperMethodNode(
SourceSection sourceSection,
Identifier methodName,
boolean isInIterable,
ExpressionNode[] argumentNodes,
boolean needsConst) {
super(sourceSection);
this.isInIterable = isInIterable;
this.needsConst = needsConst;
assert !methodName.isLocalMethod();
@@ -57,12 +54,11 @@ public abstract class InvokeSuperMethodNode extends ExpressionNode {
@Cached(value = "findSupermethod(frame)", neverDefault = true) ClassMethod supermethod,
@Cached("create(supermethod.getCallTarget(sourceSection))") DirectCallNode callNode) {
var args = new Object[3 + argumentNodes.length];
var args = new Object[2 + argumentNodes.length];
args[0] = VmUtils.getReceiverOrNull(frame);
args[1] = supermethod.getOwner();
args[2] = isInIterable;
for (int i = 0; i < argumentNodes.length; i++) {
args[3 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
return callNode.call(args);

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,6 @@ public final class ResolveMethodNode extends ExpressionNode {
private final boolean isCustomThisScope;
private final ConstLevel constLevel;
private final int constDepth;
private final boolean isInIterable;
public ResolveMethodNode(
SourceSection sourceSection,
@@ -59,8 +58,7 @@ public final class ResolveMethodNode extends ExpressionNode {
boolean isBaseModule,
boolean isCustomThisScope,
ConstLevel constLevel,
int constDepth,
boolean isInIterable) {
int constDepth) {
super(sourceSection);
@@ -70,7 +68,6 @@ public final class ResolveMethodNode extends ExpressionNode {
this.isCustomThisScope = isCustomThisScope;
this.constLevel = constLevel;
this.constDepth = constDepth;
this.isInIterable = isInIterable;
}
@Override
@@ -94,11 +91,7 @@ public final class ResolveMethodNode extends ExpressionNode {
assert localMethod.isLocal();
checkConst(currOwner, localMethod, levelsUp);
return new InvokeMethodLexicalNode(
sourceSection,
localMethod.getCallTarget(sourceSection),
levelsUp,
argumentNodes,
isInIterable);
sourceSection, localMethod.getCallTarget(sourceSection), levelsUp, argumentNodes);
}
var method = currOwner.getVmClass().getDeclaredMethod(methodName);
if (method != null) {
@@ -106,11 +99,7 @@ public final class ResolveMethodNode extends ExpressionNode {
checkConst(currOwner, method, levelsUp);
if (method.getDeclaringClass().isClosed()) {
return new InvokeMethodLexicalNode(
sourceSection,
method.getCallTarget(sourceSection),
levelsUp,
argumentNodes,
isInIterable);
sourceSection, method.getCallTarget(sourceSection), levelsUp, argumentNodes);
}
//noinspection ConstantConditions
@@ -119,7 +108,6 @@ public final class ResolveMethodNode extends ExpressionNode {
methodName,
argumentNodes,
MemberLookupMode.IMPLICIT_LEXICAL,
isInIterable,
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
GetClassNodeGen.create(null));
}
@@ -134,7 +122,7 @@ public final class ResolveMethodNode extends ExpressionNode {
(CallTarget) localMethod.getCallTarget().call(currOwner, currOwner);
return new InvokeMethodLexicalNode(
sourceSection, methodCallTarget, levelsUp, argumentNodes, isInIterable);
sourceSection, methodCallTarget, levelsUp, argumentNodes);
}
}
@@ -150,7 +138,7 @@ public final class ResolveMethodNode extends ExpressionNode {
if (method != null) {
assert !method.isLocal();
return new InvokeMethodDirectNode(
sourceSection, method, new ConstantValueNode(baseModule), argumentNodes, isInIterable);
sourceSection, method, new ConstantValueNode(baseModule), argumentNodes);
}
}
@@ -170,7 +158,6 @@ public final class ResolveMethodNode extends ExpressionNode {
argumentNodes,
MemberLookupMode.IMPLICIT_THIS,
needsConst,
isInIterable,
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThisScope),
GetClassNodeGen.create(null));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.runtime.VmUtils;
/** `this` inside `CustomThisScope` (type constraint, object member predicate). */
@@ -38,7 +37,7 @@ public final class CustomThisNode extends ExpressionNode {
if (customThisSlot == -1) {
CompilerDirectives.transferToInterpreterAndInvalidate();
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
return frame.getAuxiliarySlot(customThisSlot);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@ import org.pkl.core.ast.MemberLookupMode;
import org.pkl.core.ast.builder.ConstLevel;
import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
import org.pkl.core.ast.frame.ReadAuxiliarySlotNode;
import org.pkl.core.ast.frame.ReadEnclosingAuxiliarySlotNode;
import org.pkl.core.ast.frame.ReadEnclosingFrameSlotNodeGen;
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
import org.pkl.core.ast.member.Member;
@@ -34,14 +32,23 @@ import org.pkl.core.runtime.*;
/**
* Resolves a variable name, for example `foo` in `x = foo`.
*
* <p>A variable name can refer to any of the following: - a (potentially `local`) property in the
* lexical scope - a method or lambda parameter in the lexical scope - a base module property - a
* property accessible through `this`
* <p>A variable name can refer to any of the following:
*
* <ul>
* <li>a method/lambda parameter or for-generator/let-expression variable in the lexical scope
* <li>a (potentially `local`) property in the lexical scope
* <li>a `pkl.base` module property
* <li>a property accessible through `this`
* </ul>
*
* <p>This node's task is to make a one-time decision between these alternatives for the call site
* it represents.
*/
// TODO: Move this to parse time (required for supporting local variables, more efficient)
// TODO: Move this to parse time
// * more capable because more information is available
// and AST customization beyond replacing this node is possible
// * useful for runtime AST transformations, for example to implement property-based testing
// * more efficient
//
// TODO: In REPL, undo replace if environment changes to make the following work.
// Perhaps instrumenting this node in REPL would be a good solution.
@@ -82,49 +89,18 @@ public final class ResolveVariableNode extends ExpressionNode {
CompilerDirectives.transferToInterpreter();
var localPropertyName = variableName.toLocalProperty();
// search the frame for auxiliary slots carrying this variable (placed by
// `WriteForVariablesNode`)
var variableSlot = VmUtils.findAuxiliarySlot(frame, localPropertyName);
if (variableSlot == -1) {
variableSlot = VmUtils.findAuxiliarySlot(frame, variableName);
}
if (variableSlot != -1) {
return new ReadAuxiliarySlotNode(getSourceSection(), variableSlot);
}
// search the frame for slots carrying this variable
variableSlot = VmUtils.findSlot(frame, localPropertyName);
if (variableSlot == -1) {
variableSlot = VmUtils.findSlot(frame, variableName);
}
if (variableSlot != -1) {
return ReadFrameSlotNodeGen.create(getSourceSection(), variableSlot);
}
var currFrame = frame;
var currOwner = VmUtils.getOwner(currFrame);
var levelsUp = 0;
// Search lexical scope for a matching method/lambda parameter, `for` generator variable, or
// object property.
// Search lexical scope for a matching function parameter, for-generator variable, or object
// property.
do {
var parameterSlot = VmUtils.findSlot(currFrame, variableName);
if (parameterSlot == -1) {
parameterSlot = VmUtils.findSlot(currFrame, localPropertyName);
}
if (parameterSlot != -1) {
var slot = findFrameSlot(currFrame, variableName, localPropertyName);
if (slot != -1) {
return levelsUp == 0
? ReadFrameSlotNodeGen.create(getSourceSection(), parameterSlot)
: ReadEnclosingFrameSlotNodeGen.create(getSourceSection(), parameterSlot, levelsUp);
}
var auxiliarySlot = VmUtils.findAuxiliarySlot(currFrame, variableName);
if (auxiliarySlot == -1) {
auxiliarySlot = VmUtils.findAuxiliarySlot(currFrame, localPropertyName);
}
if (auxiliarySlot != -1) {
return levelsUp == 0
? new ReadAuxiliarySlotNode(getSourceSection(), auxiliarySlot)
: new ReadEnclosingAuxiliarySlotNode(getSourceSection(), auxiliarySlot, levelsUp);
? ReadFrameSlotNodeGen.create(getSourceSection(), slot)
: ReadEnclosingFrameSlotNodeGen.create(getSourceSection(), slot, levelsUp);
}
var localMember = currOwner.getMember(localPropertyName);
@@ -136,8 +112,8 @@ public final class ResolveVariableNode extends ExpressionNode {
var value = localMember.getConstantValue();
if (value != null) {
// This is the only code path that resolves local constant properties.
// Since this code path doesn't use ObjectMember.getCachedValue(),
// there is no point in calling localMember.setCachedValue() either.
// Since this code path doesn't call VmObject.getCachedValue(),
// there is no point in calling VmObject.setCachedValue() either.
return new ConstantValueNode(sourceSection, value);
}
@@ -236,4 +212,17 @@ public final class ResolveVariableNode extends ExpressionNode {
throw exceptionBuilder().evalError("propertyMustBeConst", variableName.toString()).build();
}
}
private static int findFrameSlot(VirtualFrame frame, Object identifier1, Object identifier2) {
var descriptor = frame.getFrameDescriptor();
// Search backwards. The for-generator implementation exploits this
// to shadow a slot by appending a slot with the same name.
for (var i = descriptor.getNumberOfSlots() - 1; i >= 0; i--) {
var slotName = descriptor.getSlotName(i);
if (slotName == identifier1 || slotName == identifier2) {
return i;
}
}
return -1;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.frame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
public class ReadAuxiliarySlotNode extends ExpressionNode {
private final int slot;
public ReadAuxiliarySlotNode(SourceSection sourceSection, int slot) {
super(sourceSection);
this.slot = slot;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
return frame.getAuxiliarySlot(slot);
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.frame;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;
public class ReadEnclosingAuxiliarySlotNode extends ExpressionNode {
private final int slot;
private final int levelsUp;
public ReadEnclosingAuxiliarySlotNode(SourceSection sourceSection, int slot, int levelsUp) {
super(sourceSection);
this.slot = slot;
this.levelsUp = levelsUp;
}
// could be factored out into a separate node s.t. it
// won't be repeated in case of FrameSlotTypeException
@ExplodeLoop
protected final MaterializedFrame getCapturedFrame(VirtualFrame frame) {
var owner = VmUtils.getOwner(frame);
for (var i = 0; i < levelsUp - 1; i++) {
owner = owner.getEnclosingOwner();
assert owner != null; // guaranteed by AstBuilder
}
return owner.getEnclosingFrame();
}
@Override
public Object executeGeneric(VirtualFrame frame) {
return getCapturedFrame(frame).getAuxiliarySlot(slot);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,7 +80,6 @@ public abstract class ToStringNode extends UnaryExpressionNode {
Identifier.TO_STRING,
new ExpressionNode[] {},
MemberLookupMode.EXPLICIT_RECEIVER,
false,
null,
null);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,12 +33,12 @@ public abstract class ApplyVmFunction0Node extends PklNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false);
return callNode.call(function.getThisValue(), function);
}
@Specialization(replaces = "evalDirect")
protected Object eval(VmFunction function, @Cached("create()") IndirectCallNode callNode) {
return callNode.call(function.getCallTarget(), function.getThisValue(), function, false);
return callNode.call(function.getCallTarget(), function.getThisValue(), function);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,13 +77,13 @@ public abstract class ApplyVmFunction1Node extends ExpressionNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false, arg1);
return callNode.call(function.getThisValue(), function, arg1);
}
@Specialization(replaces = "evalDirect")
protected Object eval(
VmFunction function, Object arg1, @Cached("create()") IndirectCallNode callNode) {
return callNode.call(function.getCallTarget(), function.getThisValue(), function, false, arg1);
return callNode.call(function.getCallTarget(), function.getThisValue(), function, arg1);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,7 +76,7 @@ public abstract class ApplyVmFunction2Node extends PklNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false, arg1, arg2);
return callNode.call(function.getThisValue(), function, arg1, arg2);
}
@Specialization(replaces = "evalDirect")
@@ -86,7 +86,6 @@ public abstract class ApplyVmFunction2Node extends PklNode {
Object arg2,
@Cached("create()") IndirectCallNode callNode) {
return callNode.call(
function.getCallTarget(), function.getThisValue(), function, false, arg1, arg2);
return callNode.call(function.getCallTarget(), function.getThisValue(), function, arg1, arg2);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public abstract class ApplyVmFunction3Node extends PklNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false, arg1, arg2, arg3);
return callNode.call(function.getThisValue(), function, arg1, arg2, arg3);
}
@Specialization(replaces = "evalDirect")
@@ -48,6 +48,6 @@ public abstract class ApplyVmFunction3Node extends PklNode {
@Cached("create()") IndirectCallNode callNode) {
return callNode.call(
function.getCallTarget(), function.getThisValue(), function, false, arg1, arg2, arg3);
function.getCallTarget(), function.getThisValue(), function, arg1, arg2, arg3);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ public abstract class ApplyVmFunction4Node extends PklNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false, arg1, arg2, arg3, arg4);
return callNode.call(function.getThisValue(), function, arg1, arg2, arg3, arg4);
}
@Specialization(replaces = "evalDirect")
@@ -51,6 +51,6 @@ public abstract class ApplyVmFunction4Node extends PklNode {
@Cached("create()") IndirectCallNode callNode) {
return callNode.call(
function.getCallTarget(), function.getThisValue(), function, false, arg1, arg2, arg3, arg4);
function.getCallTarget(), function.getThisValue(), function, arg1, arg2, arg3, arg4);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ public abstract class ApplyVmFunction5Node extends PklNode {
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
return callNode.call(function.getThisValue(), function, false, arg1, arg2, arg3, arg4, arg5);
return callNode.call(function.getThisValue(), function, arg1, arg2, arg3, arg4, arg5);
}
@Specialization(replaces = "evalDirect")
@@ -53,14 +53,6 @@ public abstract class ApplyVmFunction5Node extends PklNode {
@Cached("create()") IndirectCallNode callNode) {
return callNode.call(
function.getCallTarget(),
function.getThisValue(),
function,
false,
arg1,
arg2,
arg3,
arg4,
arg5);
function.getCallTarget(), function.getThisValue(), function, arg1, arg2, arg3, arg4, arg5);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,11 +43,7 @@ public final class FunctionNode extends RegularMemberNode {
// For VmObject receivers, the owner is the same as or an ancestor of the receiver.
// For other receivers, the owner is the prototype of the receiver's class.
// The chain of enclosing owners forms a function/property's lexical scope.
//
// For function calls only, a third implicit argument is passed; whether the call came from within
// an iterable node or not.
// This is a mitigation for an existing bug (https://github.com/apple/pkl/issues/741).
private static final int IMPLICIT_PARAM_COUNT = 3;
private static final int IMPLICIT_PARAM_COUNT = 2;
private final int paramCount;
private final int totalParamCount;
@@ -112,15 +108,9 @@ public final class FunctionNode extends RegularMemberNode {
throw wrongArgumentCount(totalArgCount - IMPLICIT_PARAM_COUNT);
}
var isInIterable = (boolean) frame.getArguments()[2];
for (var i = 0; i < parameterTypeNodes.length; i++) {
var argument = frame.getArguments()[IMPLICIT_PARAM_COUNT + i];
if (isInIterable) {
parameterTypeNodes[i].executeEagerlyAndSet(frame, argument);
} else {
parameterTypeNodes[i].executeAndSet(frame, argument);
}
parameterTypeNodes[i].executeAndSet(frame, argument);
}
var result = bodyNode.executeGeneric(frame);

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,20 +130,4 @@ public abstract class Member {
public final boolean isLocalOrExternalOrAbstract() {
return VmModifier.isLocalOrExternalOrAbstract(modifiers);
}
/**
* Tells if this member is declared inside the iterable of a for-generator, or an object spread.
*
* <p>This is {@code true} for {@code new {}} within:
*
* <pre>{@code
* for (x in new Listing { new {} }) {
* ^^^^^^
* // etc
* }
* }</pre>
*/
public boolean isInIterable() {
return VmModifier.isInIterable(modifiers);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,24 +59,8 @@ public final class PropertyTypeNode extends PklRootNode {
return qualifiedPropertyName;
}
private boolean isInIterable(VirtualFrame frame) {
var args = frame.getArguments();
return args.length >= 4 && args[3] instanceof Boolean b && b;
}
@Override
protected Object executeImpl(VirtualFrame frame) {
if (isInIterable(frame)) {
// There is currently a bug around resolving variables within the iterable of a for
// generator or spread syntax (https://github.com/apple/pkl/issues/741)
//
// Normally, mappings/listings are type-checked lazily. However, this results in said
// bug getting widened, for any object members declared in the iterable.
//
// As a workaround for now, prevent the bug from being any worse by ensuring that these
// object members are eagerly typechecked.
return typeNode.executeEagerly(frame, frame.getArguments()[2]);
}
return typeNode.execute(frame, frame.getArguments()[2]);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,8 +55,7 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
// TODO: propagate SUPER_CALL_MARKER to disable constraint (but not type) check
if (callNode != null && VmUtils.shouldRunTypeCheck(frame)) {
return callNode.call(
VmUtils.getReceiverOrNull(frame), property.getOwner(), result, member.isInIterable());
return callNode.call(VmUtils.getReceiverOrNull(frame), property.getOwner(), result);
}
return result;
@@ -76,8 +75,7 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
typeAnnNode.getCallTarget(),
VmUtils.getReceiverOrNull(frame),
property.getOwner(),
result,
member.isInIterable());
result);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,10 +47,7 @@ public final class TypedPropertyNode extends RegularMemberNode {
var propertyValue = bodyNode.executeGeneric(frame);
if (VmUtils.shouldRunTypeCheck(frame)) {
return typeCheckCallNode.call(
VmUtils.getReceiver(frame),
VmUtils.getOwner(frame),
propertyValue,
member.isInIterable());
VmUtils.getReceiver(frame), VmUtils.getOwner(frame), propertyValue);
}
return propertyValue;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,14 +54,14 @@ public final class IdentityMixinNode extends PklRootNode {
@Override
protected Object executeImpl(VirtualFrame frame) {
var arguments = frame.getArguments();
if (arguments.length != 4) {
if (arguments.length != 3) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("wrongFunctionArgumentCount", 1, arguments.length - 3)
.evalError("wrongFunctionArgumentCount", 1, arguments.length - 2)
.withSourceSection(sourceSection)
.build();
}
var argument = arguments[3];
var argument = arguments[2];
if (argumentTypeNode != null) {
return argumentTypeNode.execute(frame, argument);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.lambda.ApplyVmFunction1Node;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.VmFunction;
@@ -87,7 +86,7 @@ public abstract class TypeConstraintNode extends PklNode {
if (customThisSlot == -1) {
CompilerDirectives.transferToInterpreterAndInvalidate();
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
customThisSlot = VmUtils.findAuxiliarySlot(frame, CustomThisScope.FRAME_SLOT_ID);
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -95,14 +95,6 @@ public abstract class TypeNode extends PklNode {
return execute(frame, value);
}
/**
* Checks if {@code value} conforms to this type.
*
* <p>If {@code value} is conforming, sets {@code slot} to {@code value}. Otherwise, throws a
* {@link VmTypeMismatchException}.
*/
public abstract Object executeEagerlyAndSet(VirtualFrame frame, Object value);
// method arguments are used when default value contains a root node
public @Nullable Object createDefaultValue(
VmLanguage language,
@@ -226,11 +218,6 @@ public abstract class TypeNode extends PklNode {
frame.setLong(slot, (long) value);
return value;
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
return executeAndSet(frame, value);
}
}
public abstract static class ObjectSlotTypeNode extends FrameSlotTypeNode {
@@ -248,13 +235,6 @@ public abstract class TypeNode extends PklNode {
frame.setObject(slot, result);
return result;
}
@Override
public final Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
var result = executeEagerly(frame, value);
frame.setObject(slot, result);
return result;
}
}
/**
@@ -288,13 +268,6 @@ public abstract class TypeNode extends PklNode {
writeSlotNode.executeWithValue(frame, result);
return result;
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
var result = executeEagerly(frame, value);
writeSlotNode.executeWithValue(frame, result);
return result;
}
}
/** The `unknown` type. */
@@ -360,11 +333,6 @@ public abstract class TypeNode extends PklNode {
throw PklBugException.unreachableCode();
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
return executeAndSet(frame, value);
}
@Override
public FrameSlotKind getFrameSlotKind() {
return FrameSlotKind.Illegal;
@@ -2435,22 +2403,6 @@ public abstract class TypeNode extends PklNode {
}
}
/** See docstring on {@link TypeAliasTypeNode#execute}. */
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame);
setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try {
return aliasedTypeNode.executeEagerlyAndSet(frame, value);
} finally {
setOwner(frame, prevOwner);
setReceiver(frame, prevReceiver);
}
}
@Override
@TruffleBoundary
public @Nullable Object createDefaultValue(
@@ -2586,13 +2538,6 @@ public abstract class TypeNode extends PklNode {
return ret;
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
var ret = executeEagerly(frame, value);
childNode.executeEagerlyAndSet(frame, ret);
return ret;
}
@Override
public @Nullable Object createDefaultValue(
VmLanguage language, SourceSection headerSection, String qualifiedName) {
@@ -2740,11 +2685,6 @@ public abstract class TypeNode extends PklNode {
}
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
return executeAndSet(frame, value);
}
@Override
public VmClass getVmClass() {
return BaseModule.getNumberClass();
@@ -2813,11 +2753,6 @@ public abstract class TypeNode extends PklNode {
return value;
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
return executeAndSet(frame, value);
}
@Override
public VmClass getVmClass() {
return BaseModule.getFloatClass();
@@ -2858,11 +2793,6 @@ public abstract class TypeNode extends PklNode {
return value;
}
@Override
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
return executeAndSet(frame, value);
}
@Override
public VmClass getVmClass() {
return BaseModule.getBooleanClass();

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -141,9 +141,6 @@ public final class Identifier implements Comparable<Identifier> {
// common in lambdas etc
public static final Identifier IT = get("it");
// dummy, unrepresentable identifier
public static final Identifier DUMMY = get("`#_");
private final String name;
private Identifier(String name) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public final class VmFunction extends VmObjectLike {
// if call site is a node, use ApplyVmFunction1Node.execute() or DirectCallNode.call() instead of
// this method
public Object apply(Object arg1) {
return getCallTarget().call(thisValue, this, false, arg1);
return getCallTarget().call(thisValue, this, arg1);
}
public String applyString(Object arg1) {
@@ -69,7 +69,7 @@ public final class VmFunction extends VmObjectLike {
// if call site is a node, use ApplyVmFunction2Node.execute() or DirectCallNode.call() instead of
// this method
public Object apply(Object arg1, Object arg2) {
return getCallTarget().call(thisValue, this, false, arg1, arg2);
return getCallTarget().call(thisValue, this, arg1, arg2);
}
public VmFunction copy(

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,8 @@ import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.util.Nullable;
/**
* Corresponds to `pkl.base#Object|pkl.base#Function`. The lexical scope is a hierarchy of
* `VmOwner`s.
* Corresponds to `pkl.base#Object|pkl.base#Function`. The lexical scope is a chain of
* `VmObjectLike` instances.
*/
public abstract class VmObjectLike extends VmValue {
/** The frame that was active when this object was instantiated. * */
@@ -66,8 +66,8 @@ public abstract class VmObjectLike extends VmValue {
}
/**
* Returns the parent object in the prototype chain. For each concrete subclass X of VmOwner, the
* exact return type of this method is `X|VmTyped`.
* Returns the parent object in the prototype chain. For each concrete subclass X of VmObjectLike,
* the exact return type of this method is `X|VmTyped`.
*/
public abstract @Nullable VmObjectLike getParent();

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.SimpleRootNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.builder.AstBuilder;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.expression.primary.CustomThisNode;
import org.pkl.core.ast.expression.primary.ThisNode;
import org.pkl.core.ast.member.*;
@@ -300,6 +301,59 @@ public final class VmUtils {
return result;
}
/**
* Copies {@code numberOfLocalsToCopy} locals from {@code sourceFrame}, starting at {@code
* firstSourceSlot}, to {@code firstSourceSlot}, starting at {@code firstTargetSlot}.
*/
public static void copyLocals(
VirtualFrame sourceFrame,
int firstSourceSlot,
VirtualFrame targetFrame,
int firstTargetSlot,
int numberOfLocalsToCopy) {
var sourceDescriptor = sourceFrame.getFrameDescriptor();
var targetDescriptor = targetFrame.getFrameDescriptor();
// Alternatively, locals could be copied with `numberOfLocalsToCopy`
// `ReadFrameSlotNode/WriteFrameSlotNode`'s.
for (int i = 0; i < numberOfLocalsToCopy; i++) {
var sourceSlot = firstSourceSlot + i;
var targetSlot = firstTargetSlot + i;
// If, for a particular call site of this method,
// slot kinds of `sourceDescriptor` will reach a steady state,
// then slot kinds of `targetDescriptor` will too.
var slotKind = sourceDescriptor.getSlotKind(sourceSlot);
switch (slotKind) {
case Boolean -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Boolean);
targetFrame.setBoolean(targetSlot, sourceFrame.getBoolean(sourceSlot));
}
case Long -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Long);
targetFrame.setLong(targetSlot, sourceFrame.getLong(sourceSlot));
}
case Double -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Double);
targetFrame.setDouble(targetSlot, sourceFrame.getDouble(sourceSlot));
}
case Object -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Object);
targetFrame.setObject(
targetSlot,
sourceFrame instanceof MaterializedFrame
// Even though sourceDescriptor.getSlotKind is now Object,
// it may have been a primitive kind when `sourceFrame`'s local was written.
// Hence we need to read the local with getValue() instead of getObject().
? sourceFrame.getValue(sourceSlot)
: sourceFrame.getObject(sourceSlot));
}
default -> {
CompilerDirectives.transferToInterpreter();
throw new VmExceptionBuilder().bug("Unexpected FrameSlotKind: " + slotKind).build();
}
}
}
}
// A failed property type check looks as follows in the stack trace:
// x: Int(isPositive)
// at ...
@@ -687,9 +741,7 @@ public final class VmUtils {
}
public static TypeNode[] resolveParameterTypes(
VirtualFrame frame,
FrameDescriptor descriptor,
@Nullable UnresolvedTypeNode[] parameterTypeNodes) {
VirtualFrame frame, FrameDescriptor descriptor, UnresolvedTypeNode[] parameterTypeNodes) {
var resolvedNodes = new TypeNode[parameterTypeNodes.length];
@@ -836,25 +888,11 @@ public final class VmUtils {
return callNode.call(rootNode.getCallTarget(), module, module);
}
public static int findSlot(VirtualFrame frame, Object identifier) {
var descriptor = frame.getFrameDescriptor();
for (int i = 0; i < descriptor.getNumberOfSlots(); i++) {
if (descriptor.getSlotName(i) == identifier
&& frame.getTag(i) != FrameSlotKind.Illegal.tag
// Truffle initializes all frame tags as `FrameSlotKind.Object`, instead of
// `FrameSlotKind.Illegal`. Unevaluated (in a `when` with `false` condition) `for`
// generators, therefore, leave their bound variables tagged as `Object`, but `null`. If
// another `for` generator in the same scope binds variables with the same names, they
// resolve the wrong slot number.
&& (frame.getTag(i) != FrameSlotKind.Object.tag || frame.getObject(i) != null)) {
return i;
}
}
return -1;
}
public static int findAuxiliarySlot(VirtualFrame frame, Object identifier) {
return frame.getFrameDescriptor().getAuxiliarySlots().getOrDefault(identifier, -1);
public static int findCustomThisSlot(VirtualFrame frame) {
return frame
.getFrameDescriptor()
.getAuxiliarySlots()
.getOrDefault(CustomThisScope.FRAME_SLOT_ID, -1);
}
@TruffleBoundary

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,12 +31,11 @@ public final class FunctionNodes {
protected Object eval(VmFunction self, VmList argList) {
var argCount = argList.getLength();
var args = new Object[3 + argCount];
var args = new Object[2 + argCount];
args[0] = self.getThisValue();
args[1] = self;
args[2] = false;
var i = 3;
var i = 2;
for (var arg : argList) {
args[i++] = arg;
}

View File

@@ -1,11 +0,0 @@
a = List(1, 2, 3, 4)
b = List("a", "b", "c", "d")
foo {
for (_dup, i in a) {
for (_dup, j in b) {
i + j
}
}
}

View File

@@ -0,0 +1,18 @@
function myMethod(arg) = new {
arg = "property" // same name as method arg
for (key, value in List("one", "two", arg)) { // `arg` resolves to method arg
[Pair(arg, key)] = // `arg` resolves to method arg
Pair(arg, value) // `arg` resolves to object property
}
}
local myLambda = (arg) -> new Dynamic {
arg = "property" // same name as lambda arg
for (key, value in List("one", "two", arg)) { // `arg` resolves to lambda arg
[Pair(arg, key)] = // `arg` resolves to lambda arg
Pair(arg, value) // `arg` resolves to object property
}
}
res1 = myMethod("three")
res2 = myLambda.apply("three")

View File

@@ -0,0 +1,25 @@
// https://github.com/apple/pkl/issues/741
bar = new {}
res1 {
for (i in List(1)) {
...(bar) {
baz {
new { i }
}
}.baz
}
}
res2 {
for (i in List(1)) {
for (elem in (bar) {
baz {
new { i }
}
}.baz) {
elem
}
}
}

View File

@@ -0,0 +1,33 @@
examples {
local a = List("1", "2", "3", "4")
local b = List("a", "b", "c", "d")
["shadow key variable"] {
new {
for (key, outerValue in a) {
for (key, innerValue in b) {
List(outerValue, key, innerValue)
}
}
}
}
["shadow value variable"] {
new {
for (outerKey, value in a) {
for (innerKey, value in b) {
List(outerKey, value, innerKey)
}
}
}
}
["sibling for-generators can use same variable names"] {
new {
for (key, value in a) {
List(key, value)
}
for (key, value in b) {
List(key, value)
}
}
}
}

View File

@@ -1,6 +0,0 @@
Pkl Error
Duplicate definition of member `_dup`.
x | for (_dup, j in b) {
^^^^
at forGeneratorDuplicateParams2#foo (file:///$snippetsDir/input/errors/forGeneratorDuplicateParams2.pkl)

View File

@@ -0,0 +1,12 @@
res1 {
arg = "property"
[Pair("three", 0)] = Pair("property", "one")
[Pair("three", 1)] = Pair("property", "two")
[Pair("three", 2)] = Pair("property", "three")
}
res2 {
arg = "property"
[Pair("three", 0)] = Pair("property", "one")
[Pair("three", 1)] = Pair("property", "two")
[Pair("three", 2)] = Pair("property", "three")
}

View File

@@ -0,0 +1,11 @@
bar {}
res1 {
new {
1
}
}
res2 {
new {
1
}
}

View File

@@ -0,0 +1,54 @@
examples {
["shadow key variable"] {
new {
List("1", 0, "a")
List("1", 1, "b")
List("1", 2, "c")
List("1", 3, "d")
List("2", 0, "a")
List("2", 1, "b")
List("2", 2, "c")
List("2", 3, "d")
List("3", 0, "a")
List("3", 1, "b")
List("3", 2, "c")
List("3", 3, "d")
List("4", 0, "a")
List("4", 1, "b")
List("4", 2, "c")
List("4", 3, "d")
}
}
["shadow value variable"] {
new {
List(0, "a", 0)
List(0, "b", 1)
List(0, "c", 2)
List(0, "d", 3)
List(1, "a", 0)
List(1, "b", 1)
List(1, "c", 2)
List(1, "d", 3)
List(2, "a", 0)
List(2, "b", 1)
List(2, "c", 2)
List(2, "d", 3)
List(3, "a", 0)
List(3, "b", 1)
List(3, "c", 2)
List(3, "d", 3)
}
}
["sibling for-generators can use same variable names"] {
new {
List(0, "1")
List(1, "2")
List(2, "3")
List(3, "4")
List(0, "a")
List(1, "b")
List(2, "c")
List(3, "d")
}
}
}