/* * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.vm.compiler.deps; import static com.sun.max.vm.compiler.deps.DependenciesManager.*; import java.util.*; import com.sun.cri.ci.*; import com.sun.cri.ci.CiAssumptions.*; import com.sun.max.annotate.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.Actor; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.deps.DependencyProcessor.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.jni.MemberID; import com.sun.max.vm.type.*; /** * Encodes the {@link Assumption assumptions} made when compiling a target method. * The assumptions, which initially are specified using {@link Actor} subtypes, * are encoded in an efficient, packed, format using {@link MemberID member ids}. * Once the assumptions are validated initially, they are associated with * the {@link TargetMethod} that resulted from the compilation. * <p> * An assumption always involves a {@link ClassActor class}, which is referred to * as the <i>context</i> class. All the {@link Dependencies dependencies} with * the same context class are kept together in {@link ContextDependents}. * <p> * Changes in the VM may necessitate validation of the {@link Dependencies dependencies}. * E.g., every time a new class is {@linkplain ClassRegistry#define(ClassActor) defined}, * the classes that are ancestors of the new class must be checked * to see if any of their dependent assumptions are invalidated as a result * of adding the new class to the hierarchy. * * See <a href="https://wikis.oracle.com/display/MaxineVM/Code+Dependencies">the Wiki page</a> for more details. */ public final class Dependencies { /** * Client for {@linkplain Dependencies#visit(DependencyVisitor) iterating} * over the basic structure of a {@link Dependencies} object. * <p> * Note that there are no method definitions related to {@linkplain DependencyProcessor} * here as their parameters are processor specific. A visitor that wants to * visit a specific dependency extends this class and implements the appropriate * {@linkplain DependencyProcessorVisitor}. * <p> * Two constructors are provided, one that visits all dependencies regardless of context class, * and one that only visits dependencies of a specific context class. * * An invalidated {@linkplain Dependencies} causes {@link #doInvalidated()} to be invoked. * Otherwise the data associated with each {@linkplain DependencyProcessor} is visited, possibly filtered by context class. * It is the responsibility of the {@linkplain DependencyProcessor} to process this data, invoking * the {@linkplain DependencyProcessorVisitor} if the visitor subclass implements the related subclass * of {@linkplain DependencyProcessorVisitor}. */ public static abstract class DependencyVisitor { public DependencyVisitor() { this(ClassIDManager.NULL_CLASS_ID); } public DependencyVisitor(int classID) { this.classID = classID; } /** * Only the dependencies for the context class whose identifier matches this field are * iterated. If this field is {@link ClassIDManager#NULL_CLASS_ID}, then all dependencies * are iterated. */ protected int classID; /** * Notifies this visitor of a new class context during iteration. * * @param c the class context of subsequent dependencies or {@code null} if there are no more contexts * @param prev the previous class context or {@code null} if {@code c} is the first class context * @return {@code false} if this visitor wants to terminate the iteration */ protected boolean nextContextClass(ClassActor c, ClassActor prev) { return true; } /** * Invoked when an invalidated {@link Dependencies} instance is encountered. */ protected void doInvalidated() { } /** * Generic visit to a single dependency within the data controlled by a {@linkplain DependencyProcessor}. * The default implementation just calls back to the {@linkplain DependencyProcessor} to invoke * any {@linkplain DependencyProcessorVisitor type-friendly callback} that this visitor implements, * and, <b>important</b> to step {@code index} to the next dependency. * @param dependencies the {@linkplain Dependencies} instance * @param context the context {@linkplain ClassActor} this dependency belongs to * @param dependencyProcessor the processor that manages this dependency * @param index of the dependency data in {@code dependencies.packed}. * @return the index of the next dependency or {@code -1} to terminate the visit. */ protected int visit(Dependencies dependencies, ClassActor context, DependencyProcessor dependencyProcessor, int index) { return dependencyProcessor.visit(dependencyProcessor.match(this), context, dependencies, index); } } /** * Overrides the {@link DependencyVisitor#visit} method to grab the {@link ToStringDependencyProcessorVisitor} * for the {@link DependencyProcessor}, (which is guaranteed to match) and pass it as the {@lnk DependencyProcessorVisitor}. */ private static class GenericToStringDependencyVisitor extends DependencyVisitor { private StringBuilder sb; GenericToStringDependencyVisitor setStringBuilder(StringBuilder sb) { this.sb = sb; return this; } @Override protected int visit(Dependencies dependencies, ClassActor context, DependencyProcessor dependencyProcessor, int index) { return dependencyProcessor.visit(dependencyProcessor.getToStringDependencyProcessorVisitor(sb), context, dependencies, index); } } private static class GenericToStringDependencyVisitorTL extends ThreadLocal<GenericToStringDependencyVisitor> { @Override public GenericToStringDependencyVisitor initialValue() { return new GenericToStringDependencyVisitor(); } } private static final GenericToStringDependencyVisitorTL genericToStringDependencyVisitorTL = new GenericToStringDependencyVisitorTL(); /** * Data structure used while encoding the dependencies for a class into a {@code short[]}. * {@code records[n]} holds the records for the n'th dependency type. * * The unique ids for {@link ClassActor} and {@link MethodActor} are used in the array to save space. The * {@linkplain ClassActor context} id of the dependencies is always the first element of the array. The encoding is * extensible to new dependencies through a "flags" field, which specifies which dependencies are present. The * additional data for the dependencies follows the "flags" field, in the order specified by the bit number * of the associated {@link DependencyProcessor dependency processor}. If a dependency is present, * and it has associated data, the length of its data follows the "flags" field, otherwise the length field is absent. * I.e. there is <b>not</b> a length field of zero for processors that do not encode extra data. * This knowledge is encoded in {@linkplain DependencyProcessor#hasData }. * <p> * The common part of the packed structure is as follows: * <pre> * short type; // identifier of the context class * short flags // bit mask specifying which dependencies are present * short length // length of processor-specific data (absent if none encoded) * </pre> */ public static class ClassDeps { class Records { short count; short[] buf; void add(short s) { if (buf == null) { buf = new short[initialCapacity]; } if (count == buf.length) { buf = Arrays.copyOf(buf, count * 2); } buf[count++] = s; } } short flags; Records[] records; int initialCapacity; public ClassDeps(int initialCapacity) { this.initialCapacity = initialCapacity; records = new Records[dependencyProcessorsArray.length]; } private Records get(DependencyProcessor dp) { int id = dp.id; if (records[id] == null) { records[id] = new Records(); } return records[id]; } /** * Add a piece of dependency data for the {@linkplain DependencyProcessor}. * @param dp * @param val */ public void add(DependencyProcessor dp, short val) { get(dp).add(val); } public void add(DependencyProcessor dp, int val) { Records r = get(dp); r.add((short) (val >> 16)); r.add((short) (val & 0xffff)); } public void add(DependencyProcessor dp, long val) { add(dp, (int) (val >> 32)); add(dp, (int) (val & 0xffffffff)); } } public static final int CLASSID_INDEX = 0; public static final int FLAGS_INDEX = 1; public static final int DATA_OFFSET = 2; /** * Sentinel instance for compilations with dependencies that failed their validation phase. */ public static final Dependencies INVALID = new Dependencies(); /** * Marker used to denote invalidated dependencies. */ static final short[] INVALIDATED = {}; /** * The target method compiled with these dependencies. */ TargetMethod targetMethod; /** * Unique identifier for these dependencies. Allocated from {@link #idMap}. */ public final int id; /** * Set of dependencies, packed into a short array. */ volatile short[] packed; @HOSTED_ONLY private Dependencies() { packed = INVALIDATED; id = -1; } private Dependencies(short[] packed) { id = idMap.allocate(this); this.packed = packed; } void setTargetMethod(TargetMethod targetMethod) { FatalError.check(classHierarchyLock.getReadHoldCount() > 0, "Must hold class hierarchy lock"); this.targetMethod = targetMethod; } public TargetMethod targetMethod() { return targetMethod; } private static ClassDeps get(HashMap<ClassActor, ClassDeps> dependencies, ClassActor type) { ClassDeps buf = dependencies.get(type); if (buf == null) { buf = new ClassDeps(4); dependencies.put(type, buf); } return buf; } public int getInt(int index) { return (packed[index] << 16) | packed[index + 1]; } public long getLong(int index) { return (getInt(index) << 32) | getInt(index + 2); } static short getMIndex(MethodActor methodActor) { int mindex = methodActor.memberIndex(); FatalError.check(mindex <= Short.MAX_VALUE && mindex >= 0, "method index range not supported"); return (short) mindex; } /** * Validates a given set of assumptions and returns them encoded in a {@link Dependencies} object * if validation succeeds. If validation fails, {@link Dependencies#INVALID} is returned instead. * If {@code assumptions == null}, then {@code null} is returned. */ public static Dependencies validateDependencies(CiAssumptions assumptions) { if (assumptions == null) { return null; } classHierarchyLock.readLock().lock(); try { FatalError.check(ClassIDManager.largestClassId() <= Short.MAX_VALUE, "Support for 1 << 16 number of classes not supported yet"); HashMap<ClassActor, ClassDeps> packedDeps = new HashMap<ClassActor, ClassDeps>(10); for (Assumption a : assumptions) { ClassActor contextClassActor = (ClassActor) ((ContextAssumption) a).context; ClassDeps classDeps = get(packedDeps, contextClassActor); DependencyProcessor dependencyProcessor = DependenciesManager.dependencyProcessors.get(a.getClass()); if (dependencyProcessor != null) { classDeps.flags |= dependencyProcessor.bitMask; if (!dependencyProcessor.validate(a, classDeps)) { return Dependencies.INVALID; } } else { assert false : "unhandled subtype of CiAssumptions: " + a.getClass().getName(); } } // Calculate the size of the array needed for all the dependencies int size = 0; for (Map.Entry<ClassActor, ClassDeps> e : packedDeps.entrySet()) { ClassDeps classDeps = e.getValue(); size += 2; // context type and flags for (int d = 0; d < dependencyProcessorsArray.length; d++) { DependencyProcessor dp = dependencyProcessorsArray[d]; ClassDeps.Records records = classDeps.records[dp.id]; if (records != null && records.count > 0) { size += 1 + records.count; } } } short[] packed = new short[size]; int i = 0; for (Map.Entry<ClassActor, ClassDeps> e : packedDeps.entrySet()) { ClassActor classActor = e.getKey(); ClassDeps classDeps = e.getValue(); packed[i++] = (short) classActor.id; packed[i++] = classDeps.flags; for (int d = 0; d < dependencyProcessorsArray.length; d++) { DependencyProcessor dp = dependencyProcessorsArray[d]; ClassDeps.Records records = classDeps.records[dp.id]; if (records != null && records.count > 0) { assert i < packed.length; packed[i++] = records.count; assert i + records.count <= packed.length; System.arraycopy(records.buf, 0, packed, i, records.count); i += records.count; } } } assert i == packed.length; Dependencies deps = new Dependencies(packed); contextDependents.addDependencies(deps, packedDeps.keySet()); return deps; } finally { classHierarchyLock.readLock().unlock(); } } /** * Register the target method produced with a set of validated dependencies. * * @param deps a set of validated dependencies * @param targetMethod the target method to associate with the dependencies */ public static void registerValidatedTarget(final Dependencies deps, final TargetMethod targetMethod) { classHierarchyLock.readLock().lock(); try { deps.setTargetMethod(targetMethod); } finally { classHierarchyLock.readLock().unlock(); } if (dependenciesLogger.enabled()) { deps.logRegister(); } } /** * Invalidates this set of dependencies. * * @return {@code true} if this set of dependencies was not already invalidated */ boolean invalidate() { // Called only when modifying the class hierarchy. FatalError.check(classHierarchyLock.isWriteLocked(), "Must hold class hierarchy lock in write mode"); // The above lock makes an atomic CAS unnecessary for 'packed' if (packed == INVALIDATED) { return false; } // Remove all other mappings from context types not involved in the current class hierarchy change contextDependents.removeDependencies(this); idMap.free(this); // Prevent multiple invalidations packed = INVALIDATED; return true; } /** * Visits all the dependencies in the packed form. * * @param visitor visitor the dependencies are fed to */ public void visit(DependencyVisitor visitor) { if (packed == INVALIDATED) { visitor.doInvalidated(); return; } int i = 0; ClassActor prev = null; while (i < packed.length) { int contextClassID = packed[i++]; int flags = packed[i++]; final ClassActor contextClassActor = ClassIDManager.toClassActor(contextClassID); if (visitor.classID == ClassIDManager.NULL_CLASS_ID || visitor.classID == contextClassID) { if (!visitor.nextContextClass(contextClassActor, prev)) { return; } // scan every set dependencies' data for (int b = 0; b < DependenciesManager.dependencyProcessorsArray.length; b++) { int mask = 1 << b; if ((flags & mask) != 0) { DependencyProcessor dependencyProcessor = DependenciesManager.dependencyProcessorsArray[b]; short length = dependencyProcessor.hasData ? packed[i++] : 0; int end = i + length; do { i = visitor.visit(this, contextClassActor, dependencyProcessor, i); if (i < 0) { return; } } while (i < end); } } // if we were just iterating one context and this was it we are done if (visitor.classID == contextClassID) { return; } } else { // skip over all records for this context for (int b = 0; b < DependenciesManager.dependencyProcessorsArray.length; b++) { int mask = 1 << b; if ((flags & mask) != 0) { DependencyProcessor dependencyProcessor = DependenciesManager.dependencyProcessorsArray[b]; if (dependencyProcessor.hasData) { i += 1 + packed[i]; } } } } prev = contextClassActor; } visitor.nextContextClass(null, prev); assert i == packed.length; } void logAdd(ClassActor type) { dependenciesLogger.logAdd(targetMethod, id, type); } void logRemove(ClassActor type) { dependenciesLogger.logRemove(targetMethod, id, type); } void logRegister() { dependenciesLogger.logRegister(targetMethod, id); } void logInvalidated() { dependenciesLogger.logInvalidated(targetMethod, id); } @Override public String toString() { return toString(false); } public String toString(boolean verbose) { String value; if (targetMethod == null) { value = String.valueOf(id); } else { value = id + "#" + targetMethod; } if (!verbose) { return value; } else { final StringBuilder sb = new StringBuilder(value + Arrays.toString(packed)); visit(genericToStringDependencyVisitorTL.get().setStringBuilder(sb)); return sb.toString(); } } // TODO (ld): factor out with similar code in Class ID ? private static final int MINIMAL_DEPENDENT_TARGET_METHOD = 5000; /** * Map used to allocate and reclaim unique identifiers for {@link Dependencies} objects. */ private static class IDMap extends LinearIDMap<Dependencies> { final BitSet usedIDs; public IDMap(int initialCapacity) { super(initialCapacity); usedIDs = new BitSet(); } synchronized int allocate(Dependencies deps) { int id = usedIDs.nextClearBit(0); usedIDs.set(id); set(id, deps); return id; } synchronized void free(Dependencies deps) { assert get(deps.id) == deps : deps + " != " + get(deps.id); set(deps.id, null); usedIDs.clear(deps.id); } } /** * Map used to allocate and reclaim unique identifiers for {@link Dependencies} objects. */ public static final IDMap idMap = new IDMap(MINIMAL_DEPENDENT_TARGET_METHOD); static Dependencies fromId(int depsID) { Dependencies deps = idMap.get(depsID); assert deps != null : "invalid dependencies id: " + depsID; return deps; } @HOSTED_ONLY DependencyProcessor[] getDependencyProcessors(int flags) { int count = 0; for (int b = 0; b < MAX_DEPENDENCY_PROCESSORS; b++) { int mask = 1 << b; if ((flags & mask) != 0) { count++; } } DependencyProcessor[] result = new DependencyProcessor[count]; count = 0; for (int b = 0; b < MAX_DEPENDENCY_PROCESSORS; b++) { int mask = 1 << b; if ((flags & mask) != 0) { DependencyProcessor dependencyProcessor = DependenciesManager.dependencyProcessorsArray[b]; result[count++] = dependencyProcessor; } } return result; } }