1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.utils; 5 6 import com.android.tools.r8.errors.CompilationError; 7 import com.android.tools.r8.errors.Unreachable; 8 import com.android.tools.r8.graph.ClassKind; 9 import com.android.tools.r8.graph.DexClass; 10 import com.android.tools.r8.graph.DexType; 11 import com.google.common.collect.Sets; 12 import java.util.ArrayList; 13 import java.util.IdentityHashMap; 14 import java.util.Iterator; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Set; 18 import java.util.function.Predicate; 19 import java.util.function.Supplier; 20 21 /** 22 * Represents a collection of classes. Collection can be fully loaded, 23 * lazy loaded or have preloaded classes along with lazy loaded content. 24 */ 25 public abstract class ClassMap<T extends DexClass> { 26 // For each type which has ever been queried stores one class loaded from 27 // resources provided by different resource providers. 28 // 29 // NOTE: all access must be synchronized on `classes`. 30 private final Map<DexType, Supplier<T>> classes; 31 32 // Class provider if available. 33 // 34 // If the class provider is `null` it indicates that all classes are already present 35 // in a map referenced by `classes` and thus the collection is fully loaded. 36 // 37 // NOTE: all access must be synchronized on `classes`. 38 private ClassProvider<T> classProvider; 39 ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider)40 ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider) { 41 this.classes = classes == null ? new IdentityHashMap<>() : classes; 42 this.classProvider = classProvider; 43 assert this.classProvider == null || this.classProvider.getClassKind() == getClassKind(); 44 } 45 46 /** Resolves a class conflict by selecting a class, may generate compilation error. */ resolveClassConflict(T a, T b)47 abstract T resolveClassConflict(T a, T b); 48 49 /** Return supplier for preloaded class. */ getTransparentSupplier(T clazz)50 abstract Supplier<T> getTransparentSupplier(T clazz); 51 52 /** Kind of the classes supported by this collection. */ getClassKind()53 abstract ClassKind getClassKind(); 54 55 @Override toString()56 public String toString() { 57 synchronized (classes) { 58 return classes.size() + " loaded, provider: " + 59 (classProvider == null ? "none" : classProvider.toString()); 60 } 61 } 62 63 /** Returns a definition for a class or `null` if there is no such class in the collection. */ get(DexType type)64 public T get(DexType type) { 65 Supplier<T> supplier; 66 67 synchronized (classes) { 68 supplier = classes.get(type); 69 70 // Get class supplier, create it if it does not 71 // exist and the collection is NOT fully loaded. 72 if (supplier == null) { 73 if (classProvider == null) { 74 // There is no supplier, but the collection is fully loaded. 75 return null; 76 } 77 78 supplier = new ConcurrentClassLoader<>(this, this.classProvider, type); 79 classes.put(type, supplier); 80 } 81 } 82 83 return supplier.get(); 84 } 85 86 /** Returns all classes from the collection. The collection must be force-loaded. */ getAllClasses()87 public List<T> getAllClasses() { 88 List<T> loadedClasses = new ArrayList<>(); 89 synchronized (classes) { 90 if (classProvider != null) { 91 throw new Unreachable("Getting all classes from not fully loaded collection."); 92 } 93 for (Supplier<T> supplier : classes.values()) { 94 // Since the class map is fully loaded, all suppliers must be 95 // loaded and non-null. 96 T clazz = supplier.get(); 97 assert clazz != null; 98 loadedClasses.add(clazz); 99 } 100 } 101 return loadedClasses; 102 } 103 104 /** 105 * Forces loading of all the classes satisfying the criteria specified. 106 * 107 * NOTE: after this method finishes, the class map is considered to be fully-loaded 108 * and thus sealed. This has one side-effect: if we filter out some of the classes 109 * with `load` predicate, these classes will never be loaded. 110 */ forceLoad(Predicate<DexType> load)111 public void forceLoad(Predicate<DexType> load) { 112 Set<DexType> knownClasses; 113 ClassProvider<T> classProvider; 114 115 synchronized (classes) { 116 classProvider = this.classProvider; 117 if (classProvider == null) { 118 return; 119 } 120 121 // Collects the types which might be represented in fully loaded class map. 122 knownClasses = Sets.newIdentityHashSet(); 123 knownClasses.addAll(classes.keySet()); 124 } 125 126 // Add all types the class provider provides. Note that it may take time for class 127 // provider to collect these types, so ve do it outside synchronized context. 128 knownClasses.addAll(classProvider.collectTypes()); 129 130 // Make sure all the types in `knownClasses` are loaded. 131 // 132 // We just go and touch every class, thus triggering their loading if they 133 // are not loaded so far. In case the class has already been loaded, 134 // touching the class will be a no-op with minimal overhead. 135 for (DexType type : knownClasses) { 136 if (load.test(type)) { 137 get(type); 138 } 139 } 140 141 synchronized (classes) { 142 if (this.classProvider == null) { 143 return; // Has been force-loaded concurrently. 144 } 145 146 // We avoid calling get() on a class supplier unless we know it was loaded. 147 // At this time `classes` may have more types then `knownClasses`, but for 148 // all extra classes we expect the supplier to return 'null' after loading. 149 Iterator<Map.Entry<DexType, Supplier<T>>> iterator = classes.entrySet().iterator(); 150 while (iterator.hasNext()) { 151 Map.Entry<DexType, Supplier<T>> e = iterator.next(); 152 153 if (knownClasses.contains(e.getKey())) { 154 // Get the class (it is expected to be loaded by this time). 155 T clazz = e.getValue().get(); 156 if (clazz != null) { 157 // Since the class is already loaded, get rid of possible wrapping suppliers. 158 assert clazz.type == e.getKey(); 159 e.setValue(getTransparentSupplier(clazz)); 160 continue; 161 } 162 } 163 164 // If the type is not in `knownClasses` or resolves to `null`, 165 // just remove the record from the map. 166 iterator.remove(); 167 } 168 169 // Mark the class map as fully loaded. 170 this.classProvider = null; 171 } 172 } 173 174 // Supplier implementing a thread-safe loader for a class loaded from a 175 // class provider. Helps avoid synchronizing on the whole class map 176 // when loading a class. 177 private static class ConcurrentClassLoader<T extends DexClass> implements Supplier<T> { 178 private ClassMap<T> classMap; 179 private ClassProvider<T> provider; 180 private DexType type; 181 182 private T clazz = null; 183 private volatile boolean ready = false; 184 ConcurrentClassLoader(ClassMap<T> classMap, ClassProvider<T> provider, DexType type)185 ConcurrentClassLoader(ClassMap<T> classMap, ClassProvider<T> provider, DexType type) { 186 this.classMap = classMap; 187 this.provider = provider; 188 this.type = type; 189 } 190 191 @Override get()192 public T get() { 193 if (ready) { 194 return clazz; 195 } 196 197 synchronized (this) { 198 if (!ready) { 199 assert classMap != null && provider != null && type != null; 200 provider.collectClass(type, createdClass -> { 201 assert createdClass != null; 202 assert classMap.getClassKind().isOfKind(createdClass); 203 assert !ready; 204 205 if (createdClass.type != type) { 206 throw new CompilationError( 207 "Class content provided for type descriptor " + type.toSourceString() + 208 " actually defines class " + createdClass.type.toSourceString()); 209 } 210 211 if (clazz == null) { 212 clazz = createdClass; 213 } else { 214 // The class resolution *may* generate a compilation error as one of 215 // possible resolutions. In this case we leave `value` in (false, null) 216 // state so in rare case of another thread trying to get the same class 217 // before this error is propagated it will get the same conflict. 218 T oldClass = clazz; 219 clazz = null; 220 clazz = classMap.resolveClassConflict(oldClass, createdClass); 221 } 222 }); 223 224 classMap = null; 225 provider = null; 226 type = null; 227 ready = true; 228 } 229 } 230 231 assert ready; 232 assert classMap == null && provider == null && type == null; 233 return clazz; 234 } 235 } 236 } 237