/* * 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); } } } }