blob: 13253bca6d26817488cb931afaef3fd69fb2cba2 [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.dev.util.collect.IntMultimap;
import com.google.gwt.dev.util.collect.IntStack;
import cern.colt.list.IntArrayList;
import cern.colt.map.OpenIntIntHashMap;
import java.util.BitSet;
/**
* Uses method and type control flow indexes to compute a reachable types set.
* <p>
* Achieves high performance by operating strictly on methods and types which have been mapped to
* unique int ids but this performance comes at the cost of forcing callers to provide such a
* compliant AnalyzableTypeEnvironment.
*/
public class RapidTypeAnalyzer {
/**
* An interface for a type environment sufficient for rapid type analysis.
*/
public interface AnalyzableTypeEnvironment {
/**
* Returns a list of the ids of member methods in the given type and null if there are none.
*/
IntArrayList getMemberMethodIdsIn(int enclosingTypeId);
/**
* Returns a list of the ids of methods called by the given method and null if there are none.
*/
IntArrayList getMethodIdsCalledBy(int callerMethodId);
/**
* Returns a list of the ids of methods overriding the given method and null if there are none.
*/
IntArrayList getOverriddenMethodIds(int overridingMethodId);
/**
* Returns a list of the ids of methods overridden by the given method and null if there are
* none.
*/
IntArrayList getOverridingMethodIds(int overriddenMethodId);
/**
* Returns a list of the ids of types that are statically referenced within the given method and
* null if there are none.
*/
IntArrayList getStaticallyReferencedTypeIdsIn(int methodId);
/**
* Returns a list of the ids of types instantiated within the given method and null if there are
* none.
*/
IntArrayList getTypeIdsInstantiatedIn(int methodId);
}
/**
* Contains control flow indexes upon which this analysis is based.
*/
private AnalyzableTypeEnvironment analyzableTypeEnvironment;
/**
* The set of ids of types which have so far been found to be instantiated. They are tracked so
* that so processing the consequences of the instantiation of a type can be limited to just once
* per type.
*/
private BitSet instantiatedTypeIds = new BitSet();
/**
* A mapping from known-to-exist method ids to the ids of methods that both override them and are
* members in types that are known to be instantiated. If at some point a known-to-exist method
* becomes reachable then the previously characterized associated overriding methods immediately
* become reachable as well.
*/
private IntMultimap knownOverridingMethodIdsByOverriddenMethodId = new IntMultimap();
/**
* The ids of all methods that override some already known reachable method. If at some point a
* type becomes instantiated than any of its methods that are already in this set suddenly become
* reachable.
*/
private BitSet overidingMethodIdsOfReachableMethods = new BitSet();
/**
* Ids of methods that have been reached and processed and should not be reprocessed.
* <p>
* AKA the closed set.
*/
private BitSet reachableMethodIds = new BitSet();
/**
* The answer calculated by this reachability analyzer, aka the accumulation of ids of types that
* have been calculated to be reachable.
*/
private OpenIntIntHashMap reachableTypeIds = new OpenIntIntHashMap();
/**
* Ids of methods that are known to be reachable but for which the consequences of that
* reachability has not been processed. As exploration progresses newly seen methods are enqueued
* here for later processing.
* <p>
* AKA the open set.
*/
private IntStack unprocessedReachableMethodIds = new IntStack();
public RapidTypeAnalyzer(AnalyzableTypeEnvironment analyzableTypeEnvironment) {
this.analyzableTypeEnvironment = analyzableTypeEnvironment;
}
/**
* Follow control flow to find and return the set of ids of reachable types.
*/
public IntArrayList computeReachableTypeIds() {
while (!unprocessedReachableMethodIds.isEmpty()) {
int reachableMethodId = unprocessedReachableMethodIds.pop();
addReachableTypeIds(
analyzableTypeEnvironment.getStaticallyReferencedTypeIdsIn(reachableMethodId));
markTypeIdsInstantiated(
analyzableTypeEnvironment.getTypeIdsInstantiatedIn(reachableMethodId));
markMethodIdsReachable(analyzableTypeEnvironment.getMethodIdsCalledBy(reachableMethodId),
true);
}
return reachableTypeIds.keys();
}
/**
* Enqueues the methods within a given type (and some related overriding method ids) as reachable
* and not yet processed.
*/
public void markMemberMethodIdsReachable(int typeId) {
IntArrayList memberMethodIds = analyzableTypeEnvironment.getMemberMethodIdsIn(typeId);
if (memberMethodIds == null) {
return;
}
markMethodIdsReachable(memberMethodIds, true);
}
/**
* Enqueues a given method id (and some related overriding method ids) as reachable and not yet
* processed.
*/
public void markMethodIdReachable(int methodId, boolean cascade) {
// If the given method has already been marked reachable.
if (reachableMethodIds.get(methodId)) {
// Ignore it.
return;
}
overidingMethodIdsOfReachableMethods.set(methodId);
unprocessedReachableMethodIds.push(methodId);
reachableMethodIds.set(methodId);
recordOverridingMethodIdsOfReachableMethod(methodId);
if (cascade) {
// The current method is being marked reachable so any methods that override it should be
// marked reachable as well.
IntArrayList values = knownOverridingMethodIdsByOverriddenMethodId.get(methodId);
if (values != null) {
for (int i = 0; i < values.size(); i++) {
int overridingMethodId = values.get(i);
// Don't cascade in this nested invocation since at this point any overriding methods are
// already marked reachable.
markMethodIdReachable(overridingMethodId, false);
}
}
}
}
/**
* Records that a type is reachable.
*/
public void markTypeIdReachable(int typeId) {
reachableTypeIds.put(typeId, typeId);
}
private void addReachableTypeIds(IntArrayList typeIds) {
if (typeIds == null) {
return;
}
for (int i = 0; i < typeIds.size(); i++) {
int typeId = typeIds.get(i);
markTypeIdReachable(typeId);
}
}
private void markMethodIdsReachable(IntArrayList methodIds, boolean cascade) {
if (methodIds == null) {
return;
}
for (int i = 0; i < methodIds.size(); i++) {
markMethodIdReachable(methodIds.get(i), cascade);
}
}
private void markTypeIdInstantiated(int typeId) {
if (instantiatedTypeIds.get(typeId)) {
return;
}
instantiatedTypeIds.set(typeId);
markTypeIdReachable(typeId);
IntArrayList memberMethodIds = analyzableTypeEnvironment.getMemberMethodIdsIn(typeId);
if (memberMethodIds == null) {
return;
}
for (int i = 0; i < memberMethodIds.size(); i++) {
int memberMethodId = memberMethodIds.get(i);
IntArrayList overriddenMethodIds =
analyzableTypeEnvironment.getOverriddenMethodIds(memberMethodId);
if (overriddenMethodIds != null) {
for (int j = 0; j < overriddenMethodIds.size(); j++) {
int overriddenMethodId = overriddenMethodIds.get(j);
knownOverridingMethodIdsByOverriddenMethodId.put(overriddenMethodId, memberMethodId);
}
}
}
for (int i = 0; i < memberMethodIds.size(); i++) {
int memberMethodId = memberMethodIds.get(i);
if (overidingMethodIdsOfReachableMethods.get(memberMethodId)) {
markMethodIdReachable(memberMethodId, true);
}
}
}
private void markTypeIdsInstantiated(IntArrayList typeIds) {
if (typeIds == null) {
return;
}
for (int i = 0; i < typeIds.size(); i++) {
int typeId = typeIds.get(i);
markTypeIdInstantiated(typeId);
}
}
/**
* Records the ids of methods that override the provided known reachable method, so that they can
* themselves be efficiently marked reachable at some later time if their enclosing type is
* instantiated.
*/
private void recordOverridingMethodIdsOfReachableMethod(int methodId) {
IntArrayList overridingMethodIds = analyzableTypeEnvironment.getOverridingMethodIds(methodId);
if (overridingMethodIds != null) {
for (int i = 0; i < overridingMethodIds.size(); i++) {
int overridingMethodId = overridingMethodIds.get(i);
overidingMethodIdsOfReachableMethods.set(overridingMethodId);
}
}
}
}