All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.object.DynamicObjectLibraryImpl Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.object;

import static com.oracle.truffle.object.LayoutImpl.ACCESS;
import static com.oracle.truffle.object.LocationImpl.alwaysValidAssumption;
import static com.oracle.truffle.object.LocationImpl.expectDouble;
import static com.oracle.truffle.object.LocationImpl.expectInteger;
import static com.oracle.truffle.object.LocationImpl.expectLong;
import static com.oracle.truffle.object.LocationImpl.neverValidAssumption;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Lock;

import org.graalvm.collections.EconomicSet;

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.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.object.FinalLocationException;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.IncompatibleLocationException;
import com.oracle.truffle.api.object.Location;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;

/**
 * The implementation of {@link DynamicObjectLibrary}.
 */
@ExportLibrary(value = DynamicObjectLibrary.class, receiverType = DynamicObject.class, priority = 10, transitionLimit = "5")
abstract class DynamicObjectLibraryImpl {

    static final int KEY_LIMIT = 3;

    static boolean keyEquals(Object cachedKey, Object key) {
        if (cachedKey instanceof String) {
            return cachedKey == key || (key instanceof String && ((String) cachedKey).equals(key));
        } else if (cachedKey instanceof HiddenKey) {
            return key == cachedKey;
        } else if (cachedKey instanceof Long) {
            return key instanceof Long && ((Long) cachedKey).equals(key);
        } else {
            return cachedKey == key || keyEqualsBoundary(cachedKey, key);
        }
    }

    @TruffleBoundary(allowInlining = true)
    static boolean keyEqualsBoundary(Object cachedKey, Object key) {
        return Objects.equals(cachedKey, key);
    }

    @ExportMessage
    static boolean accepts(DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return object.getShape() == cachedShape;
    }

    @ExportMessage
    static Shape getShape(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return cachedShape;
    }

    @ExportMessage
    static Object getOrDefault(DynamicObject object, Object key, Object defaultValue,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.getOrDefault(object, cachedShape, key, defaultValue);
    }

    @ExportMessage
    static int getIntOrDefault(DynamicObject object, Object key, Object defaultValue,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) throws UnexpectedResultException {
        return keyCache.getIntOrDefault(object, cachedShape, key, defaultValue);
    }

    @ExportMessage
    static double getDoubleOrDefault(DynamicObject object, Object key, Object defaultValue,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) throws UnexpectedResultException {
        return keyCache.getDoubleOrDefault(object, cachedShape, key, defaultValue);
    }

    @ExportMessage
    static long getLongOrDefault(DynamicObject object, Object key, Object defaultValue,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) throws UnexpectedResultException {
        return keyCache.getLongOrDefault(object, cachedShape, key, defaultValue);
    }

    @ExportMessage
    static boolean containsKey(DynamicObject object, Object key,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.containsKey(object, cachedShape, key);
    }

    @ExportMessage
    static void put(DynamicObject object, Object key, Object value,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.put(object, cachedShape, key, value, Flags.DEFAULT);
    }

    @ExportMessage
    static void putInt(DynamicObject object, Object key, int value,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.putInt(object, cachedShape, key, value, Flags.DEFAULT);
    }

    @ExportMessage
    static void putLong(DynamicObject object, Object key, long value,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.putLong(object, cachedShape, key, value, Flags.DEFAULT);
    }

    @ExportMessage
    static void putDouble(DynamicObject object, Object key, double value,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.putDouble(object, cachedShape, key, value, Flags.DEFAULT);
    }

    @ExportMessage
    static boolean putIfPresent(DynamicObject object, Object key, Object value,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.put(object, cachedShape, key, value, Flags.SET_EXISTING);
    }

    @ExportMessage
    static void putWithFlags(DynamicObject object, Object key, Object value, int flags,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.put(object, cachedShape, key, value, Flags.propertyFlagsToPutFlags(flags) | Flags.UPDATE_FLAGS);
    }

    @ExportMessage
    static void putConstant(DynamicObject object, Object key, Object value, int flags,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        keyCache.put(object, cachedShape, key, value, Flags.propertyFlagsToPutFlags(flags) | Flags.UPDATE_FLAGS | Flags.CONST);
    }

    @ExportMessage
    public static Property getProperty(@SuppressWarnings("unused") DynamicObject object, Object key,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.getProperty(object, cachedShape, key);
    }

    @ExportMessage
    public static boolean setPropertyFlags(DynamicObject object, Object key, int propertyFlags,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.setPropertyFlags(object, cachedShape, key, propertyFlags);
    }

    @TruffleBoundary
    static ShapeImpl changePropertyFlags(ShapeImpl shape, PropertyImpl cachedProperty, int propertyFlags) {
        return shape.replaceProperty(cachedProperty, cachedProperty.copyWithFlags(propertyFlags));
    }

    @ExportMessage
    public static boolean removeKey(DynamicObject object, Object key,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Shared("keyCache") @Cached("create(object.getShape(), key)") KeyCacheNode keyCache) {
        return keyCache.removeKey(object, cachedShape, key);
    }

    @ExportMessage
    public static Object getDynamicType(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return cachedShape.getDynamicType();
    }

    @ExportMessage
    public static boolean setDynamicType(DynamicObject object, @SuppressWarnings("unused") Object objectType,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Cached SetDynamicTypeNode setCache) {
        return setCache.execute(object, cachedShape, objectType);
    }

    @ExportMessage
    public static int getShapeFlags(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return cachedShape.getFlags();
    }

    @ExportMessage
    public static boolean setShapeFlags(DynamicObject object, @SuppressWarnings("unused") int flags,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Cached SetFlagsNode setCache) {
        return setCache.execute(object, cachedShape, flags);
    }

    @ExportMessage
    public static boolean isShared(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return cachedShape.isShared();
    }

    @ExportMessage
    public static void markShared(DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Cached MakeSharedNode setCache) {
        setCache.execute(object, cachedShape);
    }

    @ExportMessage
    public static boolean updateShape(DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        if (cachedShape.isValid()) {
            return false;
        } else {
            return updateShapeImpl(object);
        }
    }

    @TruffleBoundary
    static boolean updateShapeImpl(DynamicObject object) {
        return ((ShapeImpl) object.getShape()).getLayout().getStrategy().updateShape(object);
    }

    @ExportMessage
    public static boolean resetShape(DynamicObject object, Shape otherShape,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape,
                    @Cached ResetShapeNode setCache) {
        return setCache.execute(object, cachedShape, otherShape);
    }

    @ExportMessage
    public static Object[] getKeyArray(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return ((ShapeImpl) cachedShape).getKeyArray();
    }

    @ExportMessage
    public static Property[] getPropertyArray(@SuppressWarnings("unused") DynamicObject object,
                    @Shared("cachedShape") @Cached(value = "object.getShape()", allowUncached = true) Shape cachedShape) {
        return ((ShapeImpl) cachedShape).getPropertyArray();
    }

    private static LocationImpl getLocation(Property existing) {
        return (LocationImpl) existing.getLocation();
    }

    @TruffleBoundary
    protected static boolean putUncached(DynamicObject object, Object key, Object value, long putFlags) {
        Shape s = ACCESS.getShape(object);
        Property existingProperty = s.getProperty(key);
        if (existingProperty == null && Flags.isSetExisting(putFlags)) {
            return false;
        }
        if (existingProperty != null && !Flags.isUpdateFlags(putFlags) && existingProperty.getLocation().canSet(value)) {
            try {
                getLocation(existingProperty).set(object, value, false);
            } catch (IncompatibleLocationException | FinalLocationException e) {
                throw shouldNotHappen(e);
            }
            return true;
        } else {
            return putUncachedSlow(object, key, value, putFlags);
        }
    }

    private static boolean putUncachedSlow(DynamicObject object, Object key, Object value, long putFlags) {
        CompilerAsserts.neverPartOfCompilation();
        updateShapeImpl(object);
        ShapeImpl oldShape;
        Property existingProperty;
        Shape newShape;
        Property property;
        do {
            oldShape = (ShapeImpl) ACCESS.getShape(object);
            existingProperty = oldShape.getProperty(key);
            if (existingProperty == null) {
                if (Flags.isSetExisting(putFlags)) {
                    return false;
                } else {
                    LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                    newShape = strategy.defineProperty(oldShape, key, value, Flags.getPropertyFlags(putFlags), null, existingProperty, putFlags);
                    property = newShape.getProperty(key);
                }
            } else if (Flags.isUpdateFlags(putFlags) && Flags.getPropertyFlags(putFlags) != existingProperty.getFlags()) {
                LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                newShape = strategy.defineProperty(oldShape, key, value, Flags.getPropertyFlags(putFlags), null, existingProperty, putFlags);
                property = newShape.getProperty(key);
            } else {
                if (existingProperty.getLocation().canSet(value)) {
                    newShape = oldShape;
                    property = existingProperty;
                } else {
                    LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                    newShape = strategy.defineProperty(oldShape, key, value, existingProperty.getFlags(), null, existingProperty, putFlags);
                    property = newShape.getProperty(key);
                }
            }
        } while (updateShapeImpl(object));

        assert ACCESS.getShape(object) == oldShape;
        if (oldShape != newShape) {
            ACCESS.growAndSetShape(object, oldShape, newShape);
            try {
                getLocation(property).setInternal(object, value, false);
            } catch (IncompatibleLocationException e) {
                throw shouldNotHappen(e);
            }
            updateShapeImpl(object);
        } else {
            try {
                getLocation(property).set(object, value, false);
            } catch (IncompatibleLocationException | FinalLocationException e) {
                throw shouldNotHappen(e);
            }
        }
        return true;
    }

    static RemovePlan prepareRemove(ShapeImpl shapeBefore, ShapeImpl shapeAfter) {
        LayoutStrategy strategy = shapeBefore.getLayout().getStrategy();
        List moves = new ArrayList<>();
        boolean canMoveInPlace = shapeAfter.getObjectArrayCapacity() <= shapeBefore.getObjectArrayCapacity() &&
                        shapeAfter.getPrimitiveArrayCapacity() <= shapeBefore.getPrimitiveArrayCapacity();
        for (ListIterator iterator = shapeAfter.getPropertyListInternal(false).listIterator(); iterator.hasNext();) {
            Property to = iterator.next();
            Property from = shapeBefore.getProperty(to.getKey());
            LocationImpl fromLoc = getLocation(from);
            LocationImpl toLoc = getLocation(to);
            if (LocationImpl.isSameLocation(toLoc, fromLoc)) {
                continue;
            }
            assert !toLoc.isValue();
            int fromOrd = strategy.getLocationOrdinal(fromLoc);
            int toOrd = strategy.getLocationOrdinal(toLoc);
            Move move = new Move(fromLoc, toLoc, fromOrd, toOrd);
            canMoveInPlace = canMoveInPlace && fromOrd > toOrd;
            moves.add(move);
        }
        if (canMoveInPlace) {
            if (!isSorted(moves)) {
                Collections.sort(moves);
            }
        }
        return new RemovePlan(moves.toArray(new Move[0]), canMoveInPlace, shapeBefore, shapeAfter);
    }

    private static boolean isSorted(List moves) {
        for (int i = 1; i < moves.size(); i++) {
            Move m1 = moves.get(i - 1);
            Move m2 = moves.get(i);
            if (m1.compareTo(m2) > 0) {
                return false;
            }
        }
        return true;
    }

    private static final class Move implements Comparable {
        private final LocationImpl fromLoc;
        private final LocationImpl toLoc;
        private final int fromOrd;
        private final int toOrd;

        Move(LocationImpl fromLoc, LocationImpl toLoc, int fromOrd, int toOrd) {
            this.fromLoc = fromLoc;
            this.toLoc = toLoc;
            this.fromOrd = fromOrd;
            this.toOrd = toOrd;
        }

        void perform(DynamicObject obj, boolean clear) {
            performSet(obj, performGet(obj), clear);
        }

        Object performGet(DynamicObject obj) {
            return fromLoc.get(obj, false);
        }

        void performSet(DynamicObject obj, Object value, boolean clear) {
            try {
                toLoc.setInternal(obj, value, false);
            } catch (IncompatibleLocationException e) {
                throw shouldNotHappen(e);
            }
            if (clear) {
                clear(obj);
            }
        }

        void clear(DynamicObject obj) {
            if (fromLoc instanceof CoreLocations.ObjectLocation) {
                // clear location to avoid memory leak
                try {
                    fromLoc.setInternal(obj, null, false);
                } catch (IncompatibleLocationException e) {
                    throw shouldNotHappen(e);
                }
            }
        }

        @Override
        public String toString() {
            CompilerAsserts.neverPartOfCompilation();
            return fromLoc + " => " + toLoc;
        }

        @Override
        public int compareTo(Move other) {
            int order = Integer.compare(fromOrd, other.fromOrd);
            assert order == Integer.compare(toOrd, other.toOrd);
            return -order;
        }
    }

    private static final class RemovePlan {
        private static final int MAX_UNROLL = 32;

        @CompilationFinal(dimensions = 1) private final Move[] moves;
        private final boolean canMoveInPlace;
        private final Shape shapeBefore;
        private final Shape shapeAfter;

        RemovePlan(Move[] moves, boolean canMoveInPlace, Shape shapeBefore, Shape shapeAfter) {
            this.moves = moves;
            this.canMoveInPlace = canMoveInPlace;
            this.shapeBefore = shapeBefore;
            this.shapeAfter = shapeAfter;
        }

        void execute(DynamicObject object) {
            CompilerAsserts.partialEvaluationConstant(moves.length);
            if (CompilerDirectives.inCompiledCode() && moves.length <= MAX_UNROLL) {
                perform(object);
            } else {
                performBoundary(object);
            }
        }

        @ExplodeLoop
        void perform(DynamicObject object) {
            CompilerAsserts.partialEvaluationConstant(moves.length);
            if (canMoveInPlace) {
                // perform the moves in inverse order
                for (int i = moves.length - 1; i >= 0; i--) {
                    moves[i].perform(object, i == 0);
                }
                ACCESS.trimToSize(object, shapeBefore, shapeAfter);
                ACCESS.setShape(object, shapeAfter);
            } else {
                // we cannot perform the moves in place, so stash away the values
                Object[] tempValues = new Object[moves.length];
                for (int i = moves.length - 1; i >= 0; i--) {
                    tempValues[i] = moves[i].performGet(object);
                }
                ACCESS.resizeAndSetShape(object, shapeBefore, shapeAfter);
                for (int i = moves.length - 1; i >= 0; i--) {
                    moves[i].performSet(object, tempValues[i], true);
                }
            }
        }

        @TruffleBoundary
        void performBoundary(DynamicObject object) {
            perform(object);
        }
    }

    abstract static class KeyCacheNode extends Node {
        public abstract Object getOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue);

        public abstract int getIntOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException;

        public abstract long getLongOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException;

        public abstract double getDoubleOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException;

        public abstract boolean put(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags);

        public abstract boolean containsKey(DynamicObject object, Shape cachedShape, Object key);

        public abstract Property getProperty(DynamicObject object, Shape cachedShape, Object key);

        public abstract boolean setPropertyFlags(DynamicObject object, Shape cachedShape, Object key, int propertyFlags);

        public abstract boolean removeKey(DynamicObject object, Shape cachedShape, Object key);

        public boolean putInt(DynamicObject object, Shape cachedShape, Object key, int value, long putFlags) {
            return put(object, cachedShape, key, value, putFlags);
        }

        public boolean putLong(DynamicObject object, Shape cachedShape, Object key, long value, long putFlags) {
            return put(object, cachedShape, key, value, putFlags);
        }

        public boolean putDouble(DynamicObject object, Shape cachedShape, Object key, double value, long putFlags) {
            return put(object, cachedShape, key, value, putFlags);
        }

        boolean isIdentity() {
            return false;
        }

        static KeyCacheNode create(Shape cachedShape, Object key) {
            if (key == null) {
                return getUncached();
            }
            return AnyKey.create(key, cachedShape);
        }

        static KeyCacheEntry getUncached() {
            return Generic.instance();
        }
    }

    abstract static class KeyCacheEntry extends KeyCacheNode {
        @Child KeyCacheEntry next;

        KeyCacheEntry(KeyCacheEntry next) {
            this.next = next;
        }

        public boolean acceptsKey(@SuppressWarnings("unused") Object key) {
            return true;
        }
    }

    static class Generic extends KeyCacheEntry {
        private static final Generic INSTANCE = new Generic();

        Generic() {
            super(null);
        }

        static Generic instance() {
            return INSTANCE;
        }

        @Override
        public boolean isAdoptable() {
            return false;
        }

        @TruffleBoundary
        @Override
        public Object getOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) {
            Property existing = ACCESS.getShape(object).getProperty(key);
            if (existing != null) {
                return getLocation(existing).get(object, false);
            } else {
                return defaultValue;
            }
        }

        @TruffleBoundary
        @Override
        public int getIntOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            Property existing = ACCESS.getShape(object).getProperty(key);
            if (existing != null) {
                return getLocation(existing).getInt(object, false);
            } else {
                return expectInteger(defaultValue);
            }
        }

        @TruffleBoundary
        @Override
        public long getLongOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            Property existing = ACCESS.getShape(object).getProperty(key);
            if (existing != null) {
                return getLocation(existing).getLong(object, false);
            } else {
                return expectLong(defaultValue);
            }
        }

        @TruffleBoundary
        @Override
        public double getDoubleOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            Property existing = ACCESS.getShape(object).getProperty(key);
            if (existing != null) {
                return getLocation(existing).getDouble(object, false);
            } else {
                return expectDouble(defaultValue);
            }
        }

        @Override
        public boolean put(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags) {
            return putUncached(object, key, value, putFlags);
        }

        @Override
        public boolean containsKey(DynamicObject object, Shape cachedShape, Object key) {
            Property existing = getProperty(object, cachedShape, key);
            return existing != null;
        }

        @Override
        public Property getProperty(DynamicObject object, Shape cachedShape, Object key) {
            return ACCESS.getShape(object).getProperty(key);
        }

        @TruffleBoundary
        @Override
        public boolean setPropertyFlags(DynamicObject object, Shape cachedShape, Object key, int propertyFlags) {
            ShapeImpl oldShape = (ShapeImpl) ACCESS.getShape(object);
            Property existingProperty = oldShape.getProperty(key);
            if (existingProperty == null) {
                return false;
            }
            if (existingProperty.getFlags() != propertyFlags) {
                updateShapeImpl(object);
                Shape newShape = changePropertyFlags(oldShape, (PropertyImpl) existingProperty, propertyFlags);
                if (newShape != oldShape) {
                    ACCESS.setShape(object, newShape);
                    updateShapeImpl(object);
                }
            }
            return true;
        }

        @TruffleBoundary
        @Override
        public boolean removeKey(DynamicObject obj, Shape cachedShape, Object key) {
            ShapeImpl oldShape = (ShapeImpl) cachedShape;
            Property property = oldShape.getProperty(key);
            if (property == null) {
                return false;
            }

            Map archive = null;
            assert (archive = ACCESS.archive(obj)) != null;

            ShapeImpl newShape = oldShape.removeProperty(property);
            assert oldShape != newShape;
            assert ACCESS.getShape(obj) == oldShape;

            if (!oldShape.isShared()) {
                RemovePlan plan = prepareRemove(oldShape, newShape);
                plan.execute(obj);
            } else {
                ACCESS.setShape(obj, newShape);
            }

            assert ACCESS.verifyValues(obj, archive);
            return true;
        }

    }

    /**
     * Polymorphic inline cache for a limited number of distinct property keys.
     *
     * The generic case is used if the number of property keys accessed overflows the limit of the
     * polymorphic inline cache.
     */
    static class AnyKey extends KeyCacheNode {

        @Child private KeyCacheEntry keyCache;

        AnyKey(KeyCacheEntry keyCache) {
            this.keyCache = keyCache;
        }

        public static KeyCacheNode create() {
            return new AnyKey(null);
        }

        public static KeyCacheNode create(Object key, Shape cachedShape) {
            return new AnyKey(SpecificKey.create(key, cachedShape, null, true));
        }

        @ExplodeLoop
        @Override
        public Object getOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.getOrDefault(object, cachedShape, key, defaultValue);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.getOrDefault(object, cachedShape, key, defaultValue);
                }
            }
            return Generic.instance().getOrDefault(object, cachedShape, key, defaultValue);
        }

        @ExplodeLoop
        @Override
        public int getIntOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.getIntOrDefault(object, cachedShape, key, defaultValue);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.getIntOrDefault(object, cachedShape, key, defaultValue);
                }
            }
            return Generic.instance().getIntOrDefault(object, cachedShape, key, defaultValue);
        }

        @ExplodeLoop
        @Override
        public long getLongOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.getLongOrDefault(object, cachedShape, key, defaultValue);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.getLongOrDefault(object, cachedShape, key, defaultValue);
                }
            }
            return Generic.instance().getLongOrDefault(object, cachedShape, key, defaultValue);
        }

        @ExplodeLoop
        @Override
        public double getDoubleOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.getDoubleOrDefault(object, cachedShape, key, defaultValue);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.getDoubleOrDefault(object, cachedShape, key, defaultValue);
                }
            }
            return Generic.instance().getDoubleOrDefault(object, cachedShape, key, defaultValue);
        }

        @ExplodeLoop
        @Override
        public boolean put(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.put(object, cachedShape, key, value, putFlags);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.put(object, cachedShape, key, value, putFlags);
                }
            }
            return Generic.instance().put(object, cachedShape, key, value, putFlags);
        }

        @ExplodeLoop
        @Override
        public boolean containsKey(DynamicObject object, Shape cachedShape, Object key) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.containsKey(object, cachedShape, key);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.containsKey(object, cachedShape, key);
                }
            }
            return Generic.instance().containsKey(object, cachedShape, key);
        }

        @ExplodeLoop
        @Override
        public Property getProperty(DynamicObject object, Shape cachedShape, Object key) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.getProperty(object, cachedShape, key);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.getProperty(object, cachedShape, key);
                }
            }
            return Generic.instance().getProperty(object, cachedShape, key);
        }

        @ExplodeLoop
        @Override
        public boolean setPropertyFlags(DynamicObject object, Shape cachedShape, Object key, int propertyFlags) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.setPropertyFlags(object, cachedShape, key, propertyFlags);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.setPropertyFlags(object, cachedShape, key, propertyFlags);
                }
            }
            return Generic.instance().setPropertyFlags(object, cachedShape, key, propertyFlags);
        }

        @ExplodeLoop
        @Override
        public boolean removeKey(DynamicObject object, Shape cachedShape, Object key) {
            KeyCacheEntry start = keyCache;
            if (start != KeyCacheNode.getUncached()) {
                for (KeyCacheEntry c = start; c != null; c = c.next) {
                    if (c.acceptsKey(key)) {
                        return c.removeKey(object, cachedShape, key);
                    }
                }
                CompilerDirectives.transferToInterpreterAndInvalidate();
                KeyCacheNode impl = insertIntoKeyCache(key, cachedShape);
                if (impl != null) {
                    return impl.removeKey(object, cachedShape, key);
                }
            }
            return Generic.instance().removeKey(object, cachedShape, key);
        }

        private KeyCacheNode insertIntoKeyCache(Object key, Shape cachedShape) {
            CompilerAsserts.neverPartOfCompilation();
            Lock lock = getLock();
            lock.lock();
            try {
                KeyCacheEntry tail = this.keyCache;
                int cachedCount = 0;
                boolean generic = false;
                boolean useIdentity = true;

                for (KeyCacheEntry c = tail; c != null; c = c.next) {
                    if (c == KeyCacheNode.getUncached()) {
                        generic = true;
                        break;
                    } else {
                        cachedCount++;
                        if (c.acceptsKey(key)) {
                            return c;
                        }
                        if (!c.isIdentity()) {
                            useIdentity = false;
                        }
                    }
                }

                if (cachedCount > 1 && useIdentity) {
                    // if we have duplicate keys in the cache due to identity comparison,
                    // clear the cache and compare keys with equals() from now on.
                    if (hasDuplicateCacheKeys(tail, key)) {
                        tail = null;
                        cachedCount = 0;
                        useIdentity = false;
                    }
                }

                if (cachedCount >= KEY_LIMIT) {
                    generic = true;
                    this.keyCache = KeyCacheNode.getUncached();
                }
                if (generic) {
                    return null;
                }

                SpecificKey newEntry = SpecificKey.create(key, cachedShape, tail, useIdentity);
                insert(newEntry);
                this.keyCache = newEntry;
                return this;
            } finally {
                lock.unlock();
            }
        }

        private static boolean hasDuplicateCacheKeys(KeyCacheEntry tail, Object key) {
            EconomicSet keySet = EconomicSet.create();
            for (KeyCacheEntry c = tail; c != null; c = c.next) {
                if (c instanceof SpecificKey) {
                    SpecificKey cacheEntry = (SpecificKey) c;
                    if (!keySet.add(cacheEntry.cachedKey)) {
                        return true;
                    }
                }
            }
            return !keySet.add(key);
        }
    }

    abstract static class SpecificKey extends KeyCacheEntry {
        final Object cachedKey;

        @CompilationFinal MutateCacheData cache;

        SpecificKey(Object key, KeyCacheEntry next) {
            super(next);
            this.cachedKey = key;
        }

        static SpecificKey create(Object key, Shape shape, KeyCacheEntry next, boolean useIdentity) {
            if (key != null) {
                Property property = shape.getProperty(key);
                if (property != null) {
                    return useIdentity ? new SpecificKey.ExistingKeyIdentity(key, property, next) : new SpecificKey.ExistingKey(key, property, next);
                }
            }
            return useIdentity ? new SpecificKey.MissingKeyIdentity(key, next) : new SpecificKey.MissingKey(key, next);
        }

        protected final boolean assertCachedKeyAndShapeForRead(DynamicObject object, Shape cachedShape, Object key) {
            // The object's Shape might differ from cachedShape if cachedShape is shared,
            // this is fine for reads.
            assert object.getShape() == cachedShape || cachedShape.isShared();
            assert keyEquals(this.cachedKey, key);
            return true;
        }

        protected final boolean assertCachedKeyAndShapeForWrite(DynamicObject object, Shape cachedShape, Object key) {
            assert object.getShape() == cachedShape;
            assert keyEquals(this.cachedKey, key);
            return true;
        }

        @Override
        public boolean acceptsKey(Object key) {
            return keyEquals(cachedKey, key);
        }

        static class ExistingKey extends SpecificKey {
            final Property cachedProperty;

            ExistingKey(Object key, Property property, KeyCacheEntry next) {
                super(key, next);
                this.cachedProperty = property;
            }

            private static boolean guard(DynamicObject object, Shape cachedShape) {
                return object.getShape() == cachedShape;
            }

            @Override
            public Object getOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return getLocation(cachedProperty).get(object, guard(object, cachedShape));
            }

            @Override
            public int getIntOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return getLocation(cachedProperty).getInt(object, guard(object, cachedShape));
            }

            @Override
            public long getLongOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return getLocation(cachedProperty).getLong(object, guard(object, cachedShape));
            }

            @Override
            public double getDoubleOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return getLocation(cachedProperty).getDouble(object, guard(object, cachedShape));
            }

            @Override
            public boolean put(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putImpl(object, cachedShape, key, value, putFlags, cachedProperty);
            }

            @Override
            public boolean putInt(DynamicObject object, Shape cachedShape, Object key, int value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putIntImpl(object, cachedShape, key, value, putFlags, cachedProperty);
            }

            @Override
            public boolean putLong(DynamicObject object, Shape cachedShape, Object key, long value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putLongImpl(object, cachedShape, key, value, putFlags, cachedProperty);
            }

            @Override
            public boolean putDouble(DynamicObject object, Shape cachedShape, Object key, double value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putDoubleImpl(object, cachedShape, key, value, putFlags, cachedProperty);
            }

            @Override
            public boolean containsKey(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return true;
            }

            @Override
            public Property getProperty(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return cachedProperty;
            }

            @Override
            public boolean setPropertyFlags(DynamicObject object, Shape cachedShape, Object key, int propertyFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return setPropertyFlagsImpl(object, cachedShape, key, propertyFlags, cachedProperty);
            }

            @Override
            public boolean removeKey(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return removeKeyImpl(object, cachedShape, key, cachedProperty);
            }
        }

        static class MissingKey extends SpecificKey {
            MissingKey(Object key, KeyCacheEntry next) {
                super(key, next);
            }

            @Override
            public Object getOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return defaultValue;
            }

            @Override
            public boolean put(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putImpl(object, cachedShape, key, value, putFlags, null);
            }

            @Override
            public boolean putInt(DynamicObject object, Shape cachedShape, Object key, int value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putIntImpl(object, cachedShape, key, value, putFlags, null);
            }

            @Override
            public boolean putLong(DynamicObject object, Shape cachedShape, Object key, long value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putLongImpl(object, cachedShape, key, value, putFlags, null);
            }

            @Override
            public boolean putDouble(DynamicObject object, Shape cachedShape, Object key, double value, long putFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return putDoubleImpl(object, cachedShape, key, value, putFlags, null);
            }

            @Override
            public boolean containsKey(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return false;
            }

            @Override
            public Property getProperty(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForRead(object, cachedShape, key);
                return null;
            }

            @Override
            public int getIntOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                return expectInteger(defaultValue);
            }

            @Override
            public long getLongOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                return expectLong(defaultValue);
            }

            @Override
            public double getDoubleOrDefault(DynamicObject object, Shape cachedShape, Object key, Object defaultValue) throws UnexpectedResultException {
                return expectDouble(defaultValue);
            }

            @Override
            public boolean setPropertyFlags(DynamicObject object, Shape cachedShape, Object key, int propertyFlags) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return false;
            }

            @Override
            public boolean removeKey(DynamicObject object, Shape cachedShape, Object key) {
                CompilerAsserts.partialEvaluationConstant(cachedShape);
                assert assertCachedKeyAndShapeForWrite(object, cachedShape, key);
                return false;
            }
        }

        static class ExistingKeyIdentity extends ExistingKey {
            ExistingKeyIdentity(Object key, Property property, KeyCacheEntry next) {
                super(key, property, next);
            }

            @Override
            public boolean acceptsKey(Object key) {
                return cachedKey == key;
            }

            @Override
            boolean isIdentity() {
                return true;
            }
        }

        static class MissingKeyIdentity extends MissingKey {
            MissingKeyIdentity(Object key, KeyCacheEntry next) {
                super(key, next);
            }

            @Override
            public boolean acceptsKey(Object key) {
                return cachedKey == key;
            }

            @Override
            boolean isIdentity() {
                return true;
            }
        }

        @ExplodeLoop
        protected boolean putImpl(DynamicObject object, Shape cachedShape, Object key, Object value, long putFlags, Property oldProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC || !cachedShape.isValid()) {
                return putUncached(object, key, value, putFlags);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (!c.isValid()) {
                    break;
                } else if (c instanceof PutCacheData && ((PutCacheData) c).putFlags == putFlags) {
                    Property newProperty = ((PutCacheData) c).property;
                    if (newProperty == null) {
                        assert Flags.isSetExisting(putFlags);
                        return false;
                    } else {
                        LocationImpl location = getLocation(newProperty);
                        boolean guardCondition = object.getShape() == oldShape;
                        if (location.canStore(value)) {
                            Shape newShape = c.newShape;
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.set(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        }
                    }
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoPutCache(object, cachedShape, value, putFlags, oldProperty);
            return impl.put(object, cachedShape, key, value, putFlags);
        }

        @ExplodeLoop
        protected boolean putIntImpl(DynamicObject object, Shape cachedShape, Object key, int value, long putFlags, Property oldProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC || !cachedShape.isValid()) {
                return putUncached(object, key, value, putFlags);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (!c.isValid()) {
                    break;
                } else if (c instanceof PutCacheData && ((PutCacheData) c).putFlags == putFlags) {
                    Property newProperty = ((PutCacheData) c).property;
                    if (newProperty == null) {
                        assert Flags.isSetExisting(putFlags);
                        return false;
                    } else {
                        LocationImpl location = getLocation(newProperty);
                        Shape newShape = c.newShape;
                        boolean guardCondition = object.getShape() == oldShape;
                        if (location.isIntLocation()) {
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.setInt(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        } else if (location.isImplicitCastIntToLong()) {
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.setLong(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        } else if (location.isImplicitCastIntToDouble()) {
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.setDouble(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        } else if (location.canStore(value)) {
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.set(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        }
                    }
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoPutCache(object, cachedShape, value, putFlags, oldProperty);
            return impl.putInt(object, cachedShape, key, value, putFlags);
        }

        @ExplodeLoop
        protected boolean putLongImpl(DynamicObject object, Shape cachedShape, Object key, long value, long putFlags, Property oldProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC) {
                return putUncached(object, key, value, putFlags);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (c instanceof PutCacheData && ((PutCacheData) c).putFlags == putFlags) {
                    Property newProperty = ((PutCacheData) c).property;
                    if (newProperty == null) {
                        assert Flags.isSetExisting(putFlags);
                        return false;
                    } else {
                        LocationImpl location = getLocation(newProperty);
                        boolean guardCondition = object.getShape() == oldShape;
                        if (location.isLongLocation()) {
                            Shape newShape = c.newShape;
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.setLong(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        } else if (location.canStore(value)) {
                            Shape newShape = c.newShape;
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.set(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        }
                    }
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoPutCache(object, cachedShape, value, putFlags, oldProperty);
            return impl.putLong(object, cachedShape, key, value, putFlags);
        }

        @ExplodeLoop
        protected boolean putDoubleImpl(DynamicObject object, Shape cachedShape, Object key, double value, long putFlags, Property oldProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC) {
                return putUncached(object, key, value, putFlags);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (c instanceof PutCacheData && ((PutCacheData) c).putFlags == putFlags) {
                    Property newProperty = ((PutCacheData) c).property;
                    if (newProperty == null) {
                        assert Flags.isSetExisting(putFlags);
                        return false;
                    } else {
                        LocationImpl location = getLocation(newProperty);
                        boolean guardCondition = object.getShape() == oldShape;
                        if (location.isDoubleLocation()) {
                            Shape newShape = c.newShape;
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.setDouble(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        } else if (newProperty.getLocation().canStore(value)) {
                            Shape newShape = c.newShape;
                            if (newShape != oldShape) {
                                ACCESS.growAndSetShape(object, oldShape, newShape);
                            } else if (location.isFinal()) {
                                continue;
                            }
                            try {
                                location.set(object, value, guardCondition);
                            } catch (IncompatibleLocationException | FinalLocationException e) {
                                throw shouldNotHappen(e);
                            }
                            c.maybeUpdateShape(object);
                            return true;
                        }
                    }
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoPutCache(object, cachedShape, value, putFlags, oldProperty);
            return impl.putDouble(object, cachedShape, key, value, putFlags);
        }

        protected KeyCacheNode insertIntoPutCache(DynamicObject object, Shape cachedShape, Object value, long putFlags, Property property) {
            CompilerAsserts.neverPartOfCompilation();
            if (!cachedShape.isValid()) {
                return Generic.instance();
            }
            Lock lock = getLock();
            lock.lock();
            try {
                MutateCacheData tail = filterValid(this.cache);

                ShapeImpl oldShape = (ShapeImpl) cachedShape;
                ShapeImpl newShape = getNewShape(object, value, putFlags, property, oldShape);

                if (!oldShape.isValid()) {
                    // If shape was invalidated, other locations may have changed, too,
                    // so we need to update the object's shape first.
                    // Cache entries with an invalid cache entry directly go to the slow path.
                    return Generic.instance();
                }

                Property newProperty;
                if (newShape == oldShape) {
                    newProperty = property;
                } else {
                    newProperty = newShape.getProperty(cachedKey);
                    assert newProperty.getLocation().canSet(value);
                }

                Assumption newShapeValid = getShapeValidAssumption(oldShape, newShape);
                this.cache = new PutCacheData(putFlags, newShape, newShapeValid, newProperty, tail);
                return this;
            } finally {
                lock.unlock();
            }
        }

        private ShapeImpl getNewShape(DynamicObject object, Object value, long putFlags, Property property, ShapeImpl oldShape) {
            if (property == null) {
                if (Flags.isSetExisting(putFlags)) {
                    return oldShape;
                } else {
                    int propertyFlags = Flags.getPropertyFlags(putFlags);
                    LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                    return strategy.defineProperty(oldShape, cachedKey, value, propertyFlags, null, putFlags);
                }
            }

            if (Flags.isUpdateFlags(putFlags)) {
                if (Flags.getPropertyFlags(putFlags) != property.getFlags()) {
                    int propertyFlags = Flags.getPropertyFlags(putFlags);
                    LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                    return strategy.defineProperty(oldShape, cachedKey, value, propertyFlags, null, putFlags);
                }
            }

            Location location = property.getLocation();
            if (!location.isDeclared() && !location.canSet(value)) {
                // generalize
                assert oldShape == ACCESS.getShape(object);
                LayoutStrategy strategy = oldShape.getLayout().getStrategy();
                ShapeImpl newShape = strategy.definePropertyGeneralize(oldShape, property, value, null, putFlags);
                assert newShape != oldShape;
                return newShape;
            } else if (location.isDeclared()) {
                // redefine declared
                LayoutStrategy strategy = oldShape.layout.getStrategy();
                return strategy.defineProperty(oldShape, cachedKey, value, property.getFlags(), null, putFlags);
            } else {
                // set existing
                assert location.canSet(value);
                return oldShape;
            }
        }

        @ExplodeLoop
        protected boolean setPropertyFlagsImpl(DynamicObject object, Shape cachedShape, Object key, int propertyFlags, Property cachedProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC || !cachedShape.isValid()) {
                return Generic.instance().setPropertyFlags(object, cachedShape, key, propertyFlags);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (!c.isValid()) {
                    break;
                } else if (c instanceof SetPropertyFlagsCacheData && ((SetPropertyFlagsCacheData) c).property.getFlags() == propertyFlags) {
                    if (cachedProperty == null) {
                        return false;
                    }
                    if (cachedProperty.getFlags() != propertyFlags) {
                        Shape newShape = c.newShape;
                        if (newShape != oldShape) {
                            ACCESS.setShape(object, newShape);
                            c.maybeUpdateShape(object);
                        }
                    }
                    return true;
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoSetPropertyFlagsCache(cachedShape, propertyFlags, cachedProperty);
            return impl.setPropertyFlags(object, cachedShape, key, propertyFlags);
        }

        protected KeyCacheNode insertIntoSetPropertyFlagsCache(Shape cachedShape, int propertyFlags, Property cachedProperty) {
            CompilerAsserts.neverPartOfCompilation();
            if (!cachedShape.isValid()) {
                return Generic.instance();
            }
            Lock lock = getLock();
            lock.lock();
            try {
                MutateCacheData tail = filterValid(this.cache);

                ShapeImpl oldShape = (ShapeImpl) cachedShape;
                ShapeImpl newShape = changePropertyFlags(oldShape, (PropertyImpl) cachedProperty, propertyFlags);

                if (!oldShape.isValid()) {
                    // If shape was invalidated, other locations may have changed, too,
                    // so we need to update the object's shape first.
                    // Cache entries with an invalid cache entry directly go to the slow path.
                    return Generic.instance();
                }

                Property newProperty;
                if (newShape == oldShape) {
                    newProperty = cachedProperty;
                } else {
                    newProperty = newShape.getProperty(cachedKey);
                }

                Assumption newShapeValid = getShapeValidAssumption(oldShape, newShape);
                this.cache = new SetPropertyFlagsCacheData(newShape, newShapeValid, newProperty, tail);
                return this;
            } finally {
                lock.unlock();
            }
        }

        @ExplodeLoop
        protected boolean removeKeyImpl(DynamicObject object, Shape cachedShape, Object key, Property cachedProperty) {
            Shape oldShape = cachedShape;
            MutateCacheData start = cache;
            if (start == MutateCacheData.GENERIC || !cachedShape.isValid()) {
                return Generic.instance().removeKey(object, cachedShape, key);
            }
            for (MutateCacheData c = start; c != null; c = c.next) {
                if (!c.isValid()) {
                    break;
                } else if (c instanceof RemovePropertyCacheData) {
                    if (cachedProperty == null) {
                        return false;
                    }
                    Shape newShape = c.newShape;
                    assert newShape != oldShape;
                    Map archive = null;
                    assert (archive = ACCESS.archive(object)) != null;

                    if (!oldShape.isShared()) {
                        ((RemovePropertyCacheData) c).removePlan.execute(object);
                    } else {
                        ACCESS.setShape(object, newShape);
                    }

                    assert ACCESS.verifyValues(object, archive);

                    c.maybeUpdateShape(object);
                    return true;
                }
            }
            CompilerDirectives.transferToInterpreterAndInvalidate();
            KeyCacheNode impl = insertIntoRemoveKeyCache(cachedShape, cachedProperty);
            return impl.removeKey(object, cachedShape, key);
        }

        protected KeyCacheNode insertIntoRemoveKeyCache(Shape cachedShape, Property cachedProperty) {
            CompilerAsserts.neverPartOfCompilation();
            if (!cachedShape.isValid()) {
                return Generic.instance();
            }
            Lock lock = getLock();
            lock.lock();
            try {
                MutateCacheData tail = filterValid(this.cache);

                ShapeImpl oldShape = (ShapeImpl) cachedShape;
                ShapeImpl newShape = oldShape.removeProperty(cachedProperty);

                if (!oldShape.isValid()) {
                    // If shape was invalidated, other locations may have changed, too,
                    // so we need to update the object's shape first.
                    // Cache entries with an invalid cache entry directly go to the slow path.
                    return Generic.instance();
                }

                RemovePlan removePlan = null;
                if (!oldShape.isShared()) {
                    removePlan = prepareRemove(oldShape, newShape);
                }

                Assumption newShapeValid = getShapeValidAssumption(oldShape, newShape);
                this.cache = new RemovePropertyCacheData(newShape, newShapeValid, removePlan, tail);
                return this;
            } finally {
                lock.unlock();
            }
        }

        private static Assumption getShapeValidAssumption(Shape oldShape, Shape newShape) {
            if (oldShape == newShape) {
                return alwaysValidAssumption();
            }
            return newShape.isValid() ? newShape.getValidAssumption() : neverValidAssumption();
        }
    }

    static > 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;
        }
    }

    static RuntimeException shouldNotHappen(Exception e) {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        throw new IllegalStateException(e);
    }

    abstract static class CacheData> {
        final T next;

        CacheData(T next) {
            this.next = next;
        }

        protected boolean isValid() {
            return true;
        }

        protected abstract T withNext(T newNext);
    }

    static class MutateCacheData extends CacheData {
        static final MutateCacheData GENERIC = new MutateCacheData(null, null, null);

        final Shape newShape;
        final Assumption newShapeValidAssumption;

        MutateCacheData(MutateCacheData next, Shape newShape, Assumption newShapeValidAssumption) {
            super(next);
            this.newShape = newShape;
            this.newShapeValidAssumption = newShapeValidAssumption;
        }

        @Override
        protected boolean isValid() {
            return newShapeValidAssumption == neverValidAssumption() || newShapeValidAssumption == alwaysValidAssumption() || newShapeValidAssumption.isValid();
        }

        protected void maybeUpdateShape(DynamicObject store) {
            if (newShapeValidAssumption == neverValidAssumption()) {
                updateShapeImpl(store);
            }
        }

        @Override
        protected MutateCacheData withNext(MutateCacheData newNext) {
            return new MutateCacheData(next, newShape, newShapeValidAssumption);
        }
    }

    static class PutCacheData extends MutateCacheData {

        final long putFlags;
        final Property property;

        PutCacheData(long putFlags, Shape newShape, Assumption newShapeValidAssumption, Property property, MutateCacheData next) {
            super(next, newShape, newShapeValidAssumption);
            this.putFlags = putFlags;
            this.property = property;
        }

        @Override
        protected MutateCacheData withNext(MutateCacheData newNext) {
            return new PutCacheData(putFlags, newShape, newShapeValidAssumption, property, newNext);
        }
    }

    static class SetPropertyFlagsCacheData extends MutateCacheData {

        final Property property;

        SetPropertyFlagsCacheData(Shape newShape, Assumption newShapeValidAssumption, Property property, MutateCacheData next) {
            super(next, newShape, newShapeValidAssumption);
            this.property = property;
        }

        @Override
        protected MutateCacheData withNext(MutateCacheData newNext) {
            return new SetPropertyFlagsCacheData(newShape, newShapeValidAssumption, property, newNext);
        }
    }

    static class RemovePropertyCacheData extends MutateCacheData {

        final RemovePlan removePlan;

        RemovePropertyCacheData(Shape newShape, Assumption newShapeValidAssumption, RemovePlan removePlan, MutateCacheData next) {
            super(next, newShape, newShapeValidAssumption);
            this.removePlan = removePlan;
        }

        @Override
        protected MutateCacheData withNext(MutateCacheData newNext) {
            return new RemovePropertyCacheData(newShape, newShapeValidAssumption, removePlan, newNext);
        }
    }

    @GenerateUncached
    abstract static class SetFlagsNode extends Node {
        abstract boolean execute(DynamicObject object, Shape cachedShape, int flags);

        @Specialization(guards = {"flags == newFlags"}, limit = "3")
        static boolean doCached(DynamicObject object, Shape cachedShape, @SuppressWarnings("unused") int flags,
                        @Cached(value = "flags", allowUncached = true) @SuppressWarnings("unused") int newFlags,
                        @Cached(value = "shapeSetFlags(cachedShape, newFlags)", allowUncached = true) Shape newShape) {
            if (newShape != cachedShape) {
                ACCESS.setShape(object, newShape);
                return true;
            } else {
                return false;
            }
        }

        @Specialization(replaces = "doCached")
        static boolean doUncached(DynamicObject object, Shape cachedShape, int flags) {
            Shape newShape = shapeSetFlags(cachedShape, flags);
            if (newShape != cachedShape) {
                ACCESS.setShape(object, newShape);
                return true;
            } else {
                return false;
            }
        }

        static Shape shapeSetFlags(Shape shape, int newFlags) {
            return ((ShapeImpl) shape).setFlags(newFlags);
        }
    }

    @GenerateUncached
    abstract static class SetDynamicTypeNode extends Node {
        abstract boolean execute(DynamicObject object, Shape cachedShape, Object objectType);

        @Specialization(guards = {"objectType == newObjectType"}, limit = "3")
        static boolean doCached(DynamicObject object, Shape cachedShape, @SuppressWarnings("unused") Object objectType,
                        @Cached(value = "objectType", allowUncached = true) @SuppressWarnings("unused") Object newObjectType,
                        @Cached(value = "shapeSetDynamicType(cachedShape, newObjectType)", allowUncached = true) Shape newShape) {
            if (newShape != cachedShape) {
                ACCESS.setShape(object, newShape);
                return true;
            } else {
                return false;
            }
        }

        @Specialization(replaces = "doCached")
        static boolean doUncached(DynamicObject object, Shape cachedShape, Object objectType) {
            Shape newShape = shapeSetDynamicType(cachedShape, objectType);
            if (newShape != cachedShape) {
                ACCESS.setShape(object, newShape);
                return true;
            } else {
                return false;
            }
        }

        static Shape shapeSetDynamicType(Shape shape, Object newType) {
            return ((ShapeImpl) shape).setDynamicType(newType);
        }
    }

    @GenerateUncached
    abstract static class MakeSharedNode extends Node {
        abstract void execute(DynamicObject object, Shape cachedShape);

        @Specialization
        static void doCached(DynamicObject object, Shape cachedShape,
                        @Cached(value = "cachedShape.makeSharedShape()", allowUncached = true) Shape newShape) {
            assert newShape != cachedShape;
            ACCESS.growAndSetShape(object, cachedShape, newShape);
        }
    }

    @GenerateUncached
    abstract static class ResetShapeNode extends Node {
        abstract boolean execute(DynamicObject object, Shape cachedShape, Shape newShape);

        @Specialization(guards = "otherShape == cachedOtherShape")
        static boolean doCached(DynamicObject object, Shape cachedShape, @SuppressWarnings("unused") Shape otherShape,
                        @Cached(value = "verifyResetShape(cachedShape, otherShape)", allowUncached = true) Shape cachedOtherShape) {
            if (cachedShape == cachedOtherShape) {
                return false;
            }
            ACCESS.resizeAndSetShape(object, cachedShape, cachedOtherShape);
            return true;
        }

        static Shape verifyResetShape(Shape currentShape, Shape otherShape) {
            if (((ShapeImpl) otherShape).hasInstanceProperties()) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                throw new IllegalArgumentException("Shape must not contain any instance properties.");
            }
            if (currentShape != otherShape) {
                ACCESS.invalidateAllPropertyAssumptions(currentShape);
            }
            return otherShape;
        }
    }
}