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

org.pkl.thirdparty.truffle.api.staticobject.ArrayBasedStaticShape Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2021, 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 org.pkl.thirdparty.truffle.api.staticobject;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.pkl.thirdparty.truffle.api.CompilerDirectives;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.CompilationFinal;

import sun.misc.Unsafe;

final class ArrayBasedStaticShape extends StaticShape {
    @SuppressWarnings("rawtypes") //
    private static final Class[] PRIMITIVE_TYPES = new Class[]{long.class, double.class, int.class, float.class, short.class, char.class, byte.class, boolean.class};
    private static final int N_PRIMITIVES = PRIMITIVE_TYPES.length;

    // Initialized by TruffleBaseFeature$StaticObjectArrayBasedSupport and used to patch the offsets
    // and the indexes used to store primitive values. The keys are factory instances (they store
    // the length of the primitive byte[]) and the primitive byte[] (they need to be resized).
    // Initially, values are the same as the corresponding keys. Once a replacement is computed, it
    // is stored as value. We can use an equality-based map because generated factory instances to
    // not override `equals()` and `hashCode()`.
    //
    // Cleared and set to null by TruffleBaseFeature to avoid leaking objects and calls to
    // `ImageInfo.inImageBuildtimeCode()` in code that might be PE'd.
    static ConcurrentHashMap replacements;

    @CompilationFinal(dimensions = 1) //
    private final StaticShape[] superShapes;
    private final ArrayBasedPropertyLayout propertyLayout;

    @SuppressWarnings({"unchecked", "rawtypes"})
    private ArrayBasedStaticShape(ArrayBasedStaticShape parentShape, Class storageClass, ArrayBasedPropertyLayout propertyLayout, boolean safetyChecks) {
        super(storageClass, safetyChecks);
        if (parentShape == null) {
            superShapes = new StaticShape[]{this};
        } else {
            int depth = parentShape.superShapes.length;
            superShapes = new StaticShape[depth + 1];
            System.arraycopy(parentShape.superShapes, 0, superShapes, 0, depth);
            superShapes[depth] = this;
        }
        this.propertyLayout = propertyLayout;
    }

    static  ArrayBasedStaticShape create(ArrayBasedShapeGenerator generator, Class generatedStorageClass, Class generatedFactoryClass, ArrayBasedStaticShape parentShape,
                    Collection staticProperties, boolean checkShapes) {
        try {
            ArrayBasedPropertyLayout parentPropertyLayout = parentShape == null ? null : parentShape.getPropertyLayout();
            ArrayBasedPropertyLayout propertyLayout = new ArrayBasedPropertyLayout(generator, parentPropertyLayout, staticProperties);
            ArrayBasedStaticShape shape = new ArrayBasedStaticShape<>(parentShape, generatedStorageClass, propertyLayout, checkShapes);
            T factory = generatedFactoryClass.cast(
                            generatedFactoryClass.getConstructor(
                                            ArrayBasedStaticShape.class,
                                            int.class,
                                            int.class,
                                            boolean.class).newInstance(
                                                            shape,
                                                            propertyLayout.getPrimitiveArraySize(),
                                                            propertyLayout.getObjectArraySize(),
                                                            replacements != null));
            shape.setFactory(factory);
            return shape;
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SuppressWarnings("cast")
    Object getStorage(Object obj, boolean primitive) {
        Object receiverObject = cast(obj, storageClass, false);
        if (safetyChecks) {
            checkShape(receiverObject);
        } else {
            assert checkShape(receiverObject);
        }
        /*
         * The safety of the unsafeCasts below is based on the fact that those 2 fields are final,
         * initialized in the constructor:
         *
         * * the object array is exactly an Object[] (see
         * ArrayBasedShapeGenerator.addStorageConstructors)
         *
         * * the byte[] is exact because there are no byte[] subclasses
         *
         * * the fields are final (see ArrayBasedShapeGenerator.generateStorage)
         *
         * * Any access of these fields after the constructor must remain after the final fields are
         * stored (because of the barrier at the end of the constructor for final fields) and thus
         * must see the initialized, non-null array of the correct type.
         *
         * * This getStorage access is not reachable inside the constructor (see the code of the
         * constructor).
         */
        if (primitive) {
            Object storage = UNSAFE.getObject(receiverObject, (long) propertyLayout.generator.getByteArrayOffset());
            assert storage != null;
            assert storage.getClass() == byte[].class;
            return SomAccessor.RUNTIME.unsafeCast(storage, byte[].class, true, true, true);
        } else {
            Object storage = UNSAFE.getObject(receiverObject, (long) propertyLayout.generator.getObjectArrayOffset());
            assert storage != null;
            assert storage.getClass() == Object[].class;
            return SomAccessor.RUNTIME.unsafeCast(storage, Object[].class, true, true, true);
        }
    }

    @SuppressWarnings("cast")
    private boolean checkShape(Object receiverObject) {
        ArrayBasedStaticShape receiverShape = cast(UNSAFE.getObject(receiverObject, (long) propertyLayout.generator.getShapeOffset()), ArrayBasedStaticShape.class, false);
        if (this != receiverShape && (receiverShape.superShapes.length < superShapes.length || receiverShape.superShapes[superShapes.length - 1] != this)) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            throw new IllegalArgumentException("Incompatible shape on property access. Expected '" + this + "' got '" + receiverShape + "'.");
        }
        return true;
    }

    private ArrayBasedPropertyLayout getPropertyLayout() {
        return propertyLayout;
    }

    private static int typeToInt(Class type) {
        if (!type.isPrimitive()) {
            return PRIMITIVE_TYPES.length;
        } else {
            for (int i = 0; i < PRIMITIVE_TYPES.length; i++) {
                if (type == PRIMITIVE_TYPES[i]) {
                    return i;
                }
            }
            throw new IllegalArgumentException("Invalid StaticProperty type: " + type.getName());
        }
    }

    private static Class intToType(int i) {
        return i == PRIMITIVE_TYPES.length ? Object.class : PRIMITIVE_TYPES[i];
    }

    /**
     * 

* Creates a layout for the primitive fields of a given class, and assigns to each field the raw * offset in the byte array that represents the data. The layout tries to be as compact as * possible. The rules for determining the layout are as follow: * *

  • The Top klass (j.l.Object) will have its field offset corresponding the point where the * data in the byte array begins (the first offset after the array header)
  • *
  • If this offset is not long-aligned, then start further so that this new offset is aligned * to a long. Register that there is some space between the start of the raw data and the first * field offset (perhaps a byte could be squeezed in).
  • *
  • Other klasses will inherit their super klass' layout, and start appending their own * declared field at the first offset aligned with the biggest primitive a given class has.
  • *
  • If there are known holes in the parents layout, the klass will attempt to squeeze its own * fields in these holes.
  • *

    *

    * For example, suppose we have the following hierarchy, and that the first offset of the data * in a byte array is at 14: *

    * *
         * class A {
         *     long l;
         *     int i;
         * }
         *
         * class B extends A {
         *     double d;
         * }
         *
         * class C extends B {
         *     float f;
         *     short s;
         * }
         * 
    * * Then, the resulting layout for A would be: * *
         * - 0-13: header
         * - 14-15: unused  -> Padding for aligned long
         * - 16-23: l
         * - 24-27: i
         * 
    * * the resulting layout for B would be: * *
         * - 0-13: header   }
         * - 14-15: unused  }   Same as
         * - 16-23: l       }      A
         * - 24-27: i       }
         * - 28-31: unused  -> Padding for aligned double
         * - 32-39: d
         * 
    * * the resulting layout for C would be: * *
         * - 0-13: header
         * - 14-15: s       ->   hole filled
         * - 16-23: l
         * - 24-27: i
         * - 28-31: f       ->   hole filled
         * - 32-39: d
         * 
    */ static class ArrayBasedPropertyLayout { private final int primitiveArraySize; private final int objectArraySize; // Stats about primitive fields @CompilationFinal(dimensions = 2) // private final int[][] leftoverHoles; private final int lastOffset; private final ArrayBasedShapeGenerator generator; ArrayBasedPropertyLayout(ArrayBasedShapeGenerator generator, ArrayBasedPropertyLayout parentLayout, Collection staticProperties) { this.generator = generator; // Stats about primitive fields int superTotalByteCount; int[][] parentLeftoverHoles; int objArraySize; if (parentLayout == null) { // Align the starting offset to a long. superTotalByteCount = base() + alignmentCorrection(); // Register a hole if we had to realign. if (alignmentCorrection() > 0) { parentLeftoverHoles = new int[][]{{base(), base() + alignmentCorrection()}}; } else { parentLeftoverHoles = new int[0][]; } objArraySize = 0; } else { superTotalByteCount = parentLayout.lastOffset; parentLeftoverHoles = parentLayout.leftoverHoles; objArraySize = parentLayout.objectArraySize; } int[] primitiveFields = new int[N_PRIMITIVES]; for (StaticProperty staticProperty : staticProperties) { int propertyIndex = typeToInt(staticProperty.getPropertyType()); if (staticProperty.getPropertyType().isPrimitive()) { primitiveFields[propertyIndex]++; } } PrimitiveFieldIndexes primitiveFieldIndexes = new PrimitiveFieldIndexes(primitiveFields, superTotalByteCount, parentLeftoverHoles); for (StaticProperty staticProperty : staticProperties) { int offset; if (staticProperty.getPropertyType().isPrimitive()) { int propertyIndex = typeToInt(staticProperty.getPropertyType()); offset = primitiveFieldIndexes.getIndex(propertyIndex); } else { // These offsets are re-computed for SVM: // TruffleBaseFeature.Target_com_oracle_truffle_api_staticobject_StaticProperty offset = Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * objArraySize++; } staticProperty.initOffset(offset); } lastOffset = primitiveFieldIndexes.offsets[N_PRIMITIVES - 1]; primitiveArraySize = getSizeToAlloc(parentLayout == null ? 0 : parentLayout.primitiveArraySize, primitiveFieldIndexes); objectArraySize = objArraySize; leftoverHoles = primitiveFieldIndexes.schedule.nextLeftoverHoles; } /* * If the object model does not start on a long-aligned offset. To manage, we will align our * indexes to the actual relative address to the start of the object. Note that we still * make a pretty strong assumption here: All arrays are allocated at an address aligned with * a *long* * * Note that we cannot use static final fields here, as SVM initializes them at build time * using HotSpot's values, and thus the values for base and alignment would not be correct. */ private static int base() { return Unsafe.ARRAY_BYTE_BASE_OFFSET; } private static int alignmentCorrection() { int misalignment = Unsafe.ARRAY_BYTE_BASE_OFFSET % Unsafe.ARRAY_LONG_INDEX_SCALE; return misalignment == 0 ? 0 : Unsafe.ARRAY_LONG_INDEX_SCALE - misalignment; } private static int getSizeToAlloc(int superToAlloc, PrimitiveFieldIndexes fieldIndexes) { int toAlloc = fieldIndexes.offsets[N_PRIMITIVES - 1] - base(); assert toAlloc >= 0; if (toAlloc == alignmentCorrection() && fieldIndexes.schedule.isEmpty()) { // If superKlass has fields in the alignment hole, we will need to allocate. If not, // we // can save an array. Note that if such a field exists, we will allocate an array of // size at least the alignment correction, since we fill holes from the right to the // left. toAlloc = superToAlloc; } return toAlloc; } int getPrimitiveArraySize() { return primitiveArraySize; } int getObjectArraySize() { return objectArraySize; } /** * Number of bytes that are necessary to represent a value of this kind. * * @return the number of bytes */ static int getByteCount(int b) { Class type = intToType(b); if (type == boolean.class) { return 1; } else { return getBitCount(type) >> 3; } } /** * Number of bits that are necessary to represent a value of this kind. * * @return the number of bits */ private static int getBitCount(Class type) { if (type == boolean.class) { return 1; } else if (type == byte.class) { return 8; } else if (type == char.class || type == short.class) { return 16; } else if (type == float.class || type == int.class) { return 32; } else if (type == double.class || type == long.class) { return 64; } else { throw new IllegalArgumentException("Invalid StaticProperty type: " + type.getName()); } } private static final class PrimitiveFieldIndexes { final int[] offsets; final FillingSchedule schedule; // To ignore leftoverHoles, pass FillingSchedule.EMPTY_INT_ARRAY_ARRAY. // This is used for static fields, where the gain would be negligible. PrimitiveFieldIndexes(int[] primitiveFields, int superTotalByteCount, int[][] leftoverHoles) { offsets = new int[N_PRIMITIVES]; offsets[0] = startOffset(superTotalByteCount, primitiveFields); this.schedule = FillingSchedule.create(superTotalByteCount, offsets[0], primitiveFields, leftoverHoles); // FillingSchedule.create() modifies primitiveFields. // Only offsets[0] must be initialized before creating the filling schedule. for (int i = 1; i < N_PRIMITIVES; i++) { offsets[i] = offsets[i - 1] + primitiveFields[i - 1] * getByteCount(i - 1); } } int getIndex(int propertyIndex) { ScheduleEntry entry = schedule.query(propertyIndex); if (entry != null) { return entry.offset; } else { int prevOffset = offsets[propertyIndex]; offsets[propertyIndex] += getByteCount(propertyIndex); return prevOffset; } } // Find first primitive to set, and align on it. private static int startOffset(int superTotalByteCount, int[] primitiveCounts) { int i = 0; while (i < N_PRIMITIVES && primitiveCounts[i] == 0) { i++; } if (i == N_PRIMITIVES) { return superTotalByteCount; } int r = superTotalByteCount % getByteCount(i); if (r == 0) { return superTotalByteCount; } return superTotalByteCount + getByteCount(i) - r; } } /** * Greedily tries to fill the space between a parent's fields and its child. */ private static final class FillingSchedule { static final int[][] EMPTY_INT_ARRAY_ARRAY = new int[0][]; final List schedule; int[][] nextLeftoverHoles; final boolean isEmpty; boolean isEmpty() { return isEmpty; } static FillingSchedule create(int holeStart, int holeEnd, int[] counts, int[][] leftoverHoles) { List schedule = new ArrayList<>(); if (leftoverHoles == EMPTY_INT_ARRAY_ARRAY) { // packing static fields is not as interesting as instance fields: the array // created // to remember the hole would be bigger than what we would gain. Only schedule // for // direct parent. scheduleHole(holeStart, holeEnd, counts, schedule); return new FillingSchedule(schedule); } else { List nextHoles = new ArrayList<>(); scheduleHole(holeStart, holeEnd, counts, schedule, nextHoles); if (leftoverHoles != null) { for (int[] hole : leftoverHoles) { scheduleHole(hole[0], hole[1], counts, schedule, nextHoles); } } return new FillingSchedule(schedule, nextHoles); } } private static void scheduleHole(int holeStart, int holeEnd, int[] counts, List schedule, List nextHoles) { int end = holeEnd; int holeSize = holeEnd - holeStart; byte i = 0; mainloop: while (holeSize > 0 && i < N_PRIMITIVES) { int byteCount = getByteCount(i); while (counts[i] > 0 && byteCount <= holeSize) { int newEnd = end - byteCount; if (newEnd % byteCount != 0) { int misalignment = newEnd % byteCount; int aligned = newEnd - misalignment; if (aligned < holeStart) { // re-aligning the store makes it overlap with somethig else: abort. i++; continue mainloop; } schedule.add(new ScheduleEntry(i, aligned)); counts[i]--; // We created a new hole of size `misaligned`. Try to fill it. scheduleHole(end - misalignment, end, counts, schedule, nextHoles); newEnd = aligned; } else { counts[i]--; schedule.add(new ScheduleEntry(i, newEnd)); } end = newEnd; holeSize = end - holeStart; } i++; } if (holeSize > 0) { nextHoles.add(new int[]{holeStart, end}); } } private static void scheduleHole(int holeStart, int holeEnd, int[] counts, List schedule) { int end = holeEnd; int holeSize = holeEnd - holeStart; byte i = 0; while (holeSize > 0 && i < N_PRIMITIVES) { int primitiveByteCount = getByteCount(i); while (counts[i] > 0 && primitiveByteCount <= holeSize) { counts[i]--; end -= primitiveByteCount; holeSize -= primitiveByteCount; schedule.add(new ScheduleEntry(i, end)); } i++; } assert holeSize >= 0; } private FillingSchedule(List schedule) { this.schedule = schedule; this.isEmpty = schedule == null || schedule.isEmpty(); } private FillingSchedule(List schedule, List nextHoles) { this.schedule = schedule; this.nextLeftoverHoles = nextHoles.isEmpty() ? null : nextHoles.toArray(EMPTY_INT_ARRAY_ARRAY); this.isEmpty = schedule != null && schedule.isEmpty(); } ScheduleEntry query(int propertyIndex) { for (ScheduleEntry e : schedule) { if (e.propertyIndex == propertyIndex) { schedule.remove(e); return e; } } return null; } } private static class ScheduleEntry { final int propertyIndex; final int offset; ScheduleEntry(int propertyIndex, int offset) { this.propertyIndex = propertyIndex; this.offset = offset; } } } }




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy