mirror of
https://github.com/apple/pkl.git
synced 2026-05-25 16:19:20 +02:00
Move to truffle object model
This commit is contained in:
+19
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,10 +22,20 @@ 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.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmObjectLike;
|
||||
import org.pkl.core.runtime.VmObject;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
|
||||
/**
|
||||
* Reads a local non-constant property that is known to exist in the lexical scope of this node.
|
||||
*
|
||||
* <p>Local property values are cached using the ObjectMember as the key (identity-based) rather
|
||||
* than the property name. This is necessary because the same property name can exist at different
|
||||
* declaration sites in an amends chain, and we need to distinguish between them for correct
|
||||
* late-binding semantics.
|
||||
*
|
||||
* <p>The cache is stored in a separate IdentityHashMap in VmObject (not in the DynamicObject
|
||||
* storage) to avoid shape transitions that would destroy cache locality.
|
||||
*/
|
||||
public final class ReadLocalPropertyNode extends ExpressionNode {
|
||||
private final ObjectMember property;
|
||||
private final int levelsUp;
|
||||
@@ -63,15 +73,17 @@ public final class ReadLocalPropertyNode extends ExpressionNode {
|
||||
owner = owner.getEnclosingOwner();
|
||||
}
|
||||
|
||||
assert receiver instanceof VmObjectLike
|
||||
assert receiver instanceof VmObject
|
||||
: "Assumption: This node isn't used in Truffle ASTs of `external` pkl.base classes whose values aren't VmObject's.";
|
||||
|
||||
var objReceiver = (VmObjectLike) receiver;
|
||||
var result = objReceiver.getCachedValue(property);
|
||||
// Use the local property cache instead of DynamicObject storage
|
||||
// to avoid shape transitions from ObjectMember keys
|
||||
var objReceiver = (VmObject) receiver;
|
||||
var result = objReceiver.getLocalCachedValue(property);
|
||||
|
||||
if (result == null) {
|
||||
result = callNode.call(objReceiver, owner, property.getName());
|
||||
objReceiver.setCachedValue(property, result);
|
||||
objReceiver.setLocalCachedValue(property, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,9 +19,12 @@ import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.*;
|
||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.object.DynamicObjectLibrary;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.MemberLookupMode;
|
||||
@@ -61,6 +64,27 @@ public abstract class ReadPropertyNode extends ExpressionNode {
|
||||
this(sourceSection, propertyName, MemberLookupMode.EXPLICIT_RECEIVER, false);
|
||||
}
|
||||
|
||||
// Optimized specialization for VmTyped using DynamicObjectLibrary
|
||||
@Specialization
|
||||
protected Object evalTyped(
|
||||
VmTyped receiver,
|
||||
@CachedLibrary(limit = "3") DynamicObjectLibrary objectLibrary,
|
||||
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
|
||||
|
||||
checkConst(receiver);
|
||||
|
||||
// fast path: check cache using optimized library access
|
||||
var result = receiver.getCachedValue(propertyName, objectLibrary);
|
||||
if (result != null) return result;
|
||||
|
||||
// slow path: look up member in prototype chain and compute value
|
||||
result = VmUtils.readMemberOrNull(receiver, propertyName, true, callNode);
|
||||
if (result != null) return result;
|
||||
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw cannotFindProperty(receiver);
|
||||
}
|
||||
|
||||
// This method effectively covers `VmObject receiver` but is implemented in a more
|
||||
// efficient way. See:
|
||||
// https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces
|
||||
@@ -68,7 +92,7 @@ public abstract class ReadPropertyNode extends ExpressionNode {
|
||||
protected Object evalObject(
|
||||
Object receiver,
|
||||
@Cached("getVmObjectSubclassOrNull(receiver)") Class<? extends VmObjectLike> cachedClass,
|
||||
@Cached("create()") IndirectCallNode callNode) {
|
||||
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
|
||||
|
||||
var object = cachedClass.cast(receiver);
|
||||
checkConst(object);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright © 2026 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.runtime;
|
||||
|
||||
import com.oracle.truffle.api.object.Shape;
|
||||
|
||||
/** Factory for Truffle {@link Shape} instances used by Pkl objects. */
|
||||
public final class PklShape {
|
||||
|
||||
/** The root shape for all Pkl object instances. */
|
||||
private static final Shape ROOT_SHAPE = Shape.newBuilder().build();
|
||||
|
||||
private PklShape() {}
|
||||
|
||||
/**
|
||||
* Returns the root shape for Pkl objects.
|
||||
*
|
||||
* <p>This is the base shape from which all instance shapes derive. Properties are added
|
||||
* dynamically as values are cached via {@link
|
||||
* com.oracle.truffle.api.object.DynamicObjectLibrary#put}.
|
||||
*/
|
||||
public static Shape getRootShape() {
|
||||
return ROOT_SHAPE;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public final class TestRunner {
|
||||
if (factValue == Boolean.FALSE) {
|
||||
if (PowerAssertions.isEnabled()) {
|
||||
try (var valueTracker = valueTrackerFactory.create()) {
|
||||
listing.cachedValues.clear();
|
||||
listing.cleanAllCachedValues();
|
||||
VmUtils.readMember(listing, idx);
|
||||
var failure =
|
||||
factFailure(
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.pkl.core.util.ByteArrayUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmBytes extends VmValue implements Iterable<Long> {
|
||||
public final class VmBytes implements VmValue, Iterable<Long> {
|
||||
|
||||
private @Nullable VmList vmList;
|
||||
private @Nullable String base64;
|
||||
|
||||
@@ -19,6 +19,8 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Idempotent;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.object.Shape;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
@@ -43,7 +45,7 @@ import org.pkl.core.util.Nullable;
|
||||
// The currently implemented (and likely insufficient) solution is to
|
||||
// * deeply force standard library modules at initialization time.
|
||||
// * ensure that any further mutation (e.g., lazy initialization in VmClass) is thread-safe.
|
||||
public final class VmClass extends VmValue {
|
||||
public final class VmClass implements VmValue {
|
||||
private final SourceSection sourceSection;
|
||||
private final SourceSection headerSection;
|
||||
private final SourceSection @Nullable [] docComment;
|
||||
@@ -123,6 +125,16 @@ public final class VmClass extends VmValue {
|
||||
|
||||
private final Object mapToTypedMembersLock = new Object();
|
||||
|
||||
// Shape for instances of this class - used for Truffle's Dynamic Object Model
|
||||
@LateInit
|
||||
@GuardedBy("instanceShapeLock")
|
||||
private Shape __instanceShape;
|
||||
|
||||
private final Object instanceShapeLock = new Object();
|
||||
|
||||
/** Cached IndirectCallNode for force() operations. */
|
||||
private final IndirectCallNode cachedCallNode = IndirectCallNode.create();
|
||||
|
||||
public VmClass(
|
||||
SourceSection sourceSection,
|
||||
SourceSection headerSection,
|
||||
@@ -697,6 +709,41 @@ public final class VmClass extends VmValue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Truffle Shape for instances of this class.
|
||||
*
|
||||
* <p>The shape is lazily initialized from the root shape. Instance shapes are used by the Dynamic
|
||||
* Object Model to provide optimized property storage and inline caching for cached property
|
||||
* values.
|
||||
*/
|
||||
public Shape getInstanceShape() {
|
||||
synchronized (instanceShapeLock) {
|
||||
if (__instanceShape == null) {
|
||||
__instanceShape = buildInstanceShape();
|
||||
}
|
||||
return __instanceShape;
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private Shape buildInstanceShape() {
|
||||
// start with the superclass shape if available, otherwise use root shape
|
||||
if (superclass != null) {
|
||||
return superclass.getInstanceShape();
|
||||
}
|
||||
return PklShape.getRootShape();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cached IndirectCallNode for force() operations.
|
||||
*
|
||||
* <p>This node is shared by all instances of this class and avoids the overhead of {@code
|
||||
* IndirectCallNode.getUncached()} which performs a lookup on every call.
|
||||
*/
|
||||
public IndirectCallNode getCachedCallNode() {
|
||||
return cachedCallNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given property defines a member of this class. Requires a fully initialized
|
||||
* inheritance hierarchy.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.util.Iterator;
|
||||
import org.organicdesign.fp.xform.Xform;
|
||||
|
||||
public abstract class VmCollection extends VmValue implements Iterable<Object> {
|
||||
public abstract class VmCollection implements VmValue, Iterable<Object> {
|
||||
public interface Builder<T extends VmCollection> {
|
||||
void add(Object element);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,7 @@ import org.pkl.core.util.MathUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmDataSize extends VmValue implements Comparable<VmDataSize> {
|
||||
public final class VmDataSize implements VmValue, Comparable<VmDataSize> {
|
||||
private static final Map<Identifier, DataSizeUnit> UNITS =
|
||||
Map.ofEntries(
|
||||
entry(Identifier.B, DataSizeUnit.BYTES),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,7 @@ import org.pkl.core.util.DurationUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmDuration extends VmValue implements Comparable<VmDuration> {
|
||||
public final class VmDuration implements VmValue, Comparable<VmDuration> {
|
||||
private static final Map<Identifier, DurationUnit> UNITS =
|
||||
Map.of(
|
||||
Identifier.NS, DurationUnit.NANOS,
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.pkl.core.util.EconomicMaps;
|
||||
public final class VmDynamic extends VmObject {
|
||||
private int cachedRegularMemberCount = -1;
|
||||
|
||||
private final int length;
|
||||
|
||||
private static final class EmptyHolder {
|
||||
private static final VmDynamic EMPTY =
|
||||
new VmDynamic(
|
||||
@@ -37,8 +39,6 @@ public final class VmDynamic extends VmObject {
|
||||
0);
|
||||
}
|
||||
|
||||
private final int length;
|
||||
|
||||
public static VmDynamic empty() {
|
||||
return EmptyHolder.EMPTY;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public final class VmDynamic extends VmObject {
|
||||
VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
int length) {
|
||||
super(enclosingFrame, Objects.requireNonNull(parent), members);
|
||||
super(PklShape.getRootShape(), enclosingFrame, Objects.requireNonNull(parent), members);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,7 @@ public final class VmDynamic extends VmObject {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public PObject export() {
|
||||
var properties =
|
||||
CollectionUtils.<String, Object>newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
var properties = CollectionUtils.<String, Object>newLinkedHashMap(getCachedValueCount());
|
||||
|
||||
iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
@@ -109,7 +108,7 @@ public final class VmDynamic extends VmObject {
|
||||
other.force(false);
|
||||
if (getRegularMemberCount() != other.getRegularMemberCount()) return false;
|
||||
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
while (cursor.advance()) {
|
||||
Object key = cursor.getKey();
|
||||
if (isHiddenOrLocalProperty(key)) continue;
|
||||
@@ -130,7 +129,7 @@ public final class VmDynamic extends VmObject {
|
||||
|
||||
force(false);
|
||||
var result = 0;
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
|
||||
while (cursor.advance()) {
|
||||
var key = cursor.getKey();
|
||||
@@ -150,8 +149,9 @@ public final class VmDynamic extends VmObject {
|
||||
if (cachedRegularMemberCount != -1) return cachedRegularMemberCount;
|
||||
|
||||
var result = 0;
|
||||
for (var key : cachedValues.getKeys()) {
|
||||
if (!isHiddenOrLocalProperty(key)) result += 1;
|
||||
var cursor = getCachedValueEntries();
|
||||
while (cursor.advance()) {
|
||||
if (!isHiddenOrLocalProperty(cursor.getKey())) result += 1;
|
||||
}
|
||||
cachedRegularMemberCount = result;
|
||||
return result;
|
||||
|
||||
@@ -23,9 +23,14 @@ import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.pkl.core.ast.PklRootNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.EmptyMapCursor;
|
||||
import org.pkl.core.util.MapCursor;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public final class VmFunction extends VmObjectLike {
|
||||
public final class VmFunction implements VmObjectLike {
|
||||
// Own fields (VmFunction does not extend DynamicObject, so it has its own storage)
|
||||
private final MaterializedFrame enclosingFrame;
|
||||
private @Nullable Object extraStorage;
|
||||
|
||||
private final Object thisValue;
|
||||
private final int paramCount;
|
||||
@@ -37,13 +42,28 @@ public final class VmFunction extends VmObjectLike {
|
||||
int paramCount,
|
||||
PklRootNode rootNode,
|
||||
@Nullable Object extraStorage) {
|
||||
super(enclosingFrame);
|
||||
this.enclosingFrame = enclosingFrame;
|
||||
this.thisValue = thisValue;
|
||||
this.paramCount = paramCount;
|
||||
this.rootNode = rootNode;
|
||||
this.extraStorage = extraStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterializedFrame getEnclosingFrame() {
|
||||
return enclosingFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getExtraStorage() {
|
||||
return extraStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtraStorage(@Nullable Object extraStorage) {
|
||||
this.extraStorage = extraStorage;
|
||||
}
|
||||
|
||||
public RootCallTarget getCallTarget() {
|
||||
return rootNode.getCallTarget();
|
||||
}
|
||||
@@ -123,17 +143,27 @@ public final class VmFunction extends VmObjectLike {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateMemberValues(MemberValueConsumer consumer) {
|
||||
public MapCursor<Object, Object> getCachedValueEntries() {
|
||||
return EmptyMapCursor.instance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCachedValueCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateMemberValues(VmObjectLike.MemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
public boolean forceAndIterateMemberValues(VmObjectLike.ForcedMemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
public boolean iterateAlreadyForcedMemberValues(VmObjectLike.ForcedMemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,7 @@ import org.pkl.core.util.Nullable;
|
||||
// Some code copied from kotlin.ranges.Progressions, kotlin.ranges.ProgressionIterators,
|
||||
// kotlin.internal.ProgressionUtil (Apache 2).
|
||||
@ValueType
|
||||
public final class VmIntSeq extends VmValue implements Iterable<Long> {
|
||||
public final class VmIntSeq implements VmValue, Iterable<Long> {
|
||||
public final long start;
|
||||
public final long end;
|
||||
public final long step;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,7 +87,7 @@ public final class VmListing extends VmListingOrMapping {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public List<Object> export() {
|
||||
var properties = new ArrayList<>(EconomicMaps.size(cachedValues));
|
||||
var properties = new ArrayList<>(getCachedValueCount());
|
||||
|
||||
iterateMemberValues(
|
||||
(key, prop, value) -> {
|
||||
@@ -121,7 +121,7 @@ public final class VmListing extends VmListingOrMapping {
|
||||
force(false);
|
||||
other.force(false);
|
||||
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
while (cursor.advance()) {
|
||||
var key = cursor.getKey();
|
||||
if (key instanceof Identifier) continue;
|
||||
@@ -142,7 +142,7 @@ public final class VmListing extends VmListingOrMapping {
|
||||
|
||||
force(false);
|
||||
var result = 0;
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
|
||||
while (cursor.advance()) {
|
||||
var key = cursor.getKey();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.ast.type.TypeNode;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public abstract class VmListingOrMapping extends VmObject {
|
||||
@@ -32,24 +31,24 @@ public abstract class VmListingOrMapping extends VmObject {
|
||||
private final @Nullable Object typeCheckReceiver;
|
||||
private final @Nullable VmObjectLike typeCheckOwner;
|
||||
|
||||
public VmListingOrMapping(
|
||||
protected VmListingOrMapping(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members) {
|
||||
super(enclosingFrame, parent, members);
|
||||
super(PklShape.getRootShape(), enclosingFrame, parent, members);
|
||||
typeCastNode = null;
|
||||
typeCheckReceiver = null;
|
||||
typeCheckOwner = null;
|
||||
}
|
||||
|
||||
public VmListingOrMapping(
|
||||
protected VmListingOrMapping(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
ListingOrMappingTypeCastNode typeCastNode,
|
||||
Object typeCheckReceiver,
|
||||
VmObjectLike typeCheckOwner) {
|
||||
super(enclosingFrame, parent, members);
|
||||
super(PklShape.getRootShape(), enclosingFrame, parent, members);
|
||||
this.typeCastNode = typeCastNode;
|
||||
this.typeCheckReceiver = typeCheckReceiver;
|
||||
this.typeCheckOwner = typeCheckOwner;
|
||||
@@ -88,7 +87,7 @@ public abstract class VmListingOrMapping extends VmObject {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final @Nullable Object getCachedValue(Object key) {
|
||||
var result = EconomicMaps.get(cachedValues, key);
|
||||
var result = super.getCachedValue(key);
|
||||
// if this object has members, `this[key]` may differ from `parent[key]`, so stop the search
|
||||
if (result != null || !members.isEmpty()) return result;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.pkl.core.util.paguro.RrbTree;
|
||||
import org.pkl.core.util.paguro.RrbTree.ImRrbt;
|
||||
import org.pkl.core.util.paguro.RrbTree.MutRrbt;
|
||||
|
||||
public final class VmMap extends VmValue implements Iterable<Map.Entry<Object, Object>> {
|
||||
public final class VmMap implements VmValue, Iterable<Map.Entry<Object, Object>> {
|
||||
public static final VmMap EMPTY = new VmMap(PersistentHashMap.empty(), RrbTree.empty());
|
||||
|
||||
private final ImMap<Object, Object> map;
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public Map<Object, Object> export() {
|
||||
var properties = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
var properties = CollectionUtils.newLinkedHashMap(getCachedValueCount());
|
||||
|
||||
iterateMemberValues(
|
||||
(key, prop, value) -> {
|
||||
@@ -128,7 +128,7 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
other.force(false);
|
||||
if (getLength() != other.getLength()) return false;
|
||||
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
while (cursor.advance()) {
|
||||
Object key = cursor.getKey();
|
||||
if (key instanceof Identifier) continue;
|
||||
@@ -149,7 +149,7 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
|
||||
force(false);
|
||||
var result = 0;
|
||||
var cursor = cachedValues.getEntries();
|
||||
var cursor = getCachedValueEntries();
|
||||
|
||||
while (cursor.advance()) {
|
||||
var key = cursor.getKey();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ import org.pkl.core.PNull;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmNull extends VmValue {
|
||||
public final class VmNull implements VmValue {
|
||||
private static final VmNull WITHOUT_DEFAULT = new VmNull(null);
|
||||
|
||||
// worthwhile to create this lazily?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,42 +18,69 @@ package org.pkl.core.runtime;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.object.DynamicObject;
|
||||
import com.oracle.truffle.api.object.DynamicObjectLibrary;
|
||||
import com.oracle.truffle.api.object.Shape;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import org.graalvm.collections.EconomicMap;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.util.CollectionUtils;
|
||||
import org.pkl.core.util.DynamicObjectMapCursor;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.MapCursor;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Corresponds to `pkl.base#Object`. */
|
||||
public abstract class VmObject extends VmObjectLike {
|
||||
/**
|
||||
* Corresponds to `pkl.base#Object`.
|
||||
*
|
||||
* <p>Extends {@link DynamicObject} to leverage Truffle's object storage and inline caching
|
||||
* capabilities. Cached property values are stored directly in this object using the Dynamic Object
|
||||
* Model.
|
||||
*/
|
||||
public abstract class VmObject extends DynamicObject implements VmObjectLike {
|
||||
// moved from VmObjectLike
|
||||
protected final MaterializedFrame enclosingFrame;
|
||||
protected @Nullable Object extraStorage;
|
||||
|
||||
@CompilationFinal protected @Nullable VmObject parent;
|
||||
protected final UnmodifiableEconomicMap<Object, ObjectMember> members;
|
||||
protected final EconomicMap<Object, Object> cachedValues;
|
||||
|
||||
protected int cachedHash;
|
||||
private boolean forced;
|
||||
|
||||
public VmObject(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
EconomicMap<Object, Object> cachedValues) {
|
||||
super(enclosingFrame);
|
||||
this.parent = parent;
|
||||
this.members = members;
|
||||
this.cachedValues = cachedValues;
|
||||
/**
|
||||
* Separate cache for local property values.
|
||||
*
|
||||
* <p>This is kept separate from the DynamicObject storage to avoid shape transitions.
|
||||
*/
|
||||
private @Nullable IdentityHashMap<ObjectMember, Object> localPropertyCache;
|
||||
|
||||
assert parent != this;
|
||||
}
|
||||
|
||||
public VmObject(
|
||||
protected VmObject(
|
||||
Shape shape,
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members) {
|
||||
this(enclosingFrame, parent, members, EconomicMaps.create());
|
||||
super(shape);
|
||||
this.enclosingFrame = enclosingFrame;
|
||||
this.parent = parent;
|
||||
this.members = members;
|
||||
assert parent != this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MaterializedFrame getEnclosingFrame() {
|
||||
return enclosingFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable Object getExtraStorage() {
|
||||
return extraStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setExtraStorage(@Nullable Object extraStorage) {
|
||||
this.extraStorage = extraStorage;
|
||||
}
|
||||
|
||||
public final void lateInitParent(VmObject parent) {
|
||||
@@ -67,11 +94,13 @@ public abstract class VmObject extends VmObjectLike {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean hasMember(Object key) {
|
||||
return EconomicMaps.containsKey(members, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final @Nullable ObjectMember getMember(Object key) {
|
||||
return EconomicMaps.get(members, key);
|
||||
}
|
||||
@@ -82,23 +111,81 @@ public abstract class VmObject extends VmObjectLike {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public @Nullable Object getCachedValue(Object key) {
|
||||
return EconomicMaps.get(cachedValues, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setCachedValue(Object key, Object value) {
|
||||
EconomicMaps.put(cachedValues, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean hasCachedValue(Object key) {
|
||||
return EconomicMaps.containsKey(cachedValues, key);
|
||||
return DynamicObjectLibrary.getUncached().getOrDefault(this, key, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateMemberValues(MemberValueConsumer consumer) {
|
||||
public void setCachedValue(Object key, Object value) {
|
||||
DynamicObjectLibrary.getUncached().put(this, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean hasCachedValue(Object key) {
|
||||
return DynamicObjectLibrary.getUncached().containsKey(this, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCursor<Object, Object> getCachedValueEntries() {
|
||||
return new DynamicObjectMapCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public int getCachedValueCount() {
|
||||
return DynamicObjectLibrary.getUncached().getKeyArray(this).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean all cached values. Local or otherwise. Resets cached values to null without removing the
|
||||
* keys, preserving the object's shape for pre-allocated slots.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public void cleanAllCachedValues() {
|
||||
if (localPropertyCache != null) {
|
||||
localPropertyCache.clear();
|
||||
}
|
||||
|
||||
var lib = DynamicObjectLibrary.getUncached();
|
||||
Object[] keys = lib.getKeyArray(this);
|
||||
for (Object key : keys) {
|
||||
lib.put(this, key, null);
|
||||
}
|
||||
|
||||
forced = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached local property value.
|
||||
*
|
||||
* @param property the ObjectMember representing the local property declaration
|
||||
* @return the cached value, or null if not cached
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public @Nullable Object getLocalCachedValue(ObjectMember property) {
|
||||
return localPropertyCache == null ? null : localPropertyCache.get(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cached local property value.
|
||||
*
|
||||
* @param property the ObjectMember representing the local property declaration
|
||||
* @param value the value to cache
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public void setLocalCachedValue(ObjectMember property, Object value) {
|
||||
if (localPropertyCache == null) {
|
||||
localPropertyCache = new IdentityHashMap<>(4);
|
||||
}
|
||||
localPropertyCache.put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateMemberValues(VmObjectLike.MemberValueConsumer consumer) {
|
||||
var visited = new HashSet<>();
|
||||
return iterateMembers(
|
||||
(key, member) -> {
|
||||
@@ -112,14 +199,16 @@ public abstract class VmObject extends VmObjectLike {
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
public final boolean forceAndIterateMemberValues(
|
||||
VmObjectLike.ForcedMemberValueConsumer consumer) {
|
||||
force(false, false);
|
||||
return iterateAlreadyForcedMemberValues(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
public final boolean iterateAlreadyForcedMemberValues(
|
||||
VmObjectLike.ForcedMemberValueConsumer consumer) {
|
||||
var visited = new HashSet<>();
|
||||
return iterateMembers(
|
||||
(key, member) -> {
|
||||
@@ -158,6 +247,9 @@ public abstract class VmObject extends VmObjectLike {
|
||||
|
||||
if (recurse) forced = true;
|
||||
|
||||
// use cached call node from this object's class to avoid getUncached() overhead
|
||||
var callNode = getVmClass().getCachedCallNode();
|
||||
|
||||
try {
|
||||
for (VmObjectLike owner = this; owner != null; owner = owner.getParent()) {
|
||||
var cursor = EconomicMaps.getEntries(owner.getMembers());
|
||||
@@ -174,7 +266,7 @@ public abstract class VmObject extends VmObjectLike {
|
||||
var memberValue = getCachedValue(memberKey);
|
||||
if (memberValue == null) {
|
||||
try {
|
||||
memberValue = VmUtils.doReadMember(this, owner, memberKey, member);
|
||||
memberValue = VmUtils.doReadMember(this, owner, memberKey, member, true, callNode);
|
||||
} catch (VmUndefinedValueException e) {
|
||||
if (!allowUndefinedValues) throw e;
|
||||
continue;
|
||||
@@ -208,7 +300,7 @@ public abstract class VmObject extends VmObjectLike {
|
||||
*/
|
||||
@TruffleBoundary
|
||||
protected final Map<String, Object> exportMembers() {
|
||||
var result = CollectionUtils.<String, Object>newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
var result = CollectionUtils.<String, Object>newLinkedHashMap(getCachedValueCount());
|
||||
|
||||
iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,53 +15,39 @@
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.function.BiFunction;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.util.MapCursor;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/**
|
||||
* Corresponds to `pkl.base#Object|pkl.base#Function`. The lexical scope is a chain of
|
||||
* `VmObjectLike` instances.
|
||||
*/
|
||||
public abstract class VmObjectLike extends VmValue {
|
||||
public interface VmObjectLike extends VmValue {
|
||||
/** The frame that was active when this object was instantiated. * */
|
||||
protected final MaterializedFrame enclosingFrame;
|
||||
MaterializedFrame getEnclosingFrame();
|
||||
|
||||
protected @Nullable Object extraStorage;
|
||||
@Nullable
|
||||
Object getExtraStorage();
|
||||
|
||||
protected VmObjectLike(MaterializedFrame enclosingFrame) {
|
||||
this.enclosingFrame = enclosingFrame;
|
||||
void setExtraStorage(@Nullable Object extraStorage);
|
||||
|
||||
default boolean hasExtraStorage() {
|
||||
return getExtraStorage() != null;
|
||||
}
|
||||
|
||||
public final MaterializedFrame getEnclosingFrame() {
|
||||
return enclosingFrame;
|
||||
default @Nullable Object getEnclosingReceiver() {
|
||||
return VmUtils.getReceiverOrNull(getEnclosingFrame());
|
||||
}
|
||||
|
||||
public final @Nullable Object getEnclosingReceiver() {
|
||||
return VmUtils.getReceiverOrNull(enclosingFrame);
|
||||
default @Nullable VmObjectLike getEnclosingOwner() {
|
||||
return VmUtils.getOwnerOrNull(getEnclosingFrame());
|
||||
}
|
||||
|
||||
public final @Nullable VmObjectLike getEnclosingOwner() {
|
||||
return VmUtils.getOwnerOrNull(enclosingFrame);
|
||||
}
|
||||
|
||||
public final boolean hasExtraStorage() {
|
||||
return extraStorage != null;
|
||||
}
|
||||
|
||||
public Object getExtraStorage() {
|
||||
assert extraStorage != null;
|
||||
return extraStorage;
|
||||
}
|
||||
|
||||
public final void setExtraStorage(@Nullable Object extraStorage) {
|
||||
this.extraStorage = extraStorage;
|
||||
}
|
||||
|
||||
public boolean isModuleObject() {
|
||||
default boolean isModuleObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,41 +55,45 @@ public abstract class VmObjectLike extends VmValue {
|
||||
* 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();
|
||||
@Nullable
|
||||
VmObjectLike getParent();
|
||||
|
||||
/** Always prefer this method over `getMembers().containsKey(key)`. */
|
||||
@TruffleBoundary
|
||||
public abstract boolean hasMember(Object key);
|
||||
boolean hasMember(Object key);
|
||||
|
||||
/** Always prefer this method over `getMembers().get(key)`. */
|
||||
@TruffleBoundary
|
||||
public abstract @Nullable ObjectMember getMember(Object key);
|
||||
@Nullable
|
||||
ObjectMember getMember(Object key);
|
||||
|
||||
/** Returns the declared members of this object. */
|
||||
public abstract UnmodifiableEconomicMap<Object, ObjectMember> getMembers();
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> getMembers();
|
||||
|
||||
/**
|
||||
* Reads from the properties cache for this object. The cache contains the values of all members
|
||||
* defined in this object or an ancestor thereof which have been requested with this object as the
|
||||
* receiver.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public abstract @Nullable Object getCachedValue(Object key);
|
||||
@Nullable
|
||||
Object getCachedValue(Object key);
|
||||
|
||||
/**
|
||||
* Writes to the properties cache for this object. The cache contains the values of all members
|
||||
* defined in this object or an ancestor thereof which have been requested with this object as the
|
||||
* receiver.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public abstract void setCachedValue(Object key, Object value);
|
||||
void setCachedValue(Object key, Object value);
|
||||
|
||||
/**
|
||||
* Prefer this method over {@link #getCachedValue} if the value is not required. (There is no
|
||||
* point in calling this method to determine whether to call {@link #getCachedValue}.)
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public abstract boolean hasCachedValue(Object key);
|
||||
boolean hasCachedValue(Object key);
|
||||
|
||||
/** Returns a cursor for iterating over all cached values in this object. */
|
||||
MapCursor<Object, Object> getCachedValueEntries();
|
||||
|
||||
/** Returns the number of cached values in this object. */
|
||||
int getCachedValueCount();
|
||||
|
||||
/**
|
||||
* Iterates over member definitions and their values in order of their definition, from the top of
|
||||
@@ -118,15 +108,15 @@ public abstract class VmObjectLike extends VmValue {
|
||||
* remaining members are not visited, and `false` is returned. Otherwise, all members are visited,
|
||||
* and `true` is returned.
|
||||
*/
|
||||
public abstract boolean iterateMemberValues(MemberValueConsumer consumer);
|
||||
boolean iterateMemberValues(MemberValueConsumer consumer);
|
||||
|
||||
/**
|
||||
* Same as {@link #iterateMemberValues} except that it first performs a shallow {@link #force}. As
|
||||
* a consequence, values passed to {@code consumer} are guaranteed to be non-null.
|
||||
*/
|
||||
public abstract boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer);
|
||||
boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer);
|
||||
|
||||
public abstract boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer);
|
||||
boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer);
|
||||
|
||||
/**
|
||||
* Iterates over member definitions in order of their definition, from the top of the prototype
|
||||
@@ -135,19 +125,20 @@ public abstract class VmObjectLike extends VmValue {
|
||||
* members are not visited, and `false` is returned. Otherwise, all members are visited, and
|
||||
* `true` is returned.
|
||||
*/
|
||||
public abstract boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer);
|
||||
boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer);
|
||||
|
||||
/** Forces shallow or recursive (deep) evaluation of this object. */
|
||||
public abstract void force(boolean allowUndefinedValues, boolean recurse);
|
||||
void force(boolean allowUndefinedValues, boolean recurse);
|
||||
|
||||
/**
|
||||
* Exports this object to an external representation. Does not export local, hidden, or external
|
||||
* properties
|
||||
*/
|
||||
public abstract Object export();
|
||||
@Override
|
||||
Object export();
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MemberValueConsumer {
|
||||
interface MemberValueConsumer {
|
||||
/**
|
||||
* Returns true if {@link #iterateMemberValues} should continue calling this method for the
|
||||
* remaining members, and false otherwise.
|
||||
@@ -156,7 +147,7 @@ public abstract class VmObjectLike extends VmValue {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ForcedMemberValueConsumer {
|
||||
interface ForcedMemberValueConsumer {
|
||||
/**
|
||||
* Returns true if {@link #forceAndIterateMemberValues} should continue calling this method for
|
||||
* the remaining members, and false otherwise.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,7 @@ import org.pkl.core.Pair;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmPair extends VmValue implements Iterable<Object> {
|
||||
public final class VmPair implements VmValue, Iterable<Object> {
|
||||
private final Object first;
|
||||
private final Object second;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,7 @@ import org.pkl.core.ValueFormatter;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
@ValueType
|
||||
public final class VmRegex extends VmValue {
|
||||
public final class VmRegex implements VmValue {
|
||||
private final Pattern pattern;
|
||||
|
||||
public VmRegex(Pattern pattern) {
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,7 +36,7 @@ import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public final class VmTypeAlias extends VmValue {
|
||||
public final class VmTypeAlias implements VmValue {
|
||||
private final SourceSection sourceSection;
|
||||
private final SourceSection headerSection;
|
||||
private final SourceSection @Nullable [] docComment;
|
||||
|
||||
@@ -19,6 +19,9 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.instrumentation.InstrumentableNode.WrapperNode;
|
||||
import com.oracle.truffle.api.object.DynamicObject;
|
||||
import com.oracle.truffle.api.object.DynamicObjectLibrary;
|
||||
import com.oracle.truffle.api.object.Shape;
|
||||
import org.graalvm.collections.EconomicMap;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.pkl.core.Composite;
|
||||
@@ -34,14 +37,99 @@ import org.pkl.core.util.Nullable;
|
||||
public final class VmTyped extends VmObject {
|
||||
@CompilationFinal @LateInit private VmClass clazz;
|
||||
|
||||
/**
|
||||
* Creates a new VmTyped with the class's instance shape.
|
||||
*
|
||||
* @param enclosingFrame the frame that was active when this object was instantiated
|
||||
* @param parent the parent in the prototype chain, or null
|
||||
* @param clazz the class of this object, or null if it will be late-initialized
|
||||
* @param members the declared members of this object
|
||||
*/
|
||||
public VmTyped(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmTyped parent,
|
||||
// null -> will be initialized using lateInitVmClass() later
|
||||
@Nullable VmClass clazz,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members) {
|
||||
super(enclosingFrame, parent, members);
|
||||
this(enclosingFrame, parent, clazz, members, getShapeForClass(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new VmTyped with a custom shape.
|
||||
*
|
||||
* <p>This constructor is used when a specific shape is needed, such as when amending an object
|
||||
* where the shape may differ from the class's base instance shape.
|
||||
*
|
||||
* @param enclosingFrame the frame that was active when this object was instantiated
|
||||
* @param parent the parent in the prototype chain, or null
|
||||
* @param clazz the class of this object, or null if it will be late-initialized
|
||||
* @param members the declared members of this object
|
||||
* @param shape the Truffle shape for this object's cached value storage
|
||||
*/
|
||||
public VmTyped(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmTyped parent,
|
||||
@Nullable VmClass clazz,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
Shape shape) {
|
||||
super(shape, enclosingFrame, parent, members);
|
||||
this.clazz = clazz;
|
||||
// pre-allocate cache slots for all members to stabilize the shape
|
||||
preallocateCacheSlots(members);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-allocates cache slots for all members by putting null values. This creates shape
|
||||
* transitions upfront so all instances share the same final shape.
|
||||
*/
|
||||
private void preallocateCacheSlots(UnmodifiableEconomicMap<Object, ObjectMember> members) {
|
||||
var library = DynamicObjectLibrary.getUncached();
|
||||
var cursor = members.getEntries();
|
||||
while (cursor.advance()) {
|
||||
library.put(this, cursor.getKey(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static Shape getShapeForClass(@Nullable VmClass clazz) {
|
||||
return clazz != null ? clazz.getInstanceShape() : PklShape.getRootShape();
|
||||
}
|
||||
|
||||
/** Returns this object for cached value storage. */
|
||||
public DynamicObject getCachedValuesStorage() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached value using the provided library for PE-optimized access.
|
||||
*
|
||||
* @param key the property key
|
||||
* @param library the DynamicObjectLibrary to use (should be cached via @CachedLibrary)
|
||||
* @return the cached value, or null if not present
|
||||
*/
|
||||
public @Nullable Object getCachedValue(Object key, DynamicObjectLibrary library) {
|
||||
return library.getOrDefault(this, key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cached value using the provided library for PE-optimized access.
|
||||
*
|
||||
* @param key the property key
|
||||
* @param value the value to cache
|
||||
* @param library the DynamicObjectLibrary to use (should be cached via @CachedLibrary)
|
||||
*/
|
||||
public void setCachedValue(Object key, Object value, DynamicObjectLibrary library) {
|
||||
library.put(this, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a cached value exists using the provided library for PE-optimized access.
|
||||
*
|
||||
* @param key the property key
|
||||
* @param library the DynamicObjectLibrary to use (should be cached via @CachedLibrary)
|
||||
* @return true if a value is cached for this key
|
||||
*/
|
||||
public boolean hasCachedValue(Object key, DynamicObjectLibrary library) {
|
||||
return library.containsKey(this, key);
|
||||
}
|
||||
|
||||
public void lateInitVmClass(VmClass clazz) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,30 +17,30 @@ package org.pkl.core.runtime;
|
||||
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public abstract class VmValue {
|
||||
public abstract VmClass getVmClass();
|
||||
public interface VmValue {
|
||||
VmClass getVmClass();
|
||||
|
||||
public VmTyped getPrototype() {
|
||||
default VmTyped getPrototype() {
|
||||
return getVmClass().getPrototype();
|
||||
}
|
||||
|
||||
public boolean isPrototype() {
|
||||
default boolean isPrototype() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDynamic() {
|
||||
default boolean isDynamic() {
|
||||
return this instanceof VmDynamic;
|
||||
}
|
||||
|
||||
public boolean isListing() {
|
||||
default boolean isListing() {
|
||||
return this instanceof VmListing;
|
||||
}
|
||||
|
||||
public boolean isMapping() {
|
||||
default boolean isMapping() {
|
||||
return this instanceof VmMapping;
|
||||
}
|
||||
|
||||
public boolean isTyped() {
|
||||
default boolean isTyped() {
|
||||
return this instanceof VmTyped;
|
||||
}
|
||||
|
||||
@@ -48,21 +48,21 @@ public abstract class VmValue {
|
||||
* Tells if this value is a {@link VmCollection}, {@link VmListing}, or {@link VmDynamic} with
|
||||
* {@link VmDynamic#hasElements() elements}.
|
||||
*/
|
||||
public boolean isSequence() {
|
||||
default boolean isSequence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Forces recursive (deep) evaluation of this value. */
|
||||
public abstract void force(boolean allowUndefinedValues);
|
||||
void force(boolean allowUndefinedValues);
|
||||
|
||||
public abstract Object export();
|
||||
Object export();
|
||||
|
||||
public abstract void accept(VmValueVisitor visitor);
|
||||
void accept(VmValueVisitor visitor);
|
||||
|
||||
public abstract <T> T accept(VmValueConverter<T> converter, Iterable<Object> path);
|
||||
<T> T accept(VmValueConverter<T> converter, Iterable<Object> path);
|
||||
|
||||
/** Forces recursive (deep) evaluation of the given value. */
|
||||
public static void force(Object value, boolean allowUndefinedValues) {
|
||||
static void force(Object value, boolean allowUndefinedValues) {
|
||||
if (value instanceof VmValue vmValue) {
|
||||
vmValue.force(allowUndefinedValues);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public abstract class VmValue {
|
||||
* Used to export values other than object member values. Such values aren't `@Nullable` (but can
|
||||
* be `VmNull`).
|
||||
*/
|
||||
public static Object export(Object value) {
|
||||
static Object export(Object value) {
|
||||
if (value instanceof VmValue vmValue) {
|
||||
return vmValue.export();
|
||||
}
|
||||
@@ -80,14 +80,10 @@ public abstract class VmValue {
|
||||
}
|
||||
|
||||
/** Used to export object member values. Such values are `null` if they haven't been forced. */
|
||||
public static @Nullable Object exportNullable(@Nullable Object value) {
|
||||
static @Nullable Object exportNullable(@Nullable Object value) {
|
||||
if (value instanceof VmValue vmValue) {
|
||||
return vmValue.export();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Enables calling `vmValue.equals()` when not behind a Truffle boundary. */
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright © 2026 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.util;
|
||||
|
||||
import com.oracle.truffle.api.object.DynamicObject;
|
||||
import com.oracle.truffle.api.object.DynamicObjectLibrary;
|
||||
|
||||
/**
|
||||
* A {@link MapCursor} implementation that iterates over the properties of a {@link DynamicObject}.
|
||||
*
|
||||
* <p>This cursor provides allocation-free iteration over DynamicObject properties by caching the
|
||||
* key array at construction time and accessing values on demand.
|
||||
*/
|
||||
public final class DynamicObjectMapCursor implements MapCursor<Object, Object> {
|
||||
private final DynamicObject object;
|
||||
private final DynamicObjectLibrary library;
|
||||
private final Object[] keys;
|
||||
private int index = -1;
|
||||
|
||||
/**
|
||||
* Creates a cursor for iterating over the given DynamicObject's properties.
|
||||
*
|
||||
* @param object the DynamicObject to iterate over
|
||||
*/
|
||||
public DynamicObjectMapCursor(DynamicObject object) {
|
||||
this.object = object;
|
||||
this.library = DynamicObjectLibrary.getUncached();
|
||||
this.keys = library.getKeyArray(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
index++;
|
||||
return index < keys.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getKey() {
|
||||
if (index < 0 || index >= keys.length) {
|
||||
throw new IllegalStateException("Cursor not positioned on a valid entry");
|
||||
}
|
||||
return keys[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
if (index < 0 || index >= keys.length) {
|
||||
throw new IllegalStateException("Cursor not positioned on a valid entry");
|
||||
}
|
||||
return library.getOrDefault(object, keys[index], null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright © 2026 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.util;
|
||||
|
||||
/** A cursor that iterates over zero entries. */
|
||||
public final class EmptyMapCursor<K, V> implements MapCursor<K, V> {
|
||||
private static final EmptyMapCursor<Object, Object> INSTANCE = new EmptyMapCursor<>();
|
||||
|
||||
private EmptyMapCursor() {}
|
||||
|
||||
/** Returns the singleton empty cursor instance. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> MapCursor<K, V> instance() {
|
||||
return (MapCursor<K, V>) INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
throw new IllegalStateException("Cannot get key from empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
throw new IllegalStateException("Cannot get value from empty cursor");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright © 2026 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.util;
|
||||
|
||||
/** A cursor for iterating over map entries without allocating Entry objects. */
|
||||
public interface MapCursor<K, V> {
|
||||
/**
|
||||
* Advances the cursor to the next entry.
|
||||
*
|
||||
* @return true if there is a next entry, false if iteration is complete
|
||||
*/
|
||||
boolean advance();
|
||||
|
||||
/**
|
||||
* Returns the key at the current cursor position.
|
||||
*
|
||||
* @throws IllegalStateException if called before {@link #advance()} or after it returns false
|
||||
*/
|
||||
K getKey();
|
||||
|
||||
/**
|
||||
* Returns the value at the current cursor position.
|
||||
*
|
||||
* @throws IllegalStateException if called before {@link #advance()} or after it returns false
|
||||
*/
|
||||
V getValue();
|
||||
}
|
||||
Reference in New Issue
Block a user