mirror of
https://github.com/apple/pkl.git
synced 2026-06-06 13:52:49 +02:00
Optimize let expressions as frame slot vars (#1622)
This introduces an optimization to write let expressions to frame slots of the current frame, rather than transforming them into lambdas. As a result, let expressions are _much_ faster to execute.
This commit is contained in:
@@ -54,7 +54,7 @@ import org.pkl.core.ast.builder.SymbolTable.AnnotationScope;
|
|||||||
import org.pkl.core.ast.builder.SymbolTable.ClassScope;
|
import org.pkl.core.ast.builder.SymbolTable.ClassScope;
|
||||||
import org.pkl.core.ast.builder.SymbolTable.ModuleScope;
|
import org.pkl.core.ast.builder.SymbolTable.ModuleScope;
|
||||||
import org.pkl.core.ast.builder.SymbolTable.ObjectScope;
|
import org.pkl.core.ast.builder.SymbolTable.ObjectScope;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorVariable;
|
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorOrLetVariable;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.ImplicitThisProperty;
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitThisProperty;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
||||||
@@ -67,7 +67,7 @@ import org.pkl.core.ast.expression.binary.GreaterThanNodeGen;
|
|||||||
import org.pkl.core.ast.expression.binary.GreaterThanOrEqualNodeGen;
|
import org.pkl.core.ast.expression.binary.GreaterThanOrEqualNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.LessThanNodeGen;
|
import org.pkl.core.ast.expression.binary.LessThanNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.LessThanOrEqualNodeGen;
|
import org.pkl.core.ast.expression.binary.LessThanOrEqualNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.LetExprNode;
|
import org.pkl.core.ast.expression.binary.LetExprNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.LogicalAndNodeGen;
|
import org.pkl.core.ast.expression.binary.LogicalAndNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.LogicalOrNodeGen;
|
import org.pkl.core.ast.expression.binary.LogicalOrNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.MultiplicationNodeGen;
|
import org.pkl.core.ast.expression.binary.MultiplicationNodeGen;
|
||||||
@@ -121,7 +121,6 @@ import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
|
|||||||
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
|
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
|
||||||
import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
|
import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
|
||||||
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
|
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetEnclosingOwnerNode;
|
|
||||||
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
|
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
|
import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetModuleNode;
|
import org.pkl.core.ast.expression.primary.GetModuleNode;
|
||||||
@@ -683,10 +682,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
MemberLookupMode.IMPLICIT_LEXICAL,
|
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||||
needsConst,
|
needsConst,
|
||||||
p.levelsUp() == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(p.levelsUp()));
|
p.levelsUp() == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(p.levelsUp()));
|
||||||
} else if (resolution instanceof ForGeneratorVariable p) {
|
} else if (resolution instanceof ForGeneratorOrLetVariable p) {
|
||||||
// Parameters can possibly write to frame slots actually in a frame that is one level
|
// Parameters can possibly write to frame slots actually in a frame that is one level
|
||||||
// higher than what we can tell at parse time. However, for generator variables always
|
// higher than what we can tell at parse time. However, let exprs and for generator variables
|
||||||
// write to frame slots in the same frame.
|
// always write to frame slots in the same frame.
|
||||||
//
|
//
|
||||||
// function foo(bar) = new Mixin {
|
// function foo(bar) = new Mixin {
|
||||||
// [bar] = 1 <--- actually 1 level, not 0
|
// [bar] = 1 <--- actually 1 level, not 0
|
||||||
@@ -891,29 +890,19 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
Node child = expr;
|
Node child = expr;
|
||||||
var parent = expr.parent();
|
var parent = expr.parent();
|
||||||
var scope = symbolTable.getCurrentScope();
|
var scope = symbolTable.getCurrentScope();
|
||||||
var levelsUp = 0;
|
|
||||||
|
|
||||||
while (parent instanceof IfExpr
|
while (parent instanceof IfExpr
|
||||||
|| parent instanceof TraceExpr
|
|| parent instanceof TraceExpr
|
||||||
|| parent instanceof LetExpr letExpr && letExpr.getExpr() == child) {
|
|| parent instanceof LetExpr letExpr && letExpr.getExpr() == child) {
|
||||||
|
|
||||||
if (parent instanceof LetExpr) {
|
|
||||||
assert scope != null;
|
|
||||||
scope = scope.getParent();
|
|
||||||
levelsUp += 1;
|
|
||||||
}
|
|
||||||
child = parent;
|
child = parent;
|
||||||
parent = parent.parent();
|
parent = parent.parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert scope != null;
|
|
||||||
|
|
||||||
if (parent instanceof ClassProperty || parent instanceof ObjectProperty) {
|
if (parent instanceof ClassProperty || parent instanceof ObjectProperty) {
|
||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
InferParentWithinPropertyNodeGen.create(
|
InferParentWithinPropertyNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()),
|
createSourceSection(expr.newSpan()), scope.getName(), new GetOwnerNode());
|
||||||
scope.getName(),
|
|
||||||
levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp));
|
|
||||||
} else if (parent instanceof ObjectElement
|
} else if (parent instanceof ObjectElement
|
||||||
|| parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) {
|
|| parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) {
|
||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
@@ -921,7 +910,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
ReadPropertyNodeGen.create(
|
ReadPropertyNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()),
|
createSourceSection(expr.newSpan()),
|
||||||
org.pkl.core.runtime.Identifier.DEFAULT,
|
org.pkl.core.runtime.Identifier.DEFAULT,
|
||||||
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)),
|
new GetReceiverNode()),
|
||||||
new GetMemberKeyNode());
|
new GetMemberKeyNode());
|
||||||
} else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) {
|
} else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) {
|
||||||
var isObjectMethod =
|
var isObjectMethod =
|
||||||
@@ -931,18 +920,11 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
isObjectMethod
|
isObjectMethod
|
||||||
? new InferParentWithinObjectMethodNode(
|
? new InferParentWithinObjectMethodNode(
|
||||||
createSourceSection(expr.newSpan()),
|
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode())
|
||||||
language,
|
|
||||||
scopeName,
|
|
||||||
levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp))
|
|
||||||
: new InferParentWithinMethodNode(
|
: new InferParentWithinMethodNode(
|
||||||
createSourceSection(expr.newSpan()),
|
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode());
|
||||||
language,
|
|
||||||
scopeName,
|
|
||||||
levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp));
|
|
||||||
} else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) {
|
} else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) {
|
||||||
// TODO (unclear how to infer type now that let-expression is implemented as lambda
|
// TODO correctly infer parent, e.g. `let (x: Person = new {}) ...`
|
||||||
// invocation)
|
|
||||||
throw exceptionBuilder()
|
throw exceptionBuilder()
|
||||||
.evalError("cannotInferParent")
|
.evalError("cannotInferParent")
|
||||||
.withSourceSection(createSourceSection(expr.newSpan()))
|
.withSourceSection(createSourceSection(expr.newSpan()))
|
||||||
@@ -1113,38 +1095,28 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
public ExpressionNode visitLetExpr(LetExpr letExpr) {
|
public ExpressionNode visitLetExpr(LetExpr letExpr) {
|
||||||
var sourceSection = createSourceSection(letExpr);
|
var sourceSection = createSourceSection(letExpr);
|
||||||
var parameter = letExpr.getParameter();
|
var parameter = letExpr.getParameter();
|
||||||
var frameBuilder = new FrameDescriptorBuilder();
|
UnresolvedTypeNode typeNode = null;
|
||||||
UnresolvedTypeNode[] typeNodes;
|
String binding = null;
|
||||||
var bindings = new ArrayList<String>();
|
var slot = -1;
|
||||||
|
var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder;
|
||||||
if (parameter instanceof TypedIdentifier par) {
|
if (parameter instanceof TypedIdentifier par) {
|
||||||
typeNodes = new UnresolvedTypeNode[] {visitTypeAnnotation(par.getTypeAnnotation())};
|
typeNode = visitTypeAnnotation(par.getTypeAnnotation());
|
||||||
frameBuilder.addSlot(
|
slot =
|
||||||
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null);
|
frameDescriptorBuilder.addSlot(
|
||||||
bindings.add(par.getIdentifier().getValue());
|
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null);
|
||||||
} else {
|
binding = par.getIdentifier().getValue();
|
||||||
typeNodes = new UnresolvedTypeNode[0];
|
|
||||||
}
|
}
|
||||||
|
var bindingExpr = visitExpr(letExpr.getBindingExpr());
|
||||||
var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope();
|
var t = typeNode;
|
||||||
|
var s = slot;
|
||||||
UnresolvedFunctionNode functionNode =
|
return symbolTable.enterLetExpression(
|
||||||
symbolTable.enterLambda(
|
binding,
|
||||||
bindings,
|
slot,
|
||||||
frameBuilder,
|
scope -> {
|
||||||
scope -> {
|
var bodyExpr = visitExpr(letExpr.getExpr());
|
||||||
var expr = visitExpr(letExpr.getExpr());
|
return LetExprNodeGen.create(
|
||||||
return new UnresolvedFunctionNode(
|
sourceSection, scope.getQualifiedName(), t, bodyExpr, s, bindingExpr);
|
||||||
language,
|
});
|
||||||
scope.buildFrameDescriptor(),
|
|
||||||
new Lambda(createSourceSection(letExpr.getExpr()), scope.getQualifiedName()),
|
|
||||||
1,
|
|
||||||
typeNodes,
|
|
||||||
null,
|
|
||||||
expr);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new LetExprNode(
|
|
||||||
sourceSection, functionNode, visitExpr(letExpr.getBindingExpr()), isCustomThisScope);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2159,6 +2131,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
throw PklBugException.unreachableCode();
|
throw PklBugException.unreachableCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FrameDescriptor buildModuleFrameDescriptor() {
|
||||||
|
return symbolTable.getCurrentScope().buildFrameDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
private ResolveDeclaredTypeNode doVisitTypeName(QualifiedIdentifier ctx) {
|
private ResolveDeclaredTypeNode doVisitTypeName(QualifiedIdentifier ctx) {
|
||||||
var identifiers = ctx.getIdentifiers();
|
var identifiers = ctx.getIdentifiers();
|
||||||
return switch (identifiers.size()) {
|
return switch (identifiers.size()) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.pkl.core.ast.VmModifier;
|
|||||||
import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod;
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod;
|
||||||
import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
|
||||||
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
|
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorOrLetVariable;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
||||||
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
||||||
import org.pkl.core.ast.member.ObjectMember;
|
import org.pkl.core.ast.member.ObjectMember;
|
||||||
@@ -142,6 +143,20 @@ public final class SymbolTable {
|
|||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> T enterLetExpression(
|
||||||
|
@Nullable String binding, int slot, Function<LetExpressionScope, T> nodeFactory) {
|
||||||
|
|
||||||
|
// flatten names of let exprs inside other let exprs for presentation purposes
|
||||||
|
var parentScope = currentScope;
|
||||||
|
while (parentScope instanceof LetExpressionScope) {
|
||||||
|
parentScope = parentScope.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert parentScope != null;
|
||||||
|
var qualifiedName = parentScope.qualifiedName + "." + "<let expr>";
|
||||||
|
return doEnter(new LetExpressionScope(currentScope, binding, slot, qualifiedName), nodeFactory);
|
||||||
|
}
|
||||||
|
|
||||||
public <T> T enterProperty(
|
public <T> T enterProperty(
|
||||||
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
||||||
return doEnter(
|
return doEnter(
|
||||||
@@ -339,6 +354,19 @@ public final class SymbolTable {
|
|||||||
return curr;
|
return curr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final Scope skipLambdaAndLetScopes() {
|
||||||
|
var curr = this;
|
||||||
|
while (curr.isLambdaScope() || curr.isLetScope()) {
|
||||||
|
curr = curr.getParent();
|
||||||
|
assert curr != null : "Lambda scope always has a parent";
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isLetScope() {
|
||||||
|
return this instanceof LetExpressionScope;
|
||||||
|
}
|
||||||
|
|
||||||
public final boolean isModuleScope() {
|
public final boolean isModuleScope() {
|
||||||
return this instanceof ModuleScope;
|
return this instanceof ModuleScope;
|
||||||
}
|
}
|
||||||
@@ -348,7 +376,7 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isClassMemberScope() {
|
public final boolean isClassMemberScope() {
|
||||||
var effectiveScope = skipLambdaScopes();
|
var effectiveScope = skipLambdaAndLetScopes();
|
||||||
var parent = effectiveScope.parent;
|
var parent = effectiveScope.parent;
|
||||||
if (parent == null) return false;
|
if (parent == null) return false;
|
||||||
|
|
||||||
@@ -454,8 +482,10 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
var result = fun.apply(lex, levelsUp);
|
var result = fun.apply(lex, levelsUp);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
if (scope instanceof MethodScope || scope instanceof ForGeneratorScope) {
|
if (scope instanceof MethodScope
|
||||||
// fors and methods don't level up
|
|| scope instanceof ForGeneratorScope
|
||||||
|
|| scope instanceof LetExpressionScope) {
|
||||||
|
// fors, methods, and let exprs don't level up
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
levelsUp++;
|
levelsUp++;
|
||||||
@@ -640,6 +670,46 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class LetExpressionScope extends Scope implements LexicalScope {
|
||||||
|
private final @Nullable String binding;
|
||||||
|
private final int slot;
|
||||||
|
|
||||||
|
private static @Nullable Identifier getParentName(Scope parent) {
|
||||||
|
while (parent != null && parent.name == null) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return parent == null ? null : parent.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LetExpressionScope(
|
||||||
|
Scope parent, @Nullable String binding, int slot, String qualifiedName) {
|
||||||
|
super(
|
||||||
|
parent,
|
||||||
|
getParentName(parent),
|
||||||
|
qualifiedName,
|
||||||
|
parent.getConstLevel(),
|
||||||
|
parent.frameDescriptorBuilder);
|
||||||
|
this.binding = binding;
|
||||||
|
this.slot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
if (name.equals("_")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (name.equals(binding)) {
|
||||||
|
return new ForGeneratorOrLetVariable(slot, levelsUp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A generator scope that is resolved eagerly and one level above
|
// A generator scope that is resolved eagerly and one level above
|
||||||
public static final class EagerGeneratorScope extends Scope {
|
public static final class EagerGeneratorScope extends Scope {
|
||||||
private static FrameDescriptorBuilder getFrameDescriptorBuilder(Scope parent) {
|
private static FrameDescriptorBuilder getFrameDescriptorBuilder(Scope parent) {
|
||||||
@@ -679,7 +749,7 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
var index = frameDescriptorBuilder.findSlot(Identifier.get(name));
|
var index = frameDescriptorBuilder.findSlot(Identifier.get(name));
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
return new VariableResolution.ForGeneratorVariable(index, levelsUp);
|
return new ForGeneratorOrLetVariable(index, levelsUp);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ public sealed interface VariableResolution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let, lambda, object body param
|
// method, lambda, object body param
|
||||||
record Parameter(int slot, int levelsUp) implements VariableResolution {}
|
record Parameter(int slot, int levelsUp) implements VariableResolution {}
|
||||||
|
|
||||||
record ForGeneratorVariable(int slot, int levelsUp) implements VariableResolution {}
|
record ForGeneratorOrLetVariable(int slot, int levelsUp) implements VariableResolution {}
|
||||||
|
|
||||||
// Implicit base module lookup
|
// Implicit base module lookup
|
||||||
record ImplicitBaseProperty() implements VariableResolution {}
|
record ImplicitBaseProperty() implements VariableResolution {}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,60 +16,68 @@
|
|||||||
package org.pkl.core.ast.expression.binary;
|
package org.pkl.core.ast.expression.binary;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives;
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
import com.oracle.truffle.api.dsl.NodeChild;
|
||||||
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.ast.member.FunctionNode;
|
import org.pkl.core.ast.type.TypeNode;
|
||||||
import org.pkl.core.ast.member.UnresolvedFunctionNode;
|
import org.pkl.core.ast.type.UnresolvedTypeNode;
|
||||||
import org.pkl.core.runtime.VmFunction;
|
import org.pkl.core.runtime.VmException;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
import org.pkl.core.runtime.VmUtils;
|
||||||
import org.pkl.core.util.LateInit;
|
|
||||||
|
|
||||||
public final class LetExprNode extends ExpressionNode {
|
@NodeChild(value = "bindingNode", type = ExpressionNode.class)
|
||||||
private @Child UnresolvedFunctionNode unresolvedFunctionNode;
|
public abstract class LetExprNode extends ExpressionNode {
|
||||||
private @Child ExpressionNode valueNode;
|
|
||||||
private final boolean isCustomThisScope;
|
|
||||||
|
|
||||||
@CompilationFinal @LateInit private FunctionNode functionNode;
|
private final String qualifiedName;
|
||||||
@Child @LateInit private DirectCallNode callNode;
|
private @Child @Nullable UnresolvedTypeNode unresolvedTypeNode;
|
||||||
@CompilationFinal private int customThisSlot = -1;
|
private @Child ExpressionNode bodyNode;
|
||||||
|
private @Child @Nullable TypeNode typeNode;
|
||||||
|
private final int slot;
|
||||||
|
|
||||||
public LetExprNode(
|
protected LetExprNode(
|
||||||
SourceSection sourceSection,
|
SourceSection sourceSection,
|
||||||
UnresolvedFunctionNode functionNode,
|
String qualifiedName,
|
||||||
ExpressionNode valueNode,
|
@Nullable UnresolvedTypeNode unresolvedTypeNode,
|
||||||
boolean isCustomThisScope) {
|
ExpressionNode bodyNode,
|
||||||
|
int slot) {
|
||||||
super(sourceSection);
|
super(sourceSection);
|
||||||
this.unresolvedFunctionNode = functionNode;
|
this.qualifiedName = qualifiedName;
|
||||||
this.valueNode = valueNode;
|
this.unresolvedTypeNode = unresolvedTypeNode;
|
||||||
this.isCustomThisScope = isCustomThisScope;
|
this.bodyNode = bodyNode;
|
||||||
|
this.slot = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private TypeNode getTypeNode(VirtualFrame frame) {
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
if (typeNode == null) {
|
||||||
if (functionNode == null) {
|
|
||||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
functionNode = unresolvedFunctionNode.execute(frame);
|
if (unresolvedTypeNode != null) {
|
||||||
callNode = insert(DirectCallNode.create(functionNode.getCallTarget()));
|
typeNode = unresolvedTypeNode.execute(frame);
|
||||||
if (isCustomThisScope) {
|
} else {
|
||||||
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
|
typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection());
|
||||||
customThisSlot = VmUtils.findCustomThisSlot(frame);
|
|
||||||
}
|
}
|
||||||
|
typeNode.initWriteSlotNode(slot);
|
||||||
|
insert(typeNode);
|
||||||
}
|
}
|
||||||
|
assert typeNode != null;
|
||||||
|
return typeNode;
|
||||||
|
}
|
||||||
|
|
||||||
var function =
|
@Specialization
|
||||||
new VmFunction(
|
protected Object eval(VirtualFrame frame, Object value) {
|
||||||
frame.materialize(),
|
if (slot != -1) {
|
||||||
isCustomThisScope ? frame.getAuxiliarySlot(customThisSlot) : VmUtils.getReceiver(frame),
|
getTypeNode(frame).executeAndSet(frame, value);
|
||||||
1,
|
}
|
||||||
functionNode,
|
try {
|
||||||
null);
|
return bodyNode.executeGeneric(frame);
|
||||||
|
} catch (VmException e) {
|
||||||
var value = valueNode.executeGeneric(frame);
|
CompilerDirectives.transferToInterpreter();
|
||||||
|
e.getInsertedStackFrames()
|
||||||
return callNode.call(function.getThisValue(), function, value);
|
.put(
|
||||||
|
getRootNode().getCallTarget(),
|
||||||
|
VmUtils.createStackFrame(getSourceSection(), qualifiedName));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -961,12 +961,16 @@ public final class VmUtils {
|
|||||||
resolvedModule,
|
resolvedModule,
|
||||||
false);
|
false);
|
||||||
var language = VmLanguage.get(null);
|
var language = VmLanguage.get(null);
|
||||||
var builder = new AstBuilder(source, language, moduleInfo, moduleResolver);
|
|
||||||
var parsedExpression = parseExpressionNode(expression, source);
|
var parsedExpression = parseExpressionNode(expression, source);
|
||||||
|
var builder = new AstBuilder(source, language, moduleInfo, moduleResolver);
|
||||||
var exprNode = builder.visitExpr(parsedExpression);
|
var exprNode = builder.visitExpr(parsedExpression);
|
||||||
var rootNode =
|
var rootNode =
|
||||||
new SimpleRootNode(
|
new SimpleRootNode(
|
||||||
language, new FrameDescriptor(), exprNode.getSourceSection(), "", exprNode);
|
language,
|
||||||
|
builder.buildModuleFrameDescriptor(),
|
||||||
|
exprNode.getSourceSection(),
|
||||||
|
"",
|
||||||
|
exprNode);
|
||||||
var callNode = Truffle.getRuntime().createIndirectCallNode();
|
var callNode = Truffle.getRuntime().createIndirectCallNode();
|
||||||
return callNode.call(rootNode.getCallTarget(), module, module);
|
return callNode.call(rootNode.getCallTarget(), module, module);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public final class MicrobenchmarkNodes {
|
|||||||
var runIterationsNode =
|
var runIterationsNode =
|
||||||
new RunIterationsNode(
|
new RunIterationsNode(
|
||||||
VmLanguage.get(this),
|
VmLanguage.get(this),
|
||||||
new FrameDescriptor(),
|
codeMemberNode.getFrameDescriptor(),
|
||||||
(ExpressionNode) codeMemberNode.getBodyNode().deepCopy());
|
(ExpressionNode) codeMemberNode.getBodyNode().deepCopy());
|
||||||
var callTarget = runIterationsNode.getCallTarget();
|
var callTarget = runIterationsNode.getCallTarget();
|
||||||
return runBenchmark(self, (iterations) -> callTarget.call(self, self, iterations));
|
return runBenchmark(self, (iterations) -> callTarget.call(self, self, iterations));
|
||||||
|
|||||||
@@ -10,24 +10,25 @@ res2 =
|
|||||||
|
|
||||||
res3 =
|
res3 =
|
||||||
let (x = 1)
|
let (x = 1)
|
||||||
let (y = 2)
|
let (y = 2)
|
||||||
x + y + x
|
x + y + x
|
||||||
|
|
||||||
res4 =
|
res4 =
|
||||||
let (x = 1)
|
let (x = 1)
|
||||||
let (x = 2)
|
let (x = 2)
|
||||||
x + x
|
x + x
|
||||||
|
|
||||||
res5 =
|
res5 =
|
||||||
let (price = 500) new {
|
let (price = 500)
|
||||||
lowestPrice = price - 100
|
new {
|
||||||
averagePrice = new {
|
lowestPrice = price - 100
|
||||||
price
|
averagePrice = new {
|
||||||
|
price
|
||||||
|
}
|
||||||
|
highestPrice = new {
|
||||||
|
["price"] = price + 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
highestPrice = new {
|
|
||||||
["price"] = price + 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res6 =
|
res6 =
|
||||||
let (str = "Pigeon".reverse())
|
let (str = "Pigeon".reverse())
|
||||||
@@ -51,8 +52,9 @@ local g = (a) ->
|
|||||||
|
|
||||||
res9 = g.apply(3)
|
res9 = g.apply(3)
|
||||||
|
|
||||||
local h = let (b = 2)
|
local h =
|
||||||
(a) -> a + b
|
let (b = 2)
|
||||||
|
(a) -> a + b
|
||||||
|
|
||||||
res10 = h.apply(3)
|
res10 = h.apply(3)
|
||||||
|
|
||||||
@@ -71,9 +73,9 @@ res12 = new Lets {
|
|||||||
|
|
||||||
res13 =
|
res13 =
|
||||||
let (x = 1)
|
let (x = 1)
|
||||||
let (y = x)
|
let (y = x)
|
||||||
let (z = y)
|
let (z = y)
|
||||||
x + y + z
|
x + y + z
|
||||||
|
|
||||||
// x can't access y
|
// x can't access y
|
||||||
res14 = test.catch(() -> let (x = y) let (y = 2) x + y)
|
res14 = test.catch(() -> let (x = y) let (y = 2) x + y)
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
res1 =
|
res1 =
|
||||||
let (x = 1)
|
let (x = 1)
|
||||||
let (y = 2)
|
let (y = 2)
|
||||||
throw("ouch")
|
throw("ouch")
|
||||||
|
|||||||
+2
-6
@@ -3,15 +3,11 @@ ouch
|
|||||||
|
|
||||||
x | throw("ouch")
|
x | throw("ouch")
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
at letExpressionError1#res1.<function#2> (file:///$snippetsDir/input/errors/letExpressionError1.pkl)
|
at letExpressionError1#res1 (file:///$snippetsDir/input/errors/letExpressionError1.pkl)
|
||||||
|
|
||||||
x | let (y = 2)
|
|
||||||
^^^^^^^^^^^
|
|
||||||
at letExpressionError1#res1.<function#1> (file:///$snippetsDir/input/errors/letExpressionError1.pkl)
|
|
||||||
|
|
||||||
x | let (x = 1)
|
x | let (x = 1)
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
at letExpressionError1#res1 (file:///$snippetsDir/input/errors/letExpressionError1.pkl)
|
at letExpressionError1#res1.<let expr> (file:///$snippetsDir/input/errors/letExpressionError1.pkl)
|
||||||
|
|
||||||
xxx | renderer.renderDocument(value)
|
xxx | renderer.renderDocument(value)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
+2
-2
@@ -3,11 +3,11 @@ ouch
|
|||||||
|
|
||||||
x | let (y = throw("ouch"))
|
x | let (y = throw("ouch"))
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
at letExpressionError2#res1.<function#1> (file:///$snippetsDir/input/errors/letExpressionError2.pkl)
|
at letExpressionError2#res1 (file:///$snippetsDir/input/errors/letExpressionError2.pkl)
|
||||||
|
|
||||||
x | let (x = 1)
|
x | let (x = 1)
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
at letExpressionError2#res1 (file:///$snippetsDir/input/errors/letExpressionError2.pkl)
|
at letExpressionError2#res1.<let expr> (file:///$snippetsDir/input/errors/letExpressionError2.pkl)
|
||||||
|
|
||||||
xxx | renderer.renderDocument(value)
|
xxx | renderer.renderDocument(value)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
-4
@@ -4,10 +4,6 @@ Value: "abc"
|
|||||||
|
|
||||||
x | let (x: Float = "abc")
|
x | let (x: Float = "abc")
|
||||||
^^^^^
|
^^^^^
|
||||||
at letExpressionErrorTyped#res1.<function#1> (file:///$snippetsDir/input/errors/letExpressionErrorTyped.pkl)
|
|
||||||
|
|
||||||
x | let (x: Float = "abc")
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
at letExpressionErrorTyped#res1 (file:///$snippetsDir/input/errors/letExpressionErrorTyped.pkl)
|
at letExpressionErrorTyped#res1 (file:///$snippetsDir/input/errors/letExpressionErrorTyped.pkl)
|
||||||
|
|
||||||
xxx | renderer.renderDocument(value)
|
xxx | renderer.renderDocument(value)
|
||||||
|
|||||||
Reference in New Issue
Block a user