diff graal/com.oracle.truffle.object/src/com/oracle/truffle/object/ShapeImpl.java @ 18408:2c3666f44855

Truffle: initial commit of object API implementation
author Andreas Woess <andreas.woess@jku.at>
date Tue, 18 Nov 2014 23:19:43 +0100
parents
children b3b241bbbbdb
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.object/src/com/oracle/truffle/object/ShapeImpl.java	Tue Nov 18 23:19:43 2014 +0100
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.object;
+
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.object.*;
+import com.oracle.truffle.object.LocationImpl.InternalLongLocation;
+import com.oracle.truffle.object.Locations.ConstantLocation;
+import com.oracle.truffle.object.Locations.DeclaredDualLocation;
+import com.oracle.truffle.object.Locations.DeclaredLocation;
+import com.oracle.truffle.object.Locations.DualLocation;
+import com.oracle.truffle.object.Locations.ValueLocation;
+import com.oracle.truffle.object.Transition.AddTransition;
+import com.oracle.truffle.object.Transition.OperationsTransition;
+import com.oracle.truffle.object.Transition.PropertyTransition;
+import com.oracle.truffle.object.Transition.RemoveTransition;
+
+/**
+ * Shape objects create a mapping of Property objects to indexes. The mapping of those indexes to an
+ * actual store is not part of Shape's role, but JSObject's. Shapes are immutable; adding or
+ * deleting a property yields a new Shape which links to the old one. This allows inline caching to
+ * simply check the identity of an object's Shape to determine if the cache is valid. There is one
+ * exception to this immutability, the transition map, but that is used simply to assure that an
+ * identical series of property additions and deletions will yield the same Shape object.
+ *
+ * @see DynamicObject
+ * @see Property
+ * @see Locations
+ */
+public abstract class ShapeImpl extends Shape {
+    private final int id;
+
+    protected final LayoutImpl layout;
+    protected final ObjectType objectType;
+    protected final ShapeImpl parent;
+    protected final PropertyMap propertyMap;
+
+    private final Object extraData;
+    private final Object sharedData;
+    private final ShapeImpl root;
+
+    protected final int objectArraySize;
+    protected final int objectArrayCapacity;
+    protected final int objectFieldSize;
+    protected final int primitiveFieldSize;
+    protected final int primitiveArraySize;
+    protected final int primitiveArrayCapacity;
+    protected final boolean hasPrimitiveArray;
+
+    protected final int depth;
+    protected final int propertyCount;
+    protected Property[] propertyArray;
+
+    protected final Assumption validAssumption;
+    protected final Assumption leafAssumption;
+
+    /**
+     * Shape transition map; lazily initialized.
+     *
+     * @see #getTransitionMapForRead()
+     * @see #getTransitionMapForWrite()
+     */
+    private HashMap<Transition, ShapeImpl> transitionMap;
+
+    /**
+     * Private constructor.
+     *
+     * @see #ShapeImpl(Layout, ShapeImpl, ObjectType, Object, PropertyMap, BaseAllocator, int)
+     */
+    private ShapeImpl(Layout layout, ShapeImpl parent, ObjectType operations, Object sharedData, PropertyMap propertyMap, int objectArraySize, int objectFieldSize, int primitiveFieldSize,
+                    int primitiveArraySize, boolean hasPrimitiveArray, int id) {
+        this.layout = (LayoutImpl) layout;
+        this.objectType = Objects.requireNonNull(operations);
+        this.propertyMap = Objects.requireNonNull(propertyMap);
+        this.root = parent != null ? parent.getRoot() : this;
+        this.parent = parent;
+        this.objectArraySize = objectArraySize;
+        this.objectArrayCapacity = capacityFromSize(objectArraySize);
+        this.objectFieldSize = objectFieldSize;
+        this.primitiveFieldSize = primitiveFieldSize;
+        this.primitiveArraySize = primitiveArraySize;
+        this.primitiveArrayCapacity = capacityFromSize(primitiveArraySize);
+        this.hasPrimitiveArray = hasPrimitiveArray;
+
+        if (parent != null) {
+            this.propertyCount = makePropertyCount(parent, propertyMap);
+            this.propertyArray = makePropertiesList(parent, propertyMap);
+            this.depth = parent.depth + 1;
+        } else {
+            this.propertyCount = 0;
+            this.propertyArray = null;
+            this.depth = 0;
+        }
+
+        this.validAssumption = createValidAssumption();
+        this.leafAssumption = createLeafAssumption();
+
+        this.id = id;
+        shapeCount.inc();
+
+        this.sharedData = sharedData;
+        this.extraData = operations.createShapeData(this);
+
+        debugRegisterShape(this);
+    }
+
+    protected ShapeImpl(Layout layout, ShapeImpl parent, ObjectType operations, Object sharedData, PropertyMap propertyMap, Allocator allocator, int id) {
+        this(layout, parent, operations, sharedData, propertyMap, ((BaseAllocator) allocator).objectArraySize, ((BaseAllocator) allocator).objectFieldSize,
+                        ((BaseAllocator) allocator).primitiveFieldSize, ((BaseAllocator) allocator).primitiveArraySize, ((BaseAllocator) allocator).hasPrimitiveArray, id);
+    }
+
+    @SuppressWarnings("hiding")
+    protected abstract ShapeImpl createShape(Layout layout, Object sharedData, ShapeImpl parent, ObjectType operations, PropertyMap propertyMap, Allocator allocator, int id);
+
+    protected ShapeImpl(Layout layout, ObjectType operations, Object sharedData, int id) {
+        this(layout, null, operations, sharedData, PropertyMap.empty(), layout.createAllocator(), id);
+    }
+
+    private static int makePropertyCount(ShapeImpl parent, PropertyMap propertyMap) {
+        return parent.propertyCount + ((propertyMap.size() > parent.propertyMap.size() && !propertyMap.getLastProperty().isHidden() && !propertyMap.getLastProperty().isShadow()) ? 1 : 0);
+    }
+
+    private static Property[] makePropertiesList(ShapeImpl parent, PropertyMap propertyMap) {
+        Property[] properties = parent.propertyArray;
+        if (properties != null && propertyMap.size() != parent.propertyMap.size()) {
+            Property lastProperty = propertyMap.getLastProperty();
+            if (lastProperty != null && !lastProperty.isHidden()) {
+                propertyListAllocCount.inc();
+                if (!lastProperty.isShadow()) {
+                    properties = Arrays.copyOf(properties, properties.length + 1);
+                    properties[properties.length - 1] = lastProperty;
+                } else {
+                    properties = Arrays.copyOf(properties, properties.length);
+                    for (int i = 0; i < properties.length; i++) {
+                        if (properties[i].isSame(lastProperty)) {
+                            properties[i] = lastProperty;
+                        }
+                    }
+                }
+            } else {
+                propertyListShareCount.inc();
+            }
+        }
+        return properties;
+    }
+
+    @Override
+    public final Property getLastProperty() {
+        return propertyMap.getLastProperty();
+    }
+
+    @Override
+    public final int getId() {
+        return this.id;
+    }
+
+    /**
+     * Calculate array size for the given number of elements.
+     */
+    private static int capacityFromSize(int size) {
+        if (size == 0) {
+            return 0;
+        } else if (size < 4) {
+            return 4;
+        } else if (size < 32) {
+            return ((size + 7) / 8) * 8;
+        } else {
+            return ((size + 15) / 16) * 16;
+        }
+    }
+
+    @Override
+    public final int getObjectArraySize() {
+        return objectArraySize;
+    }
+
+    @Override
+    public final int getObjectFieldSize() {
+        return objectFieldSize;
+    }
+
+    @Override
+    public final int getPrimitiveFieldSize() {
+        return primitiveFieldSize;
+    }
+
+    @Override
+    public final int getObjectArrayCapacity() {
+        return objectArrayCapacity;
+    }
+
+    @Override
+    public final int getPrimitiveArrayCapacity() {
+        return primitiveArrayCapacity;
+    }
+
+    @Override
+    public final int getPrimitiveArraySize() {
+        return primitiveArraySize;
+    }
+
+    @Override
+    public final boolean hasPrimitiveArray() {
+        return hasPrimitiveArray;
+    }
+
+    /**
+     * Get the (parent) shape that holds the given property.
+     */
+    public final ShapeImpl getShapeFromProperty(Object propertyName) {
+        ShapeImpl current = this;
+        while (current != getRoot()) {
+            if (current.getLastProperty().getKey().equals(propertyName)) {
+                return current;
+            }
+            current = current.getParent();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the (parent) shape that holds the given property.
+     */
+    public final ShapeImpl getShapeFromProperty(Property prop) {
+        ShapeImpl current = this;
+        while (current != getRoot()) {
+            if (current.getLastProperty().equals(prop)) {
+                return current;
+            }
+            current = current.parent;
+        }
+
+        return null;
+    }
+
+    /**
+     * Get a property entry by string name.
+     *
+     * @param key the name to look up
+     * @return a Property object, or null if not found
+     */
+    @Override
+    @TruffleBoundary
+    public final Property getProperty(Object key) {
+        // return this.propertyMap.get(propertyName);
+        PropertyMap current = this.propertyMap;
+        while (current.getLastProperty() != null) {
+            if (current.getLastProperty().getKey().equals(key)) {
+                return current.getLastProperty();
+            }
+            current = current.getParentMap();
+        }
+
+        return null;
+    }
+
+    protected final void addTransition(Transition transition, ShapeImpl next) {
+        assert next.getParent() == this;
+        addTransitionInternal(transition, next);
+    }
+
+    public final void addNonlinearTransition(Transition transition, ShapeImpl next) {
+        assert next.getParent() != this;
+        addTransitionInternal(transition, next);
+    }
+
+    private void addTransitionInternal(Transition transition, ShapeImpl next) {
+        getTransitionMapForWrite().put(transition, next);
+    }
+
+    public final Map<Transition, ShapeImpl> getTransitionMapForRead() {
+        return transitionMap != null ? transitionMap : Collections.<Transition, ShapeImpl> emptyMap();
+    }
+
+    private Map<Transition, ShapeImpl> getTransitionMapForWrite() {
+        if (transitionMap != null) {
+            return transitionMap;
+        } else {
+            invalidateLeafAssumption();
+            return transitionMap = new HashMap<>();
+        }
+    }
+
+    public final PropertyMap getPropertyMap() {
+        return propertyMap;
+    }
+
+    /**
+     * Add a new property in the map, yielding a new or cached Shape object.
+     *
+     * @param property the property to add
+     * @return the new Shape
+     */
+    @TruffleBoundary
+    @Override
+    public ShapeImpl addProperty(Property property) {
+        assert isValid();
+        ShapeImpl nextShape = addPropertyInternal(property);
+        objectType.onPropertyAdded(property, this, nextShape);
+        return nextShape;
+    }
+
+    /**
+     * Add a new property in the map, yielding a new or cached Shape object.
+     *
+     * In contrast to {@link ShapeImpl#addProperty(Property)}, this method does not care about
+     * obsolete shapes.
+     *
+     * @see #addProperty(Property)
+     */
+    private ShapeImpl addPropertyInternal(Property prop) {
+        CompilerAsserts.neverPartOfCompilation();
+        assert prop.isShadow() || !(this.hasProperty(prop.getKey())) : "duplicate property";
+        assert !getPropertyListInternal(false).contains(prop);
+        // invalidatePropertyAssumption(prop.getName());
+
+        Transition.AddTransition key = new Transition.AddTransition(prop);
+        Map<Transition, ShapeImpl> transitionMapForRead = this.getTransitionMapForRead();
+        ShapeImpl cachedShape = transitionMapForRead.get(key);
+        if (cachedShape != null) { // Shape already exists?
+            shapeCacheHitCount.inc();
+            return (ShapeImpl) layout.getStrategy().returnCached(cachedShape);
+        }
+        shapeCacheMissCount.inc();
+
+        ShapeImpl oldShape = (ShapeImpl) layout.getStrategy().ensureSpace(this, prop.getLocation());
+
+        ShapeImpl newShape = makeShapeWithAddedProperty(oldShape, key);
+        oldShape.addTransition(key, newShape);
+        return newShape;
+    }
+
+    protected ShapeImpl cloneRoot(ShapeImpl from, Object newSharedData) {
+        return createShape(from.layout, newSharedData, null, from.objectType, from.propertyMap, from.allocator(), from.id);
+    }
+
+    /**
+     * Create a separate clone of a shape.
+     *
+     * @param newParent the cloned parent shape
+     */
+    protected final ShapeImpl cloneOnto(ShapeImpl newParent) {
+        ShapeImpl from = this;
+        ShapeImpl newShape = createShape(newParent.layout, newParent.sharedData, newParent, from.objectType, from.propertyMap, from.allocator(), newParent.id);
+
+        shapeCloneCount.inc();
+
+        // (aw) need to have this transition for obsolescence
+        newParent.addTransition(getTransitionFromParent(), newShape);
+        return newShape;
+    }
+
+    private Transition getTransitionFromParent() {
+        for (Map.Entry<Transition, ShapeImpl> property : parent.getTransitionMapForRead().entrySet()) {
+            if (property.getValue() == this) {
+                return property.getKey();
+            }
+        }
+        throw new NoSuchElementException();
+    }
+
+    /**
+     * Create a new shape that adds a property to the parent shape.
+     */
+    private static ShapeImpl makeShapeWithAddedProperty(ShapeImpl parent, AddTransition addTransition) {
+        Property addend = addTransition.getProperty();
+        BaseAllocator allocator = parent.allocator().addLocation(addend.getLocation());
+
+        PropertyMap newPropertyMap = parent.propertyMap.putCopy(addend);
+
+        ShapeImpl newShape = parent.createShape(parent.layout, parent.sharedData, parent, parent.objectType, newPropertyMap, allocator, parent.id);
+        assert ((LocationImpl) addend.getLocation()).primitiveArrayCount() == 0 || newShape.hasPrimitiveArray;
+        assert newShape.depth == allocator.depth;
+        return newShape;
+    }
+
+    /**
+     * Create a new shape that reserves the primitive extension array field.
+     */
+    private static ShapeImpl makeShapeWithPrimitiveExtensionArray(ShapeImpl parent) {
+        assert parent.getLayout().hasPrimitiveExtensionArray();
+        assert !parent.hasPrimitiveArray();
+        BaseAllocator allocator = parent.allocator().addLocation(parent.getLayout().getPrimitiveArrayLocation());
+        ShapeImpl newShape = parent.createShape(parent.layout, parent.sharedData, parent, parent.objectType, parent.propertyMap, allocator, parent.id);
+        assert newShape.hasPrimitiveArray();
+        assert newShape.depth == allocator.depth;
+        return newShape;
+    }
+
+    private ShapeImpl addPrimitiveExtensionArray() {
+        assert layout.hasPrimitiveExtensionArray() && !hasPrimitiveArray();
+        Transition key = new Transition.ReservePrimitiveArrayTransition();
+        ShapeImpl cachedShape = this.getTransitionMapForRead().get(key);
+        if (cachedShape != null) {
+            shapeCacheHitCount.inc();
+            return (ShapeImpl) layout.getStrategy().returnCached(cachedShape);
+        }
+        shapeCacheMissCount.inc();
+
+        ShapeImpl oldShape = (ShapeImpl) layout.getStrategy().ensureSpace(this, layout.getPrimitiveArrayLocation());
+        ShapeImpl newShape = makeShapeWithPrimitiveExtensionArray(oldShape);
+        oldShape.addTransition(key, newShape);
+        return newShape;
+    }
+
+    /**
+     * Are these two shapes related, i.e. do they have the same root?
+     *
+     * @param other Shape to compare to
+     * @return true if one shape is an upcast of the other, or the Shapes are equal
+     */
+    @Override
+    public boolean isRelated(Shape other) {
+        if (this == other) {
+            return true;
+        }
+        if (this.getRoot() == getRoot()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get a list of all properties that this Shape stores.
+     *
+     * @return list of properties
+     */
+    @TruffleBoundary
+    @Override
+    public final List<Property> getPropertyList(Pred<Property> filter) {
+        LinkedList<Property> props = new LinkedList<>();
+        next: for (ShapeImpl current = this; current != getRoot(); current = current.parent) {
+            Property currentProperty = current.getLastProperty();
+            if (!currentProperty.isHidden() && filter.test(currentProperty)) {
+                if (currentProperty.getLocation() instanceof DeclaredLocation) {
+                    for (Iterator<Property> iter = props.iterator(); iter.hasNext();) {
+                        Property other = iter.next();
+                        if (other.isShadow() && other.getKey().equals(currentProperty.getKey())) {
+                            iter.remove();
+                            props.addFirst(other);
+                            continue next;
+                        }
+                    }
+                }
+                props.addFirst(currentProperty);
+            }
+        }
+        return props;
+    }
+
+    @Override
+    public final List<Property> getPropertyList() {
+        return getPropertyList(ALL);
+    }
+
+    /**
+     * Returns all (also hidden) Property objects in this shape.
+     *
+     * @param ascending desired order
+     */
+    @TruffleBoundary
+    @Override
+    public final List<Property> getPropertyListInternal(boolean ascending) {
+        LinkedList<Property> props = new LinkedList<>();
+        for (ShapeImpl current = this; current != getRoot(); current = current.parent) {
+            if (ascending) {
+                props.addFirst(current.getLastProperty());
+            } else {
+                props.add(current.getLastProperty());
+            }
+        }
+        return props;
+    }
+
+    /**
+     * Get a list of all (visible) property names in insertion order.
+     *
+     * @return list of property names
+     */
+    @TruffleBoundary
+    @Override
+    public final List<Object> getKeyList(Pred<Property> filter) {
+        LinkedList<Object> keys = new LinkedList<>();
+        for (ShapeImpl current = this; current != getRoot(); current = current.parent) {
+            Property currentProperty = current.getLastProperty();
+            if (!currentProperty.isHidden() && filter.test(currentProperty) && !currentProperty.isShadow()) {
+                keys.addFirst(currentProperty.getKey());
+            }
+        }
+        return keys;
+    }
+
+    @Override
+    public final List<Object> getKeyList() {
+        return getKeyList(ALL);
+    }
+
+    @Override
+    public Iterable<Object> getKeys() {
+        return getKeyList();
+    }
+
+    public final void setTypeTransition(ShapeImpl successorShape, Property before, Property after) {
+        getTransitionMapForWrite().put(new Transition.TypeTransition(before, after), successorShape);
+    }
+
+    private static Assumption createValidAssumption() {
+        return Truffle.getRuntime().createAssumption("valid shape");
+    }
+
+    @Override
+    public final Assumption getValidAssumption() {
+        return validAssumption;
+    }
+
+    @Override
+    public final boolean isValid() {
+        return getValidAssumption().isValid();
+    }
+
+    private static Assumption createLeafAssumption() {
+        return Truffle.getRuntime().createAssumption("leaf shape");
+    }
+
+    @Override
+    public final Assumption getLeafAssumption() {
+        return leafAssumption;
+    }
+
+    @Override
+    public final boolean isLeaf() {
+        return getLeafAssumption().isValid();
+    }
+
+    private void invalidateLeafAssumption() {
+        getLeafAssumption().invalidate();
+    }
+
+    @Override
+    public String toString() {
+        return toStringLimit(Integer.MAX_VALUE);
+    }
+
+    @TruffleBoundary
+    public String toStringLimit(int limit) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('@');
+        sb.append(Integer.toHexString(hashCode()));
+        if (!isValid()) {
+            sb.append('!');
+        }
+
+        sb.append("{");
+        for (Iterator<Property> iterator = propertyMap.reverseOrderValues().iterator(); iterator.hasNext();) {
+            Property p = iterator.next();
+            sb.append(p);
+            if (iterator.hasNext()) {
+                sb.append(", ");
+            }
+            if (sb.length() >= limit) {
+                sb.append("...");
+                break;
+            }
+            sb.append("\n");
+        }
+        sb.append("}");
+
+        return sb.toString();
+    }
+
+    @Override
+    public final ShapeImpl getParent() {
+        return parent;
+    }
+
+    public final int getDepth() {
+        return depth;
+    }
+
+    @Override
+    public final boolean hasProperty(Object name) {
+        return getProperty(name) != null;
+    }
+
+    @TruffleBoundary
+    @Override
+    public final ShapeImpl removeProperty(Property prop) {
+        RemoveTransition transition = new RemoveTransition(prop);
+        ShapeImpl cachedShape = getTransitionMapForRead().get(transition);
+        if (cachedShape != null) {
+            return (ShapeImpl) layout.getStrategy().returnCached(cachedShape);
+        }
+
+        ShapeImpl shape = getShapeFromProperty(prop);
+        if (shape != null) {
+            List<Property> properties = new ArrayList<>();
+            ShapeImpl current = this;
+            while (current != shape) {
+                properties.add(current.getLastProperty());
+                current = current.parent;
+            }
+            ShapeImpl newShape = shape.parent;
+            for (ListIterator<Property> iterator = properties.listIterator(properties.size()); iterator.hasPrevious();) {
+                Property previous = iterator.previous();
+                newShape = newShape.append(previous);
+            }
+
+            getTransitionMapForWrite().put(transition, newShape);
+            return newShape;
+        } else {
+            return null;
+        }
+    }
+
+    @TruffleBoundary
+    @Override
+    public final ShapeImpl append(Property oldProperty) {
+        return addProperty(oldProperty.relocate(allocator().moveLocation(oldProperty.getLocation())));
+    }
+
+    @Override
+    public final BaseAllocator allocator() {
+        return layout.getStrategy().createAllocator(this);
+    }
+
+    /**
+     * Duplicate shape exchanging existing property with new property.
+     */
+    @Override
+    public final ShapeImpl replaceProperty(Property oldProperty, Property newProp) {
+        ShapeImpl top = this;
+        List<Property> propertyList = new ArrayList<>();
+        boolean found = false;
+        while (top != getRoot() && !found) {
+            Property prop = top.getLastProperty();
+            propertyList.add(prop);
+            if (prop.getKey().equals(newProp.getKey())) {
+                found = true;
+            }
+            top = top.parent;
+        }
+        ShapeImpl newShape = top;
+        for (ListIterator<Property> iterator = propertyList.listIterator(propertyList.size()); iterator.hasPrevious();) {
+            Property prop = iterator.previous();
+            if (prop.getKey().equals(newProp.getKey())) {
+                newShape = newShape.addProperty(newProp);
+            } else {
+                newShape = newShape.addProperty(prop);
+            }
+        }
+        return newShape;
+    }
+
+    /**
+     * Find lowest common ancestor of two related shapes.
+     */
+    public static ShapeImpl findCommonAncestor(ShapeImpl left, ShapeImpl right) {
+        if (!left.isRelated(right)) {
+            throw new IllegalArgumentException("shapes must have the same root");
+        } else if (left == right) {
+            return left;
+        }
+        int leftLength = left.depth;
+        int rightLength = right.depth;
+        ShapeImpl leftPtr = left;
+        ShapeImpl rightPtr = right;
+        while (leftLength > rightLength) {
+            leftPtr = leftPtr.parent;
+            leftLength--;
+        }
+        while (rightLength > leftLength) {
+            rightPtr = rightPtr.parent;
+            rightLength--;
+        }
+        while (leftPtr != rightPtr) {
+            leftPtr = leftPtr.parent;
+            rightPtr = rightPtr.parent;
+        }
+        return leftPtr;
+    }
+
+    /**
+     * For copying over properties after exchanging the prototype of an object.
+     */
+    @TruffleBoundary
+    @Override
+    public final ShapeImpl copyOverPropertiesInternal(Shape destination) {
+        assert ((ShapeImpl) destination).getDepth() == 0;
+        List<Property> properties = this.getPropertyListInternal(true);
+        ShapeImpl newShape = ((ShapeImpl) destination).addPropertiesInternal(properties);
+        return newShape;
+    }
+
+    private ShapeImpl addPropertiesInternal(List<Property> properties) {
+        ShapeImpl newShape = this;
+        for (Property p : properties) {
+            newShape = newShape.addPropertyInternal(p);
+        }
+        return newShape;
+    }
+
+    /**
+     * NB: this is not an accurate property count.
+     */
+    @Override
+    public final int getPropertyCount() {
+        return propertyCount;
+    }
+
+    /**
+     * Find difference between two shapes.
+     *
+     * @see ObjectStorageOptions#TraceReshape
+     */
+    public static List<Property> diff(Shape oldShape, Shape newShape) {
+        List<Property> oldList = oldShape.getPropertyListInternal(false);
+        List<Property> newList = newShape.getPropertyListInternal(false);
+
+        List<Property> diff = new ArrayList<>(oldList);
+        diff.addAll(newList);
+        List<Property> intersection = new ArrayList<>(oldList);
+        intersection.retainAll(newList);
+        diff.removeAll(intersection);
+        return diff;
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @Override
+    public ShapeImpl getRoot() {
+        return root;
+    }
+
+    @Override
+    public final boolean check(DynamicObject subject) {
+        return subject.getShape() == this;
+    }
+
+    @Override
+    public final LayoutImpl getLayout() {
+        return layout;
+    }
+
+    @Override
+    public final Object getData() {
+        return extraData;
+    }
+
+    @Override
+    public final Object getSharedData() {
+        return sharedData;
+    }
+
+    @TruffleBoundary
+    @Override
+    public final boolean hasTransitionWithKey(Object key) {
+        for (Transition transition : getTransitionMapForRead().keySet()) {
+            if (transition instanceof PropertyTransition) {
+                if (((PropertyTransition) transition).getProperty().getKey().equals(key)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Clone off a separate shape with new shared data.
+     */
+    @TruffleBoundary
+    @Override
+    public final ShapeImpl createSeparateShape(Object newSharedData) {
+        if (parent == null) {
+            return cloneRoot(this, newSharedData);
+        } else {
+            return this.cloneOnto(parent.createSeparateShape(newSharedData));
+        }
+    }
+
+    @Override
+    @TruffleBoundary
+    public final ShapeImpl changeType(ObjectType newOps) {
+        OperationsTransition transition = new OperationsTransition(newOps);
+        ShapeImpl cachedShape = getTransitionMapForRead().get(transition);
+        if (cachedShape != null) {
+            return cachedShape;
+        } else {
+            ShapeImpl newShape = createShape(layout, extraData, this, newOps, propertyMap, allocator(), id);
+            addTransition(transition, newShape);
+            return newShape;
+        }
+    }
+
+    @Override
+    public final Shape reservePrimitiveExtensionArray() {
+        if (layout.hasPrimitiveExtensionArray() && !hasPrimitiveArray()) {
+            return addPrimitiveExtensionArray();
+        }
+        return this;
+    }
+
+    @Override
+    public final Iterable<Property> getProperties() {
+        if (getPropertyCount() != 0 && propertyArray == null) {
+            CompilerDirectives.transferToInterpreter();
+            propertyArray = createPropertiesArray();
+        }
+        return new Iterable<Property>() {
+            public Iterator<Property> iterator() {
+                return new Iterator<Property>() {
+                    private int cursor;
+
+                    public boolean hasNext() {
+                        return cursor < getPropertyCount();
+                    }
+
+                    public Property next() {
+                        if (hasNext()) {
+                            return propertyArray[cursor++];
+                        }
+                        throw new NoSuchElementException();
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
+    }
+
+    private Property[] createPropertiesArray() {
+        propertyListAllocCount.inc();
+        Property[] propertiesArray = new Property[getPropertyCount()];
+        List<Property> ownProperties = getPropertyList(ALL);
+        assert ownProperties.size() == getPropertyCount();
+        for (int i = 0; i < getPropertyCount(); i++) {
+            propertiesArray[i] = ownProperties.get(i);
+        }
+        return propertiesArray;
+    }
+
+    @Override
+    public final DynamicObject newInstance() {
+        return layout.newInstance(this);
+    }
+
+    @Override
+    public final DynamicObjectFactory createFactory() {
+        final List<Property> properties = getPropertyListInternal(true);
+        for (Iterator<Property> iterator = properties.iterator(); iterator.hasNext();) {
+            Property property = iterator.next();
+            // skip non-instance fields
+            assert property.getLocation() != layout.getPrimitiveArrayLocation();
+            if (property.getLocation() instanceof ValueLocation) {
+                iterator.remove();
+            }
+        }
+
+        return new DynamicObjectFactory() {
+            @CompilationFinal private final PropertyImpl[] instanceFields = properties.toArray(new PropertyImpl[properties.size()]);
+
+            @ExplodeLoop
+            public DynamicObject newInstance(Object... initialValues) {
+                DynamicObject store = ShapeImpl.this.newInstance();
+                for (int i = 0; i < instanceFields.length; i++) {
+                    instanceFields[i].setInternal(store, initialValues[i]);
+                }
+                return store;
+            }
+
+            public Shape getShape() {
+                return ShapeImpl.this;
+            }
+        };
+    }
+
+    @Override
+    public Object getMutex() {
+        return getRoot();
+    }
+
+    @Override
+    public Shape tryMerge(Shape other) {
+        return null;
+    }
+
+    public abstract static class BaseAllocator extends Allocator {
+        protected final LayoutImpl layout;
+        protected int objectArraySize;
+        protected int objectFieldSize;
+        protected int primitiveFieldSize;
+        protected int primitiveArraySize;
+        protected boolean hasPrimitiveArray;
+        protected int depth;
+
+        protected BaseAllocator(LayoutImpl layout) {
+            this.layout = layout;
+        }
+
+        protected BaseAllocator(ShapeImpl shape) {
+            this(shape.getLayout());
+            this.objectArraySize = shape.objectArraySize;
+            this.objectFieldSize = shape.objectFieldSize;
+            this.primitiveFieldSize = shape.primitiveFieldSize;
+            this.primitiveArraySize = shape.primitiveArraySize;
+            this.hasPrimitiveArray = shape.hasPrimitiveArray;
+            this.depth = shape.depth;
+        }
+
+        protected abstract Location moveLocation(Location oldLocation);
+
+        protected abstract Location newObjectLocation(boolean useFinal, boolean nonNull);
+
+        protected abstract Location newTypedObjectLocation(boolean useFinal, Class<?> type, boolean nonNull);
+
+        protected abstract Location newIntLocation(boolean useFinal);
+
+        protected abstract Location newDoubleLocation(boolean useFinal);
+
+        protected abstract Location newLongLocation(boolean useFinal);
+
+        protected abstract Location newBooleanLocation(boolean useFinal);
+
+        @Override
+        public final Location constantLocation(Object value) {
+            return new ConstantLocation(value);
+        }
+
+        @Override
+        protected Location locationForValue(Object value, boolean useFinal, boolean nonNull) {
+            if (value instanceof Integer) {
+                return newIntLocation(useFinal);
+            } else if (value instanceof Double) {
+                return newDoubleLocation(useFinal);
+            } else if (value instanceof Long) {
+                return newLongLocation(useFinal);
+            } else if (value instanceof Boolean) {
+                return newBooleanLocation(useFinal);
+            } else if (ObjectStorageOptions.TypedObjectLocations && value != null) {
+                return newTypedObjectLocation(useFinal, value.getClass(), nonNull);
+            }
+            return newObjectLocation(useFinal, nonNull && value != null);
+        }
+
+        protected abstract Location locationForValueUpcast(Object value, Location oldLocation);
+
+        @Override
+        protected Location locationForType(Class<?> type, boolean useFinal, boolean nonNull) {
+            if (type == int.class) {
+                return newIntLocation(useFinal);
+            } else if (type == double.class) {
+                return newDoubleLocation(useFinal);
+            } else if (type == long.class) {
+                return newLongLocation(useFinal);
+            } else if (type == boolean.class) {
+                return newBooleanLocation(useFinal);
+            } else if (ObjectStorageOptions.TypedObjectLocations && type != null && type != Object.class) {
+                assert !type.isPrimitive() : "unsupported primitive type";
+                return newTypedObjectLocation(useFinal, type, nonNull);
+            }
+            return newObjectLocation(useFinal, nonNull);
+        }
+
+        protected Location newDualLocation(Class<?> type) {
+            return new DualLocation((InternalLongLocation) newLongLocation(false), (ObjectLocation) newObjectLocation(false, false), layout, type);
+        }
+
+        protected DualLocation newDualLocationForValue(Object value) {
+            Class<?> initialType = null;
+            if (value instanceof Integer) {
+                initialType = int.class;
+            } else if (value instanceof Double) {
+                initialType = double.class;
+            } else if (value instanceof Boolean) {
+                initialType = boolean.class;
+            } else {
+                initialType = Object.class;
+            }
+            return new DualLocation((InternalLongLocation) newLongLocation(false), (ObjectLocation) newObjectLocation(false, false), layout, initialType);
+        }
+
+        protected Location newDeclaredDualLocation(Object value) {
+            return new DeclaredDualLocation((InternalLongLocation) newLongLocation(false), (ObjectLocation) newObjectLocation(false, false), value, layout);
+        }
+
+        protected <T extends Location> T advance(T location0) {
+            if (location0 instanceof LocationImpl) {
+                LocationImpl location = (LocationImpl) location0;
+                objectArraySize += location.objectArrayCount();
+                primitiveArraySize += location.primitiveArrayCount();
+                primitiveFieldSize += location.primitiveFieldCount();
+                if (layout.hasPrimitiveExtensionArray()) {
+                    hasPrimitiveArray |= location == layout.getPrimitiveArrayLocation() || location.primitiveArrayCount() != 0;
+                    objectFieldSize = location == layout.getPrimitiveArrayLocation() ? objectFieldSize : Math.max(objectFieldSize, layout.objectFieldIndex(location) + location.objectFieldCount());
+                } else {
+                    assert !hasPrimitiveArray && location.primitiveArrayCount() == 0;
+                    objectFieldSize += location.objectFieldCount();
+                }
+            }
+            depth++;
+            return location0;
+        }
+
+        @Override
+        public BaseAllocator addLocation(Location location) {
+            advance(location);
+            return this;
+        }
+    }
+
+    private static void debugRegisterShape(ShapeImpl newShape) {
+        if (ObjectStorageOptions.DumpShapes) {
+            Debug.registerShape(newShape);
+        }
+    }
+
+    /**
+     * Match all filter.
+     */
+    public static final Pred<Property> ALL = new Pred<Property>() {
+        public boolean test(Property t) {
+            return true;
+        }
+    };
+
+    private static final DebugCounter shapeCount = DebugCounter.create("Shapes allocated total");
+    private static final DebugCounter shapeCloneCount = DebugCounter.create("Shapes allocated cloned");
+    private static final DebugCounter shapeCacheHitCount = DebugCounter.create("Shape cache hits");
+    private static final DebugCounter shapeCacheMissCount = DebugCounter.create("Shape cache misses");
+
+    protected static final DebugCounter propertyListAllocCount = DebugCounter.create("Property lists allocated");
+    protected static final DebugCounter propertyListShareCount = DebugCounter.create("Property lists shared");
+}