/*
* 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 java.util.concurrent.*;
import com.sun.max.vm.actor.holder.*;
/**
* Map from a class to the set of {@linkplain Dependencies dependencies}
* that include the class as a <i>context</i> class. When a class hierarchy change
* (during class definition) involves a context class of an assumption, the assumption
* may be invalidated. This map allows efficient discovery of dependencies involving
* a context class.
* <p>
* There is no strong reference (path) from a context class in the dependency map
* to the {@linkplain Dependencies} instances. Instead, the set of {@linkplain Dependencies}
* objects are indirectly referenced by their {@linkplain Dependencies#id identifiers}.
* This prevents a class from being kept alive simply because it is involved in a
* dependency. This is important for simplifying class unloading.
*/
public final class ContextDependents {
/**
* A set of {@link Dependencies} identifiers stored in an array. This data structure is designed
* specifically as the value type in {@link ContextDependents#map}.
*/
public static final class DSet {
/**
* Creates a new set with one element.
* @param depsID
*/
DSet(int depsID) {
data = new int[] {depsID};
size = 1;
}
private int[] data;
private int size;
public int size() {
return size;
}
/**
* Gets the dependency ID at a given index.
*/
int get(int index) {
return data[index];
}
/**
* Gets the dependency at a given index.
*/
public Dependencies getDeps(int index) {
return Dependencies.fromId(get(index));
}
/**
* Adds a value to this set that is not currently in the set.
*
* @param depsID the value to add
*/
void addUnique(int depsID) {
assert find(depsID) == -1 : depsID + " is already in the set";
if (size == data.length) {
data = Arrays.copyOf(data, size * 2);
}
data[size++] = depsID;
}
/**
* Determines if a given value is in this set.
*
* @param depsID the value to search for
* @return {@code true} iff {@code value} is in this set
*/
int find(int depsID) {
for (int i = 0; i < size; i++) {
if (depsID == data[i]) {
return i;
}
}
return -1;
}
/**
* Removes an element from this set at a known index.
*
* @param index the index of the element to remove
* @return the removed element
*/
int removeAt(int index) {
int id = data[index];
if (index == size - 1) {
data[index] = 0;
} else {
// Replace with last element
data[index] = data[size - 1];
}
size--;
return id;
}
/**
* Removes an element from this set.
*
* @param depsID the element to remove
* @return {@code true} iff the element was removed
*/
boolean remove(int depsID) {
for (int i = 0; i < size; i++) {
if (depsID == data[i]) {
int removed = removeAt(i);
assert removed == depsID;
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < size; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(getDeps(i));
}
return sb.append('}').toString();
}
}
/**
* Initial capacity of the map. Based on statistics gathered over boot image generation and VM startup.
* Needs to be adjusted depending on the dynamic compilation scheme.
*/
static final int INITIAL_CAPACITY = 600;
public static final ConcurrentHashMap<ClassActor, DSet> map = new ConcurrentHashMap<ClassActor, DSet>(INITIAL_CAPACITY);
/**
* Adds a mapping from each context type in a dependencies object to the dependency object.
*/
void addDependencies(Dependencies deps, Set<ClassActor> typesInDeps) {
assert classHierarchyLock.getReadHoldCount() > 0 : "must hold the class hierarchy lock in read mode";
for (ClassActor type : typesInDeps) {
DSet dset = map.get(type);
if (dset == null) {
dset = map.putIfAbsent(type, new DSet(deps.id));
if (dset == null) {
// won the race to add the first dependency
if (dependenciesLogger.enabled()) {
deps.logAdd(type);
}
continue;
}
}
// lost the race - fall back to locking
synchronized (dset) {
dset.addUnique(deps.id);
}
if (dependenciesLogger.enabled()) {
deps.logAdd(type);
}
}
}
/**
* Removes all mappings in which a given dependencies object is a value.
* That is, the mapping for each context type in {@code deps} is updated to
* remove {@code deps}.
*
* @return the number references to {@code deps} in this map that were removed
*/
int removeDependencies(final Dependencies deps) {
final int[] removed = {0};
deps.visit(new Dependencies.DependencyVisitor() {
@Override
public boolean nextContextClass(ClassActor type, ClassActor prev) {
if (type != null) {
DSet dset = map.get(type);
if (dset != null) {
if (dset.remove(deps.id)) {
removed[0]++;
if (dependenciesLogger.enabled()) {
deps.logRemove(type);
}
}
if (dset.size == 0) {
map.remove(type);
}
}
}
return true;
}
});
return removed[0];
}
}