From 418d383da3addcb9e637d27e2bcdfd8aff7a9f01 Mon Sep 17 00:00:00 2001 From: Dominik Helm Date: Thu, 8 Apr 2021 12:25:19 +0200 Subject: [PATCH] Use more performant data structures for type analyses --- .../cg/xta/InstantiatedTypesAnalysis.scala | 95 +++++++++++-------- .../cg/xta/TypePropagationAnalysis.scala | 33 +++---- .../cg/xta/TypePropagationState.scala | 54 ++++++----- 3 files changed, 101 insertions(+), 81 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index cc3436aa17..7996d03070 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -21,6 +21,7 @@ import org.opalj.br.fpcf.properties.cg.NoCallers import org.opalj.br.instructions.INVOKESPECIAL import org.opalj.br.instructions.NEW import org.opalj.collection.immutable.UIDSet +import org.opalj.collection.mutable.RefArrayBuffer import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.EPS @@ -37,9 +38,6 @@ import org.opalj.fpcf.Results import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.UBP -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * Marks types as instantiated if their constructor is invoked. Constructors invoked by subclass * constructors do not result in additional instantiated types. @@ -106,7 +104,7 @@ class InstantiatedTypesAnalysis private[analyses] ( seenCallers: Set[DeclaredMethod] ): PropertyComputationResult = { var newSeenCallers = seenCallers - val partialResults = new ListBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]]() + val partialResults = RefArrayBuffer.empty[PartialResult[TypeSetEntity, InstantiatedTypes]] for { (caller, _, _) ← callersUB.callers // if we already analyzed the caller, we do not need to do it twice @@ -128,7 +126,7 @@ class InstantiatedTypesAnalysis private[analyses] ( continuation(declaredMethod, declaredType, newSeenCallers) ) - Results(reRegistration, partialResults) + Results(reRegistration, partialResults.iterator()) } } @@ -136,7 +134,7 @@ class InstantiatedTypesAnalysis private[analyses] ( declaredMethod: DeclaredMethod, declaredType: ObjectType, caller: DeclaredMethod, - partialResults: ListBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]] + partialResults: RefArrayBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]] ): Unit = { // a constructor is called by a non-constructor method, there will be an initialization. if (caller.name != "") { @@ -273,20 +271,21 @@ class InstantiatedTypesAnalysisScheduler( val packageIsClosed = p.get(ClosedPackagesKey) val declaredMethods = p.get(DeclaredMethodsKey) val entryPoints = p.get(InitialEntryPointsKey) - val initialInstantiatedTypes = p.get(InitialInstantiatedTypesKey).toSet + val initialInstantiatedTypes = + UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) // While processing entry points and fields, we keep track of all array types we see, as // well as subtypes and lower-dimensional types. These types also need to be // pre-initialized. Note: This set only contains ArrayTypes whose element type is an // ObjectType. Arrays of primitive types can be ignored. - val seenArrayTypes = mutable.Set[ArrayType]() + val seenArrayTypes = UIDSet.newBuilder[ArrayType] - def initialize(setEntity: TypeSetEntity, types: Traversable[ReferenceType]): Unit = { + def initialize(setEntity: TypeSetEntity, types: UIDSet[ReferenceType]): Unit = { ps.preInitialize(setEntity, InstantiatedTypes.key) { case UBP(typeSet) ⇒ InterimEUBP(setEntity, typeSet.updated(types)) case _: EPK[_, _] ⇒ - InterimEUBP(setEntity, InstantiatedTypes(UIDSet(types.toSeq: _*))) + InterimEUBP(setEntity, InstantiatedTypes(types)) case eps ⇒ sys.error(s"unexpected property: $eps") } @@ -294,7 +293,7 @@ class InstantiatedTypesAnalysisScheduler( // Some cooperative analyses originally meant for RTA may require the global type set // to be pre-initialized. For that purpose, an empty type set is sufficient. - initialize(p, Traversable.empty) + initialize(p, UIDSet.empty) def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isObjectType @@ -305,8 +304,8 @@ class InstantiatedTypesAnalysisScheduler( ep ← entryPoints; dm = declaredMethods(ep) ) { - val typeFilters = mutable.Set[ReferenceType]() - val arrayTypeAssignments = mutable.Set[ArrayType]() + val typeFilters = UIDSet.newBuilder[ReferenceType] + val arrayTypeAssignments = UIDSet.newBuilder[ArrayType] if (!dm.definedMethod.isStatic) { typeFilters += dm.declaringClassType @@ -330,10 +329,10 @@ class InstantiatedTypesAnalysisScheduler( } // Initial assignments of ObjectTypes - val objectTypeAssignments = initialInstantiatedTypes.filter(iit ⇒ typeFilters.exists(tf ⇒ - p.classHierarchy.isSubtypeOf(iit, tf))) + val objectTypeAssignments = initialInstantiatedTypes.filter(iit ⇒ + typeFilters.result().exists(tf ⇒ p.classHierarchy.isSubtypeOf(iit, tf))) - val initialAssignment = arrayTypeAssignments ++ objectTypeAssignments + val initialAssignment = objectTypeAssignments ++ arrayTypeAssignments.result() val dmSetEntity = selectSetEntity(dm) @@ -341,7 +340,7 @@ class InstantiatedTypesAnalysisScheduler( } // Returns true if the field's type indicates that the field should be pre-initialized. - def fieldIsRelevant(f: Field): Boolean = { + @inline def fieldIsRelevant(f: Field): Boolean = { // Only fields which are ArrayType or ObjectType are relevant. f.fieldType.isReferenceType && // If the field is an ArrayType, then the array's element type must be an ObjectType. @@ -372,25 +371,32 @@ class InstantiatedTypesAnalysisScheduler( // Assign initial types to all accessable fields. p.classFile(ot) match { case Some(cf) ⇒ - for (f ← cf.fields if fieldIsRelevant(f) && f.isNotFinal && fieldIsAccessible(f)) { + for (f ← cf.fields if f.isNotFinal && fieldIsRelevant(f) && fieldIsAccessible(f)) { val fieldType = f.fieldType.asReferenceType - - val initialAssignments = fieldType match { - case ot: ObjectType ⇒ - initialInstantiatedTypes.filter( - p.classHierarchy.isSubtypeOf(_, ot) - ) - - case at: ArrayType ⇒ - seenArrayTypes += at - - val dim = at.dimensions - val et = at.elementType.asObjectType - p.classHierarchy.allSubtypes(et, reflexive = true) - .intersect(initialInstantiatedTypes).map( - ArrayType(dim, _) - ) - + import p.classHierarchy + + val initialAssignments = if (fieldType.isObjectType) { + val ot = fieldType.asObjectType + initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) { + (assignments, iit) ⇒ + if (classHierarchy.isSubtypeOf(iit, ot)) { + assignments += iit + } + assignments + }.result() + } else { + val at = fieldType.asArrayType + seenArrayTypes += at + val dim = at.dimensions + val et = at.elementType.asObjectType + val allSubtypes = p.classHierarchy.allSubtypes(et, true) + initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) { + (assignments, iit) ⇒ + if (allSubtypes.contains(iit.asObjectType)) { + assignments += ArrayType(dim, iit) + } + assignments + }.result() } val fieldSetEntity = selectSetEntity(f) @@ -407,7 +413,7 @@ class InstantiatedTypesAnalysisScheduler( // and initialize their type sets. // Remember which ArrayTypes were processed, so we don't do it twice. - val initializedArrayTypes = mutable.Set[ArrayType]() + val initializedArrayTypes = new java.util.HashSet[ArrayType]() def initializeArrayType(at: ArrayType): Unit = { // If this type has already been initialized, we skip it. @@ -415,17 +421,24 @@ class InstantiatedTypesAnalysisScheduler( return ; } - initializedArrayTypes += at + initializedArrayTypes.add(at) val et = at.elementType.asObjectType - val subtypes = p.classHierarchy.allSubtypes(et, reflexive = true).intersect(initialInstantiatedTypes) + val allSubtypes = p.classHierarchy.allSubtypes(et, true) + val subtypes = + initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) { (builder, iit) ⇒ + if (allSubtypes.contains(iit.asObjectType)) { + builder += iit + } + builder + }.result() val dim = at.dimensions if (dim > 1) { // Initialize multidimensional ArrayType. E.g., if at == A[][] and A is a supertype of A1, // we need to assign A[] and A1[] to the type set of A[][]. - val assignedArrayTypes = subtypes.map(ArrayType(dim - 1, _)) - initialize(at, assignedArrayTypes) + val assignedArrayTypes: UIDSet[ArrayType] = subtypes.map(ArrayType(dim - 1, _)) + initialize(at, assignedArrayTypes.asInstanceOf[UIDSet[ReferenceType]]) // After that, we also need to initialize the ArrayTypes which were just assigned. It is possible // that these were types which were not initially seen when processing entry points and fields. @@ -436,6 +449,6 @@ class InstantiatedTypesAnalysisScheduler( } } - seenArrayTypes foreach initializeArrayType + seenArrayTypes.result() foreach initializeArrayType } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala index 2b4939e0e4..f58fbe7dc9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala @@ -6,6 +6,8 @@ package analyses package cg package xta +import scala.collection.JavaConverters.asScalaIteratorConverter + import org.opalj.br.ArrayType import org.opalj.br.Code import org.opalj.br.DeclaredMethod @@ -23,6 +25,7 @@ import org.opalj.br.fpcf.properties.cg.InstantiatedTypes import org.opalj.br.instructions.CHECKCAST import org.opalj.br.instructions.INVOKESTATIC import org.opalj.collection.immutable.UIDSet +import org.opalj.collection.mutable.RefArrayBuffer import org.opalj.fpcf.EPS import org.opalj.fpcf.EUBP import org.opalj.fpcf.Entity @@ -37,8 +40,6 @@ import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.SomePartialResult import org.opalj.tac.fpcf.properties.TACAI -import scala.collection.mutable.ListBuffer - /** * This analysis handles the type propagation of XTA, MTA, FTA and CTA call graph * algorithms. @@ -69,20 +70,20 @@ final class TypePropagationAnalysis private[analyses] ( implicit val state: TypePropagationState = new TypePropagationState(definedMethod, typeSetEntity, tacEP, instantiatedTypesEOptP, calleesEOptP) - implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]() + implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult] if (calleesEOptP.hasUBP) processCallees(calleesEOptP.ub) processTACStatements processArrayTypes(state.ownInstantiatedTypes) - returnResults(partialResults) + returnResults(partialResults.iterator()) } /** * Processes the method upon initialization. Finds field/array accesses and wires up dependencies accordingly. */ - private def processTACStatements(implicit state: State, partialResults: ListBuffer[SomePartialResult]): Unit = { + private def processTACStatements(implicit state: State, partialResults: RefArrayBuffer[SomePartialResult]): Unit = { val bytecode = state.method.definedMethod.body.get val tac = state.tac tac.stmts.foreach { @@ -155,9 +156,9 @@ final class TypePropagationAnalysis private[analyses] ( state: State ): ProperPropertyComputationResult = { state.updateCalleeDependee(eps) - implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]() + implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult] processCallees(eps.ub) - returnResults(partialResults) + returnResults(partialResults.iterator()) } private def handleUpdateOfOwnTypeSet( @@ -170,8 +171,8 @@ final class TypePropagationAnalysis private[analyses] ( state.updateOwnInstantiatedTypesDependee(eps) val unseenTypes = UIDSet(eps.ub.dropOldest(previouslySeenTypes).toSeq: _*) - implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]() - for (fpe ← state.forwardPropagationEntities) { + implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult] + for (fpe ← state.forwardPropagationEntities.iterator().asScala) { val filters = state.forwardPropagationFilters(fpe) val propagation = propagateTypes(fpe, unseenTypes, filters) if (propagation.isDefined) @@ -180,7 +181,7 @@ final class TypePropagationAnalysis private[analyses] ( processArrayTypes(unseenTypes) - returnResults(partialResults) + returnResults(partialResults.iterator()) } private def handleUpdateOfBackwardPropagationTypeSet( @@ -205,7 +206,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { for (t ← unseenTypes if t.isArrayType; at = t.asArrayType if at.elementType.isReferenceType) { if (state.methodWritesArrays) { @@ -231,7 +232,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val bytecode = state.method.definedMethod.body.get for { @@ -263,7 +264,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val params = UIDSet.newBuilder[ReferenceType] @@ -299,7 +300,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val returnValueIsUsed = { val tacIndex = state.tac.properStmtIndexForPC(pc) @@ -334,7 +335,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { // Propagation from and to the same entity can be ignored. val typeSetEntity = selectTypeSetEntity(e) @@ -356,7 +357,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val typeSetEntity = selectTypeSetEntity(e) if (typeSetEntity == state.typeSetEntity) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala index e4cf3be12c..506ac90b6a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala @@ -18,8 +18,11 @@ import org.opalj.collection.immutable.UIDSet import org.opalj.fpcf.EOptionP import org.opalj.fpcf.SomeEOptionP import org.opalj.tac.fpcf.properties.TACAI +import java.util.{HashSet ⇒ JHashSet} +import java.util.{HashMap ⇒ JHashMap} +import java.util.{Set ⇒ JSet} -import scala.collection.mutable +import scala.collection.JavaConverters.asScalaIteratorConverter /** * Manages the state of each method analyzed by [[TypePropagationAnalysis]]. @@ -73,7 +76,7 @@ final class TypePropagationState( // // ///////////////////////////////////////////// - private[this] var _seenCallees: mutable.Set[(PC, DeclaredMethod)] = mutable.Set.empty + private[this] val _seenCallees: JSet[(PC, DeclaredMethod)] = new JHashSet() def isSeenCallee(pc: PC, callee: DeclaredMethod): Boolean = _seenCallees.contains((pc, callee)) @@ -100,12 +103,14 @@ final class TypePropagationState( // // ///////////////////////////////////////////// - private[this] var _forwardPropagationEntities: mutable.Set[TypeSetEntity] = mutable.Set.empty - private[this] var _forwardPropagationFilters: mutable.Map[TypeSetEntity, UIDSet[ReferenceType]] = mutable.Map.empty + private[this] val _forwardPropagationEntities: JSet[TypeSetEntity] = new JHashSet() + private[this] val _forwardPropagationFilters: JHashMap[TypeSetEntity, UIDSet[ReferenceType]] = + new JHashMap() - def forwardPropagationEntities: Traversable[TypeSetEntity] = _forwardPropagationEntities + def forwardPropagationEntities: JSet[TypeSetEntity] = _forwardPropagationEntities - def forwardPropagationFilters(typeSetEntity: TypeSetEntity): UIDSet[ReferenceType] = _forwardPropagationFilters(typeSetEntity) + def forwardPropagationFilters(typeSetEntity: TypeSetEntity): UIDSet[ReferenceType] = + _forwardPropagationFilters.get(typeSetEntity) /** * Registers a new set entity to consider for forward propagation alongside a set of filters. If the @@ -127,13 +132,13 @@ final class TypePropagationState( val alreadyExists = _forwardPropagationEntities.contains(typeSetEntity) if (!alreadyExists) { val compactedFilters = rootTypes(typeFilters) - _forwardPropagationEntities += typeSetEntity - _forwardPropagationFilters += typeSetEntity → compactedFilters + _forwardPropagationEntities.add(typeSetEntity) + _forwardPropagationFilters.put(typeSetEntity, compactedFilters) true } else { - val existingTypeFilters = _forwardPropagationFilters(typeSetEntity) + val existingTypeFilters = _forwardPropagationFilters.get(typeSetEntity) val newFilters = rootTypes(existingTypeFilters union typeFilters) - _forwardPropagationFilters.update(typeSetEntity, newFilters) + _forwardPropagationFilters.put(typeSetEntity, newFilters) newFilters != existingTypeFilters } } @@ -144,12 +149,13 @@ final class TypePropagationState( // // ///////////////////////////////////////////// - private[this] var _backwardPropagationDependees: mutable.Map[TypeSetEntity, EOptionP[TypeSetEntity, InstantiatedTypes]] = - mutable.Map.empty - private[this] var _backwardPropagationFilters: mutable.Map[TypeSetEntity, UIDSet[ReferenceType]] = mutable.Map.empty + private[this] val _backwardPropagationDependees: JHashMap[TypeSetEntity, EOptionP[TypeSetEntity, InstantiatedTypes]] = + new JHashMap() + private[this] val _backwardPropagationFilters: JHashMap[TypeSetEntity, UIDSet[ReferenceType]] = + new JHashMap() def backwardPropagationDependeeInstantiatedTypes(typeSetEntity: TypeSetEntity): UIDSet[ReferenceType] = { - val dependee = _backwardPropagationDependees(typeSetEntity) + val dependee = _backwardPropagationDependees.get(typeSetEntity) if (dependee.hasUBP) dependee.ub.types else @@ -157,10 +163,10 @@ final class TypePropagationState( } def backwardPropagationDependeeIsRegistered(typeSetEntity: TypeSetEntity): Boolean = - _backwardPropagationDependees.contains(typeSetEntity) + _backwardPropagationDependees.containsKey(typeSetEntity) def backwardPropagationFilters(typeSetEntity: TypeSetEntity): UIDSet[ReferenceType] = - _backwardPropagationFilters(typeSetEntity) + _backwardPropagationFilters.get(typeSetEntity) def updateBackwardPropagationFilters( typeSetEntity: TypeSetEntity, @@ -170,25 +176,25 @@ final class TypePropagationState( classHierarchy: ClassHierarchy ): Boolean = { assert(typeFilters.nonEmpty) - val alreadyExists = _backwardPropagationFilters.contains(typeSetEntity) + val alreadyExists = _backwardPropagationFilters.containsKey(typeSetEntity) if (!alreadyExists) { val compactedFilters = rootTypes(typeFilters) - _backwardPropagationFilters += typeSetEntity → compactedFilters + _backwardPropagationFilters.put(typeSetEntity, compactedFilters) true } else { - val existingTypeFilters = _backwardPropagationFilters(typeSetEntity) + val existingTypeFilters = _backwardPropagationFilters.get(typeSetEntity) val newFilters = rootTypes(existingTypeFilters union typeFilters) - _backwardPropagationFilters.update(typeSetEntity, newFilters) + _backwardPropagationFilters.put(typeSetEntity, newFilters) newFilters != existingTypeFilters } } def updateBackwardPropagationDependee(eps: EOptionP[TypeSetEntity, InstantiatedTypes]): Unit = { - _backwardPropagationDependees.update(eps.e, eps) + _backwardPropagationDependees.put(eps.e, eps) } def seenTypes(typeSetEntity: TypeSetEntity): Int = { - val dependee = _backwardPropagationDependees(typeSetEntity) + val dependee = _backwardPropagationDependees.get(typeSetEntity) if (dependee.hasUBP) dependee.ub.numElements else @@ -205,7 +211,7 @@ final class TypePropagationState( super.hasOpenDependencies || _ownInstantiatedTypesDependee.isRefinable || _calleeDependee.isRefinable || - _backwardPropagationDependees.nonEmpty + !_backwardPropagationDependees.isEmpty } override def dependees: Set[SomeEOptionP] = { @@ -218,7 +224,7 @@ final class TypePropagationState( // Note: The values are copied here. The "++" operator on List // forces immediate evaluation of the map values iterator. - dependees ++= _backwardPropagationDependees.valuesIterator + dependees ++= _backwardPropagationDependees.values().iterator().asScala dependees }