package com.oracle.truffle.js.nodes.access;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
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.Truffle;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.InvalidAssumptionException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeCost;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.cast.JSToObjectNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSAdapter;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferView;
import com.oracle.truffle.js.runtime.builtins.JSClass;
import com.oracle.truffle.js.runtime.builtins.JSDictionary;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.builtins.JSRegExp;
import com.oracle.truffle.js.runtime.builtins.JSString;
import com.oracle.truffle.js.runtime.builtins.PrototypeSupplier;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSLazyString;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSProperty;
import com.oracle.truffle.js.runtime.objects.JSShape;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.DebugCounter;
public abstract class PropertyCacheNode<T extends PropertyCacheNode.CacheNode<T>> extends JavaScriptBaseNode {
protected abstract static class ReceiverCheckNode extends JavaScriptBaseNode {
protected ReceiverCheckNode() {
}
public abstract boolean accept(Object thisObj);
public abstract DynamicObject getStore(Object thisObj);
public Shape getShape() {
return null;
}
public boolean isValid() {
return true;
}
protected boolean isUnstable() {
return false;
}
@Override
public final NodeCost getCost() {
return NodeCost.NONE;
}
}
protected abstract static class AbstractShapeCheckNode extends ReceiverCheckNode {
private final Shape shape;
protected AbstractShapeCheckNode(Shape shape) {
this.shape = shape;
}
@Override
public abstract DynamicObject getStore(Object thisObj);
@Override
public final Shape getShape() {
return shape;
}
@Override
public boolean accept(Object thisObj) {
return shape.getLayout().getType().isInstance(thisObj) && shape.check((DynamicObject) thisObj);
}
public int getDepth() {
return 0;
}
@Override
public abstract boolean isValid();
}
protected static final class NullCheckNode extends ReceiverCheckNode {
@Override
public boolean accept(Object thisObj) {
return thisObj == null;
}
@Override
public DynamicObject getStore(Object thisObj) {
throw Errors.shouldNotReachHere();
}
}
protected static final class InstanceofCheckNode extends ReceiverCheckNode {
protected final Class<?> type;
@Child private JSToObjectNode toObject;
protected InstanceofCheckNode(Class<?> type, JSContext context) {
this.type = type;
this.toObject = JSToObjectNode.createToObjectNoCheckNoForeign(context);
}
@Override
public boolean accept(Object thisObj) {
return type.isInstance(thisObj);
}
@Override
public DynamicObject getStore(Object thisObj) {
return (DynamicObject) toObject.execute(thisObj);
}
}
protected static final class PrimitiveReceiverCheckNode extends ReceiverCheckNode {
private final Class<?> type;
@Child private AbstractShapeCheckNode prototypeShapeCheck;
protected PrimitiveReceiverCheckNode(Class<?> type, AbstractShapeCheckNode prototypeShapeCheck) {
this.type = type;
this.prototypeShapeCheck = prototypeShapeCheck;
}
@Override
public boolean accept(Object thisObj) {
if (type.isInstance(thisObj)) {
return prototypeShapeCheck.accept(thisObj);
} else {
return false;
}
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototypeShapeCheck.getStore(thisObj);
}
@Override
public boolean isValid() {
return prototypeShapeCheck.isValid();
}
}
protected static final class ShapeCheckNode extends AbstractShapeCheckNode {
private final Assumption shapeValidAssumption;
public ShapeCheckNode(Shape shape) {
super(shape);
this.shapeValidAssumption = shape.getValidAssumption();
}
@Override
public DynamicObject getStore(Object thisObj) {
return getShape().getLayout().getType().cast(thisObj);
}
@Override
public boolean isValid() {
return shapeValidAssumption.isValid();
}
}
protected abstract static class AbstractAssumptionShapeCheckNode extends AbstractShapeCheckNode {
protected final JSContext context;
protected AbstractAssumptionShapeCheckNode(Shape shape, JSContext context) {
super(shape);
this.context = context;
}
}
protected static final class AssumptionShapeCheckNode extends AbstractAssumptionShapeCheckNode {
private final Assumption shapeValidAssumption;
private final Assumption unchangedShapeAssumption;
private final Assumption stablePrototypeAssumption;
protected AssumptionShapeCheckNode(Shape shape, @SuppressWarnings("unused") Object key, JSContext context, Assumption unchangedAssumption, Assumption stablePrototypeAssumption) {
super(shape, context);
this.shapeValidAssumption = shape.getValidAssumption();
this.unchangedShapeAssumption = unchangedAssumption;
this.stablePrototypeAssumption = stablePrototypeAssumption;
}
public AssumptionShapeCheckNode(Shape shape, Object key, JSContext context) {
this(shape, key, context, JSShape.getPropertyAssumption(shape, key, false), null);
}
public AssumptionShapeCheckNode(Shape shape, Object key, JSContext context, boolean prototype, Assumption stablePrototypeAssumption) {
this(shape, key, context, JSShape.getPropertyAssumption(shape, key, prototype), stablePrototypeAssumption);
}
@Override
public boolean accept(Object thisObj) {
return true;
}
@Override
public DynamicObject getStore(Object thisObj) {
return ((JSDynamicObject) thisObj);
}
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!shapeValidAssumption.isValid()) {
return false;
} else if (!unchangedShapeAssumption.isValid()) {
return false;
} else if (stablePrototypeAssumption != null && !stablePrototypeAssumption.isValid()) {
return false;
}
return true;
}
@Override
protected boolean isUnstable() {
return shapeValidAssumption.isValid() && !unchangedShapeAssumption.isValid();
}
}
protected static final class PrototypeShapeCheckNode extends AbstractAssumptionShapeCheckNode {
private final Assumption notObsoletedAssumption;
private final Assumption protoNotObsoletedAssumption;
private final Assumption protoUnchangedAssumption;
private final DynamicObject prototype;
public PrototypeShapeCheckNode(Shape shape, DynamicObject thisObj, Object key, JSContext context) {
super(shape, context);
this.notObsoletedAssumption = shape.getValidAssumption();
DynamicObject finalProto = JSObject.getPrototype(thisObj);
Shape protoShape = finalProto.getShape();
this.protoNotObsoletedAssumption = protoShape.getValidAssumption();
this.protoUnchangedAssumption = JSShape.getPropertyAssumption(protoShape, key, true);
this.prototype = finalProto;
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototype;
}
@Override
public int getDepth() {
return 1;
}
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!notObsoletedAssumption.isValid()) {
return false;
} else if (!protoNotObsoletedAssumption.isValid()) {
return false;
} else if (!protoUnchangedAssumption.isValid()) {
return false;
}
return true;
}
}
protected static final class PrototypeChainShapeCheckNode extends AbstractAssumptionShapeCheckNode {
private final Assumption shapeValidAssumption;
private final DynamicObject prototype;
@Children private final AssumptionShapeCheckNode[] shapeCheckNodes;
public PrototypeChainShapeCheckNode(Shape shape, DynamicObject thisObj, Object key, int depth, JSContext context) {
super(shape, context);
this.shapeValidAssumption = shape.getValidAssumption();
this.shapeCheckNodes = new AssumptionShapeCheckNode[depth];
Shape depthShape = shape;
DynamicObject depthProto = thisObj;
for (int i = 0; i < depth; i++) {
Assumption stablePrototypeAssumption = i == 0 ? null : JSShape.getPrototypeAssumption(depthShape);
depthProto = JSObject.getPrototype(depthProto);
depthShape = depthProto.getShape();
shapeCheckNodes[i] = new AssumptionShapeCheckNode(depthShape, key, context, true, stablePrototypeAssumption);
}
this.prototype = depthProto;
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototype;
}
@Override
public int getDepth() {
return shapeCheckNodes.length;
}
@ExplodeLoop
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!shapeValidAssumption.isValid()) {
return false;
}
for (int i = 0; i < shapeCheckNodes.length; i++) {
if (!shapeCheckNodes[i].isValid()) {
return false;
}
}
return true;
}
}
protected interface ConstantObjectReceiverCheck {
Object getExpectedObject();
void clearExpectedObject();
}
protected static final class ConstantObjectShapeCheckNode extends AbstractShapeCheckNode implements ConstantObjectReceiverCheck {
private final Assumption shapeValidAssumption;
private final WeakReference<JSDynamicObject> expectedObjectRef;
public ConstantObjectShapeCheckNode(Shape shape, JSDynamicObject thisObj) {
super(shape);
this.shapeValidAssumption = shape.getValidAssumption();
this.expectedObjectRef = new WeakReference<>(thisObj);
}
@Override
public boolean accept(Object thisObj) {
JSDynamicObject expectedObj = this.expectedObjectRef.get();
if (thisObj != expectedObj) {
return false;
}
assert expectedObj != null;
return super.accept(thisObj);
}
@Override
public DynamicObject getStore(Object thisObj) {
return ((JSDynamicObject) thisObj);
}
@Override
public boolean isValid() {
if (!shapeValidAssumption.isValid()) {
return false;
} else if (expectedObjectRef.get() == null) {
return false;
}
return true;
}
@Override
public Object getExpectedObject() {
return expectedObjectRef.get();
}
@Override
public void clearExpectedObject() {
expectedObjectRef.clear();
}
}
protected static final class ConstantObjectAssumptionShapeCheckNode extends AbstractAssumptionShapeCheckNode implements ConstantObjectReceiverCheck {
private final Assumption shapeValidAssumption;
private final Assumption unchangedAssumption;
private final WeakReference<JSDynamicObject> expectedObjectRef;
public ConstantObjectAssumptionShapeCheckNode(Shape shape, JSDynamicObject thisObj, Object key, JSContext context) {
super(shape, context);
this.shapeValidAssumption = shape.getValidAssumption();
this.unchangedAssumption = JSShape.getPropertyAssumption(shape, key);
this.expectedObjectRef = new WeakReference<>(thisObj);
}
@Override
public boolean accept(Object thisObj) {
JSDynamicObject expectedObj = this.expectedObjectRef.get();
return thisObj == expectedObj;
}
@Override
public DynamicObject getStore(Object thisObj) {
return ((JSDynamicObject) thisObj);
}
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!shapeValidAssumption.isValid()) {
return false;
} else if (!unchangedAssumption.isValid()) {
return false;
} else if (expectedObjectRef.get() == null) {
return false;
}
return true;
}
@Override
protected boolean isUnstable() {
return shapeValidAssumption.isValid() && !unchangedAssumption.isValid();
}
@Override
public Object getExpectedObject() {
return expectedObjectRef.get();
}
@Override
public void clearExpectedObject() {
expectedObjectRef.clear();
}
}
protected static final class ConstantObjectPrototypeChainShapeCheckNode extends AbstractAssumptionShapeCheckNode implements ConstantObjectReceiverCheck {
private final Assumption shapeValidAssumption;
private final Assumption shapeUnchangedAssumption;
private final WeakReference<JSDynamicObject> expectedObjectRef;
private final WeakReference<DynamicObject> prototype;
@Children private final AssumptionShapeCheckNode[] shapeCheckNodes;
public ConstantObjectPrototypeChainShapeCheckNode(Shape shape, JSDynamicObject thisObj, Object key, int depth, JSContext context) {
super(shape, context);
this.shapeValidAssumption = shape.getValidAssumption();
this.shapeUnchangedAssumption = JSShape.getPropertyAssumption(shape, key);
this.expectedObjectRef = new WeakReference<>(thisObj);
this.shapeCheckNodes = new AssumptionShapeCheckNode[depth];
Shape depthShape = shape;
DynamicObject depthProto = thisObj;
for (int i = 0; i < depth; i++) {
Assumption stablePrototypeAssumption = JSShape.getPrototypeAssumption(depthShape);
depthProto = JSObject.getPrototype(depthProto);
depthShape = depthProto.getShape();
shapeCheckNodes[i] = new AssumptionShapeCheckNode(depthShape, key, context, true, stablePrototypeAssumption);
}
this.prototype = new WeakReference<>(depthProto);
}
@Override
public boolean accept(Object thisObj) {
JSDynamicObject expectedObj = this.expectedObjectRef.get();
if (thisObj != expectedObj) {
return false;
}
assert this.prototype.get() != null;
return true;
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototype.get();
}
@Override
public int getDepth() {
return shapeCheckNodes.length;
}
@ExplodeLoop
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!shapeValidAssumption.isValid()) {
return false;
} else if (!shapeUnchangedAssumption.isValid()) {
return false;
} else if (expectedObjectRef.get() == null) {
return false;
} else if (prototype.get() == null) {
return false;
}
for (int i = 0; i < shapeCheckNodes.length; i++) {
if (!shapeCheckNodes[i].isValid()) {
return false;
}
}
return true;
}
@Override
protected boolean isUnstable() {
return shapeValidAssumption.isValid() && !shapeUnchangedAssumption.isValid();
}
@Override
public Object getExpectedObject() {
return expectedObjectRef.get();
}
@Override
public void clearExpectedObject() {
expectedObjectRef.clear();
}
}
protected static final class ConstantObjectPrototypeShapeCheckNode extends AbstractAssumptionShapeCheckNode implements ConstantObjectReceiverCheck {
private final Assumption shapeValidAssumption;
private final Assumption unchangedAssumption;
private final Assumption stableProtoAssumption;
private final Assumption protoShapeValidAssumption;
private final Assumption protoUnchangedAssumption;
private final WeakReference<JSDynamicObject> expectedObjectRef;
private final WeakReference<DynamicObject> prototype;
public ConstantObjectPrototypeShapeCheckNode(Shape shape, JSDynamicObject thisObj, Object key, JSContext context) {
super(shape, context);
this.shapeValidAssumption = shape.getValidAssumption();
this.unchangedAssumption = JSShape.getPropertyAssumption(shape, key);
this.stableProtoAssumption = JSShape.getPrototypeAssumption(shape);
DynamicObject finalProto = JSObject.getPrototype(thisObj);
Shape protoShape = finalProto.getShape();
this.protoShapeValidAssumption = protoShape.getValidAssumption();
this.protoUnchangedAssumption = JSShape.getPropertyAssumption(protoShape, key, true);
this.expectedObjectRef = new WeakReference<>(thisObj);
this.prototype = new WeakReference<>(finalProto);
}
@Override
public boolean accept(Object thisObj) {
JSDynamicObject expectedObj = this.expectedObjectRef.get();
if (thisObj != expectedObj) {
return false;
}
assert this.prototype.get() != null;
return true;
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototype.get();
}
@Override
public int getDepth() {
return 1;
}
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
} else if (!shapeValidAssumption.isValid()) {
return false;
} else if (!unchangedAssumption.isValid()) {
return false;
} else if (!stableProtoAssumption.isValid()) {
return false;
} else if (!protoShapeValidAssumption.isValid()) {
return false;
} else if (!protoUnchangedAssumption.isValid()) {
return false;
} else if (expectedObjectRef.get() == null) {
return false;
} else if (prototype.get() == null) {
return false;
}
return true;
}
@Override
protected boolean isUnstable() {
return shapeValidAssumption.isValid() && !unchangedAssumption.isValid();
}
@Override
public Object getExpectedObject() {
return expectedObjectRef.get();
}
@Override
public void clearExpectedObject() {
expectedObjectRef.clear();
}
}
protected static final class TraversePrototypeChainShapeCheckNode extends AbstractShapeCheckNode {
private final Assumption shapeValidAssumption;
@Children private final ShapeCheckNode[] shapeCheckNodes;
@Children private final GetPrototypeNode[] getPrototypeNodes;
public TraversePrototypeChainShapeCheckNode(Shape shape, DynamicObject thisObj, int depth) {
super(shape);
this.shapeValidAssumption = shape.getValidAssumption();
this.shapeCheckNodes = new ShapeCheckNode[depth];
this.getPrototypeNodes = new GetPrototypeNode[depth];
Shape depthShape = shape;
DynamicObject depthProto = thisObj;
for (int i = 0; i < depth; i++) {
depthProto = JSObject.getPrototype(depthProto);
depthShape = depthProto.getShape();
shapeCheckNodes[i] = new ShapeCheckNode(depthShape);
getPrototypeNodes[i] = GetPrototypeNode.create();
}
}
@ExplodeLoop
@Override
public boolean accept(Object thisObj) {
if (!JSDynamicObject.isJSDynamicObject(thisObj)) {
return false;
}
DynamicObject current = (DynamicObject) thisObj;
boolean result = getShape().check(current);
if (!result) {
return false;
}
for (int i = 0; i < shapeCheckNodes.length; i++) {
current = getPrototypeNodes[i].executeDynamicObject(current);
result = shapeCheckNodes[i].accept(current);
if (!result) {
return false;
}
}
return result;
}
@ExplodeLoop
@Override
public DynamicObject getStore(Object thisObj) {
DynamicObject proto = (DynamicObject) thisObj;
for (int i = 0; i < shapeCheckNodes.length; i++) {
proto = getPrototypeNodes[i].executeDynamicObject(proto);
}
return proto;
}
@Override
public int getDepth() {
return shapeCheckNodes.length;
}
@ExplodeLoop
@Override
public boolean isValid() {
if (!shapeValidAssumption.isValid()) {
return false;
}
for (int i = 0; i < shapeCheckNodes.length; i++) {
if (!shapeCheckNodes[i].isValid()) {
return false;
}
}
return true;
}
}
protected static final class TraversePrototypeShapeCheckNode extends AbstractShapeCheckNode {
private final Assumption shapeValidAssumption;
@Child private ShapeCheckNode protoShapeCheck;
@Child private GetPrototypeNode getPrototypeNode;
public TraversePrototypeShapeCheckNode(Shape shape, DynamicObject thisObj) {
super(shape);
this.shapeValidAssumption = shape.getValidAssumption();
this.protoShapeCheck = new ShapeCheckNode(JSObject.getPrototype(thisObj).getShape());
this.getPrototypeNode = GetPrototypeNode.create();
}
@Override
public boolean accept(Object thisObj) {
if (JSDynamicObject.isJSDynamicObject(thisObj)) {
DynamicObject jsobj = (DynamicObject) thisObj;
if (getShape().check(jsobj)) {
return protoShapeCheck.accept(getPrototypeNode.executeDynamicObject(jsobj));
}
}
return false;
}
@Override
public DynamicObject getStore(Object thisObj) {
return getPrototypeNode.executeDynamicObject((DynamicObject) thisObj);
}
@Override
public int getDepth() {
return 1;
}
@Override
public boolean isValid() {
if (!shapeValidAssumption.isValid()) {
return false;
} else if (!protoShapeCheck.isValid()) {
return false;
}
return true;
}
}
protected static final class PrototypeChainCheckNode extends AbstractAssumptionShapeCheckNode {
private final DynamicObject prototype;
@Children private final AssumptionShapeCheckNode[] shapeCheckNodes;
public PrototypeChainCheckNode(Shape shape, DynamicObject thisObj, Object key, int depth, JSContext context) {
super(shape, context);
assert depth >= 1;
this.shapeCheckNodes = new AssumptionShapeCheckNode[depth];
Shape depthShape = shape;
DynamicObject depthProto = thisObj;
for (int i = 0; i < depth; i++) {
Assumption stablePrototypeAssumption = JSShape.getPrototypeAssumption(depthShape);
depthProto = JSObject.getPrototype(depthProto);
depthShape = depthProto.getShape();
shapeCheckNodes[i] = new AssumptionShapeCheckNode(depthShape, key, context, true, stablePrototypeAssumption);
}
this.prototype = depthProto;
}
@Override
public boolean accept(Object thisObj) {
return true;
}
@Override
public DynamicObject getStore(Object thisObj) {
return prototype;
}
@Override
public int getDepth() {
return shapeCheckNodes.length;
}
@ExplodeLoop
@Override
public boolean isValid() {
if (!context.isSingleRealm()) {
return false;
}
for (int i = 0; i < shapeCheckNodes.length; i++) {
if (!shapeCheckNodes[i].isValid()) {
return false;
}
}
return true;
}
}
protected static final class TraversePrototypeChainCheckNode extends AbstractShapeCheckNode {
private final JSContext context;
private final PrototypeSupplier jsclass;
@Children private final ShapeCheckNode[] shapeCheckNodes;
@Children private final GetPrototypeNode[] getPrototypeNodes;
public TraversePrototypeChainCheckNode(Shape shape, DynamicObject thisObj, int depth, JSClass jsclass, JSContext context) {
super(shape);
assert depth >= 1;
this.context = context;
this.jsclass = (PrototypeSupplier) jsclass;
this.shapeCheckNodes = new ShapeCheckNode[depth];
this.getPrototypeNodes = new GetPrototypeNode[depth - 1];
Shape depthShape = shape;
DynamicObject depthProto = thisObj;
for (int i = 0; i < depth; i++) {
depthProto = JSObject.getPrototype(depthProto);
depthShape = depthProto.getShape();
shapeCheckNodes[i] = new ShapeCheckNode(depthShape);
if (i < depth - 1) {
getPrototypeNodes[i] = GetPrototypeNode.create();
}
}
}
@ExplodeLoop
@Override
public boolean accept(Object thisObj) {
DynamicObject current = jsclass.getIntrinsicDefaultProto(context.getRealm());
boolean result = true;
for (int i = 0; i < shapeCheckNodes.length; i++) {
result = shapeCheckNodes[i].accept(current);
if (!result) {
return false;
}
if (i < shapeCheckNodes.length - 1) {
current = getPrototypeNodes[i].executeDynamicObject(current);
}
}
return result;
}
@ExplodeLoop
@Override
public DynamicObject getStore(Object thisObj) {
DynamicObject proto = jsclass.getIntrinsicDefaultProto(context.getRealm());
for (int i = 0; i < getPrototypeNodes.length; i++) {
proto = getPrototypeNodes[i].executeDynamicObject(proto);
}
return proto;
}
@Override
public int getDepth() {
return shapeCheckNodes.length;
}
@ExplodeLoop
@Override
public boolean isValid() {
for (int i = 0; i < shapeCheckNodes.length; i++) {
if (!shapeCheckNodes[i].isValid()) {
return false;
}
}
return true;
}
}
protected static final class JSClassCheckNode extends ReceiverCheckNode {
private final JSClass jsclass;
protected JSClassCheckNode(JSClass jsclass) {
this.jsclass = jsclass;
}
@Override
public boolean accept(Object thisObj) {
return JSClass.isInstance(thisObj, jsclass);
}
@Override
public DynamicObject getStore(Object thisObj) {
return (DynamicObject) thisObj;
}
}
protected static final class ForeignLanguageCheckNode extends ReceiverCheckNode {
@Override
public boolean accept(Object thisObj) {
return JSRuntime.isForeignObject(thisObj);
}
@Override
public DynamicObject getStore(Object thisObj) {
throw Errors.shouldNotReachHere();
}
}
public abstract static class CacheNode<T extends CacheNode<T>> extends JavaScriptBaseNode {
@Child protected T next;
@Child protected ReceiverCheckNode receiverCheck;
protected CacheNode(ReceiverCheckNode receiverCheck) {
this.receiverCheck = receiverCheck;
}
protected CacheNode(T next, ReceiverCheckNode receiverCheck) {
this.next = next;
this.receiverCheck = receiverCheck;
}
protected final T getNext() {
return next;
}
protected final void setNext(T to) {
next = to;
}
@SuppressWarnings("unchecked")
protected T withNext(T newNext) {
T copy = (T) copy();
copy.next = newNext;
return copy;
}
protected final boolean isGeneric() {
return receiverCheck == null;
}
protected final boolean accepts(Object thisObj) {
return receiverCheck == null || receiverCheck.accept(thisObj);
}
protected boolean isValid() {
return receiverCheck == null || receiverCheck.isValid();
}
protected boolean acceptsValue(Object value) {
assert value == null;
return true;
}
protected boolean sweep() {
return false;
}
protected String debugString() {
CompilerAsserts.neverPartOfCompilation();
if (receiverCheck != null) {
return getClass().getSimpleName() + "<check=" + receiverCheck + ", shape=" + receiverCheck.getShape() + ">\n" + ((next == null) ? "" : next.debugString());
}
return null;
}
@Override
public final NodeCost getCost() {
return NodeCost.NONE;
}
}
protected final Object key;
protected final JSContext context;
@Child protected T cacheNode;
@CompilationFinal private Assumption invalidationAssumption;
public PropertyCacheNode(Object key, JSContext context) {
this.key = key;
this.context = context;
assert JSRuntime.isPropertyKey(key) || key instanceof HiddenKey;
}
public final Object getKey() {
return key;
}
protected abstract T createGenericPropertyNode();
protected abstract T createCachedPropertyNode(Property entry, Object thisObj, int depth, Object value, T currentHead);
protected abstract T createUndefinedPropertyNode(Object thisObj, Object store, int depth, Object value);
protected abstract T createJavaPropertyNodeMaybe(Object thisObj, int depth);
protected abstract T createTruffleObjectPropertyNode();
@TruffleBoundary
protected T specialize(Object thisObj) {
return specialize(thisObj, null);
}
@TruffleBoundary
protected T specialize(Object thisObj, Object value) {
T res;
Lock lock = getLock();
lock.lock();
try {
T currentHead = cacheNode;
do {
assert currentHead == cacheNode;
int cachedCount = 0;
boolean invalid = false;
boolean generic = false;
res = null;
for (T c = currentHead; c != null; c = c.next) {
if (c.isGeneric()) {
generic = true;
res = c;
assert c.next == null;
break;
} else {
cachedCount++;
if (!c.isValid()) {
invalid = true;
break;
} else {
c.sweep();
if (res == null && c.accepts(thisObj) && c.acceptsValue(value)) {
res = c;
} else if (isUnexpectedConstantObject(c, thisObj)) {
invalid = true;
break;
}
}
}
}
if (invalid) {
checkForUnstableAssumption(currentHead, thisObj);
currentHead = rewriteCached(currentHead, filterValid(currentHead));
traceAssumptionInvalidated();
res = null;
continue;
}
if (res == null) {
assert !generic;
T newNode = createSpecialization(thisObj, currentHead, cachedCount, value);
if (newNode == null) {
currentHead = this.cacheNode;
continue;
}
res = newNode;
assert res.getParent() != null;
}
} while (res == null);
} finally {
lock.unlock();
}
if (!(res.isGeneric() || (res.accepts(thisObj) && res.acceptsValue(value)))) {
throw Errors.shouldNotReachHere();
}
return res;
}
protected T createSpecialization(Object thisObj, T currentHead, int cachedCount, Object value) {
int depth = 0;
T specialized = null;
DynamicObject store = null;
if (JSDynamicObject.isJSDynamicObject(thisObj)) {
if ((!JSAdapter.isJSAdapter(thisObj) && !JSProxy.isJSProxy(thisObj)) || key instanceof HiddenKey) {
store = (DynamicObject) thisObj;
}
} else if (JSRuntime.isForeignObject(thisObj)) {
assert !JSDynamicObject.isJSDynamicObject(thisObj);
specialized = createTruffleObjectPropertyNode();
} else {
store = wrapPrimitive(thisObj, context);
}
while (store != null) {
if (DynamicObjectLibrary.getUncached().updateShape(store)) {
return retryCache();
}
Shape cacheShape = store.getShape();
if (JSConfig.DictionaryObject && JSDictionary.isJSDictionaryObject(store)) {
return rewriteToGeneric(currentHead, cachedCount, "dictionary object");
}
if (JSConfig.MergeShapes && cachedCount > 0) {
synchronized (store.getShape().getMutex()) {
if (tryMergeShapes(cacheShape, currentHead)) {
DynamicObjectLibrary.getUncached().updateShape(store);
return retryCache();
}
}
}
Property property = cacheShape.getProperty(key);
if (property != null) {
specialized = createCachedPropertyNode(property, thisObj, depth, value, currentHead);
if (specialized == null) {
return null;
}
break;
} else if (alwaysUseStore(store, key)) {
specialized = createUndefinedPropertyNode(thisObj, store, depth, value);
break;
} else if (isOwnProperty()) {
break;
}
store = (DynamicObject) JSRuntime.toJavaNull(JSObject.getPrototype(store));
if (store != null) {
depth++;
}
}
if (cachedCount >= context.getPropertyCacheLimit() || (specialized != null && specialized.isGeneric())) {
return rewriteToGeneric(currentHead, cachedCount, "cache limit reached");
}
if (specialized == null) {
specialized = createUndefinedPropertyNode(thisObj, thisObj, depth, value);
}
return insertCached(specialized, currentHead, cachedCount);
}
protected static boolean alwaysUseStore(DynamicObject store, Object key) {
return JSProxy.isJSProxy(store) || (JSArrayBufferView.isJSArrayBufferView(store) && isNonIntegerIndex(key)) || key instanceof HiddenKey;
}
protected final void deoptimize() {
if (invalidationAssumption == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
} else {
if (CompilerDirectives.inCompiledCode()) {
try {
invalidationAssumption.check();
} catch (InvalidAssumptionException e) {
}
}
}
}
protected T retryCache() {
if (invalidationAssumption == null) {
invalidationAssumption = Truffle.getRuntime().createAssumption("PropertyCacheNode");
cacheAssumptionInitializedCount.inc();
reportPolymorphicSpecialize();
}
return null;
}
protected void invalidateCache() {
if (invalidationAssumption != null) {
invalidationAssumption.invalidate("PropertyCacheNode invalidation");
invalidationAssumption = Truffle.getRuntime().createAssumption("PropertyCacheNode");
cacheAssumptionInvalidatedCount.inc();
}
}
protected T insertCached(T specialized, T currentHead, int cachedCount) {
assert currentHead == this.cacheNode;
invalidateCache();
insert(specialized);
specialized.setNext(currentHead);
this.cacheNode = specialized;
if (cachedCount > 0) {
polymorphicCount.inc();
}
traceRewriteInsert(specialized, cachedCount);
if (JSConfig.TracePolymorphicPropertyAccess && cachedCount > 0) {
System.out.printf("POLYMORPHIC PROPERTY ACCESS key='%s' %s\n%s\n---\n", key, getEncapsulatingSourceSection(), specialized.debugString());
}
return specialized;
}
protected T rewriteToGeneric(T currentHead, int cachedCount, String reason) {
assert currentHead == this.cacheNode;
T newNode = createGenericPropertyNode();
invalidateCache();
insert(newNode);
this.cacheNode = newNode;
if (cachedCount > 0 && cachedCount >= context.getPropertyCacheLimit()) {
megamorphicCount.inc();
reportPolymorphicSpecialize();
}
traceRewriteMegamorphic(newNode, reason);
if (JSConfig.TraceMegamorphicPropertyAccess) {
System.out.printf("MEGAMORPHIC PROPERTY ACCESS key='%s' %s\n%s\n---\n", key, getEncapsulatingSourceSection(), currentHead.debugString());
}
return newNode;
}
protected T rewriteCached(T currentHead, T newHead) {
assert currentHead == this.cacheNode;
invalidateCache();
this.cacheNode = newHead;
return newHead;
}
protected static <T extends CacheNode<T>> boolean tryMergeShapes(Shape cacheShape, T head) {
assert cacheShape.isValid();
boolean result = false;
for (T cur = head; cur != null; cur = cur.next) {
if (cur.receiverCheck == null) {
continue;
}
Shape other = cur.receiverCheck.getShape();
if (cacheShape != other && other != null && other.isValid()) {
assert cacheShape.isValid();
result |= cacheShape.tryMerge(other) != null;
if (!cacheShape.isValid()) {
break;
}
}
}
return result;
}
protected void checkForUnstableAssumption(T head, Object thisObj) {
for (T cur = head; cur != null; cur = cur.next) {
ReceiverCheckNode check = cur.receiverCheck;
if (check == null) {
continue;
}
if (check.isUnstable()) {
setPropertyAssumptionCheckEnabled(false);
propertyAssumptionCheckFailedCount.inc();
}
if (isUnexpectedConstantObject(cur, thisObj)) {
((ConstantObjectReceiverCheck) check).clearExpectedObject();
setPropertyAssumptionCheckEnabled(false);
constantObjectCheckFailedCount.inc();
traceRewriteEvictFinal(cur);
}
}
}
private boolean isUnexpectedConstantObject(T cache, Object thisObj) {
return cache.receiverCheck instanceof ConstantObjectReceiverCheck && ((ConstantObjectReceiverCheck) cache.receiverCheck).getExpectedObject() != thisObj;
}
protected static <T extends CacheNode<T>> T filterValid(T cache) {
if (cache == null) {
return null;
}
T filteredNext = filterValid(cache.next);
if (cache.isValid()) {
if (filteredNext == cache.next) {
return cache;
} else {
return cache.withNext(filteredNext);
}
} else {
return filteredNext;
}
}
protected static final DynamicObject wrapPrimitive(Object thisObject, JSContext context) {
Object wrapper = JSRuntime.toObjectFromPrimitive(context, thisObject, false);
return JSDynamicObject.isJSDynamicObject(wrapper) ? ((JSDynamicObject) wrapper) : null;
}
protected final AbstractShapeCheckNode createShapeCheckNode(Shape shape, JSDynamicObject thisObj, int depth, boolean isConstantObjectFinal, boolean isDefine) {
if (depth == 0) {
return createShapeCheckNodeDepth0(shape, thisObj, isConstantObjectFinal, isDefine);
} else if (depth == 1) {
return createShapeCheckNodeDepth1(shape, thisObj, depth, isConstantObjectFinal);
} else {
return createShapeCheckNodeDeeper(shape, thisObj, depth, isConstantObjectFinal);
}
}
private AbstractShapeCheckNode createShapeCheckNodeDepth0(Shape shape, JSDynamicObject thisObj, boolean isConstantObjectFinal, boolean isDefine) {
if (isGlobal() && JSConfig.SkipGlobalShapeCheck && !isDefine && isPropertyAssumptionCheckEnabled() && JSShape.getPropertyAssumption(shape, key).isValid()) {
return new AssumptionShapeCheckNode(shape, key, getContext());
} else if (isConstantObjectFinal) {
assert !isDefine;
if (isPropertyAssumptionCheckEnabled() && JSShape.getPropertyAssumption(shape, key).isValid()) {
return new ConstantObjectAssumptionShapeCheckNode(shape, thisObj, key, getContext());
} else {
return new ConstantObjectShapeCheckNode(shape, thisObj);
}
} else {
assert !isConstantObjectFinal;
return new ShapeCheckNode(shape);
}
}
private AbstractShapeCheckNode createShapeCheckNodeDepth1(Shape shape, JSDynamicObject thisObj, int depth, boolean isConstantObjectFinal) {
assert depth == 1;
if (JSConfig.SkipPrototypeShapeCheck && prototypesInShape(thisObj, depth) && propertyAssumptionsValid(thisObj, depth, isConstantObjectFinal)) {
return isConstantObjectFinal
? new ConstantObjectPrototypeShapeCheckNode(shape, thisObj, key, getContext())
: new PrototypeShapeCheckNode(shape, thisObj, key, getContext());
} else {
traversePrototypeShapeCheckCount.inc();
return new TraversePrototypeShapeCheckNode(shape, thisObj);
}
}
private AbstractShapeCheckNode createShapeCheckNodeDeeper(Shape shape, JSDynamicObject thisObj, int depth, boolean isConstantObjectFinal) {
assert depth > 1;
if (JSConfig.SkipPrototypeShapeCheck && prototypesInShape(thisObj, depth) && propertyAssumptionsValid(thisObj, depth, isConstantObjectFinal)) {
return isConstantObjectFinal
? new ConstantObjectPrototypeChainShapeCheckNode(shape, thisObj, key, depth, getContext())
: new PrototypeChainShapeCheckNode(shape, thisObj, key, depth, getContext());
} else {
traversePrototypeChainShapeCheckCount.inc();
return new TraversePrototypeChainShapeCheckNode(shape, thisObj, depth);
}
}
protected static boolean prototypesInShape(DynamicObject thisObj, int depth) {
DynamicObject depthObject = thisObj;
for (int i = 0; i < depth; i++) {
if (!JSShape.isPrototypeInShape(depthObject.getShape())) {
return false;
}
depthObject = JSObject.getPrototype(depthObject);
}
return true;
}
protected final boolean propertyAssumptionsValid(DynamicObject thisObj, int depth, boolean checkDepth0) {
if (!getContext().isSingleRealm()) {
return false;
}
DynamicObject depthObject = thisObj;
Shape depthShape = depthObject.getShape();
if (checkDepth0 && !JSShape.getPropertyAssumption(depthShape, key).isValid()) {
return false;
}
for (int i = 0; i < depth; i++) {
if ((depth != 0 || checkDepth0) && !JSShape.getPrototypeAssumption(depthShape).isValid()) {
return false;
}
depthObject = JSObject.getPrototype(depthObject);
depthShape = depthObject.getShape();
if (!JSShape.getPropertyAssumption(depthShape, key, true).isValid()) {
return false;
}
}
return true;
}
protected final ReceiverCheckNode createPrimitiveReceiverCheck(Object thisObj, int depth) {
if (depth == 0) {
return new InstanceofCheckNode(thisObj.getClass(), context);
} else {
assert JSRuntime.isJSPrimitive(thisObj);
DynamicObject wrapped = wrapPrimitive(thisObj, context);
AbstractShapeCheckNode prototypeShapeCheck;
if (JSConfig.SkipPrototypeShapeCheck && prototypesInShape(wrapped, depth) && propertyAssumptionsValid(wrapped, depth, false)) {
prototypeShapeCheck = new PrototypeChainCheckNode(wrapped.getShape(), wrapped, key, depth, context);
} else {
prototypeShapeCheck = new TraversePrototypeChainCheckNode(wrapped.getShape(), wrapped, depth, JSObject.getJSClass(wrapped), context);
}
return new PrimitiveReceiverCheckNode(thisObj.getClass(), prototypeShapeCheck);
}
}
protected abstract boolean isGlobal();
protected abstract boolean isOwnProperty();
public final JSContext getContext() {
return context;
}
protected abstract boolean isPropertyAssumptionCheckEnabled();
protected abstract void setPropertyAssumptionCheckEnabled(boolean value);
@Override
public NodeCost getCost() {
if (cacheNode == null) {
return NodeCost.UNINITIALIZED;
} else if (cacheNode.isGeneric()) {
return NodeCost.MEGAMORPHIC;
} else if (cacheNode.getNext() == null) {
return NodeCost.MONOMORPHIC;
} else {
return NodeCost.POLYMORPHIC;
}
}
protected static boolean isArrayLengthProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSArray.ArrayLengthProxyProperty;
}
protected static boolean isFunctionLengthProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSFunction.FunctionLengthPropertyProxy;
}
protected static boolean isFunctionNameProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSFunction.FunctionNamePropertyProxy;
}
protected static boolean isClassPrototypeProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSFunction.ClassPrototypeProxyProperty;
}
protected static boolean isStringLengthProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSString.StringLengthProxyProperty;
}
protected static boolean isLazyRegexResultIndexProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSRegExp.LazyRegexResultIndexProxyProperty;
}
protected static boolean isLazyNamedCaptureGroupProperty(Property property) {
return JSProperty.isProxy(property) && JSProperty.getConstantProxy(property) instanceof JSRegExp.LazyNamedCaptureGroupProperty;
}
protected static boolean isNonIntegerIndex(Object key) {
assert !(key instanceof String) || JSRuntime.INFINITY_STRING.equals(key) || (JSRuntime.canonicalNumericIndexString((String) key) == Undefined.instance);
return JSRuntime.INFINITY_STRING.equals(key);
}
private void traceRewriteInsert(Node newNode, int cacheDepth) {
if (TruffleOptions.TraceRewrites) {
PrintStream out = System.out;
out.printf("[truffle] rewrite %-50s |Property %s |Node %s (%d/%d)%n", this, key, newNode, cacheDepth, getContext().getPropertyCacheLimit());
}
}
private void traceRewriteMegamorphic(Node newNode, String reason) {
if (TruffleOptions.TraceRewrites) {
PrintStream out = System.out;
out.printf("[truffle] rewrite %-50s |Property %s |Node %s |Reason %s (limit %d)%n", this, key, newNode, reason, getContext().getPropertyCacheLimit());
}
}
protected void traceRewriteEvictFinal(Node evicted) {
if (TruffleOptions.TraceRewrites) {
PrintStream out = System.out;
out.printf("[truffle] rewrite %-50s |Property %s |Node %s |Reason evict final%n", this, key, evicted);
}
}
private void traceAssumptionInvalidated() {
if (TruffleOptions.TraceRewrites) {
PrintStream out = System.out;
out.printf("[truffle] rewrite %-50s |Property %s |Reason assumption invalidated%n", this, key);
}
}
protected String getAccessorKey(String getset) {
return getAccessorKey(getset, getKey());
}
@TruffleBoundary
protected static String getAccessorKey(String getset, Object key) {
assert JSRuntime.isString(key);
String origKey = key instanceof String ? (String) key : ((JSLazyString) key).toString();
if (origKey.length() > 0 && Character.isLetter(origKey.charAt(0))) {
return getset + origKey.substring(0, 1).toUpperCase() + origKey.substring(1);
}
return null;
}
protected static DynamicObjectLibrary createCachedAccess(Object key, ReceiverCheckNode receiverCheck, DynamicObject store) {
assert key != null;
if (receiverCheck instanceof AbstractAssumptionShapeCheckNode) {
return DynamicObjectLibrary.getFactory().create(store);
} else if (receiverCheck instanceof AbstractShapeCheckNode && !(receiverCheck instanceof AbstractAssumptionShapeCheckNode)) {
return DynamicObjectLibrary.getFactory().create(store);
} else {
return DynamicObjectLibrary.getFactory().createDispatched(JSConfig.PropertyCacheLimit);
}
}
private static final DebugCounter polymorphicCount = DebugCounter.create("Polymorphic property cache count");
private static final DebugCounter megamorphicCount = DebugCounter.create("Megamorphic property cache count");
private static final DebugCounter cacheAssumptionInitializedCount = DebugCounter.create("Property cache assumptions initialized");
private static final DebugCounter cacheAssumptionInvalidatedCount = DebugCounter.create("Property cache assumptions invalidated");
private static final DebugCounter propertyAssumptionCheckFailedCount = DebugCounter.create("Property assumption checks failed");
private static final DebugCounter constantObjectCheckFailedCount = DebugCounter.create("Constant object checks failed");
private static final DebugCounter traversePrototypeShapeCheckCount = DebugCounter.create("TraversePrototypeShapeCheckNode count");
private static final DebugCounter traversePrototypeChainShapeCheckCount = DebugCounter.create("TraversePrototypeChainShapeCheckNode count");
}