| // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.graph; |
| |
| import com.android.tools.r8.utils.Box; |
| import com.android.tools.r8.utils.IteratorUtils; |
| import com.android.tools.r8.utils.TraversalContinuation; |
| import com.google.common.collect.Lists; |
| import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; |
| import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| public class MethodMapBacking extends MethodCollectionBacking { |
| |
| private SortedMap<DexMethodSignature, DexEncodedMethod> methodMap; |
| |
| public MethodMapBacking() { |
| this(createdLinkedMap()); |
| } |
| |
| private MethodMapBacking(SortedMap<DexMethodSignature, DexEncodedMethod> methodMap) { |
| this.methodMap = methodMap; |
| } |
| |
| public static MethodMapBacking createSorted() { |
| return new MethodMapBacking(new Object2ReferenceRBTreeMap<>()); |
| } |
| |
| private static SortedMap<DexMethodSignature, DexEncodedMethod> createdLinkedMap() { |
| // Maintain a linked map so the output order remains a deterministic function of the input. |
| return new Object2ReferenceLinkedOpenHashMap<>(); |
| } |
| |
| private static SortedMap<DexMethodSignature, DexEncodedMethod> createdLinkedMap(int capacity) { |
| // Maintain a linked map so the output order remains a deterministic function of the input. |
| return new Object2ReferenceLinkedOpenHashMap<>(capacity); |
| } |
| |
| private void replace(DexMethodSignature existingKey, DexEncodedMethod method) { |
| if (method.getReference().match(existingKey)) { |
| methodMap.put(existingKey, method); |
| } else { |
| methodMap.remove(existingKey); |
| methodMap.put(method.getSignature(), method); |
| } |
| } |
| |
| @Override |
| boolean verify() { |
| methodMap.forEach( |
| (signature, method) -> { |
| assert method.getReference().match(signature); |
| }); |
| return true; |
| } |
| |
| @Override |
| public int numberOfDirectMethods() { |
| return numberOfMethodsMatching(this::belongsToDirectPool); |
| } |
| |
| @Override |
| public int numberOfVirtualMethods() { |
| return numberOfMethodsMatching(this::belongsToVirtualPool); |
| } |
| |
| private int numberOfMethodsMatching(Predicate<DexEncodedMethod> predicate) { |
| int count = 0; |
| for (DexEncodedMethod method : methodMap.values()) { |
| if (predicate.test(method)) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| @Override |
| int size() { |
| return methodMap.size(); |
| } |
| |
| @Override |
| TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) { |
| for (Entry<DexMethodSignature, DexEncodedMethod> entry : methodMap.entrySet()) { |
| TraversalContinuation result = fn.apply(entry.getValue()); |
| if (result.shouldBreak()) { |
| return result; |
| } |
| } |
| return TraversalContinuation.CONTINUE; |
| } |
| |
| @Override |
| Iterable<DexEncodedMethod> methods() { |
| return methodMap.values(); |
| } |
| |
| @Override |
| Iterable<DexEncodedMethod> directMethods() { |
| return () -> IteratorUtils.filter(methodMap.values().iterator(), this::belongsToDirectPool); |
| } |
| |
| @Override |
| Iterable<DexEncodedMethod> virtualMethods() { |
| return () -> IteratorUtils.filter(methodMap.values().iterator(), this::belongsToVirtualPool); |
| } |
| |
| @Override |
| DexEncodedMethod getMethod(DexProto methodProto, DexString methodName) { |
| return methodMap.get(DexMethodSignature.create(methodName, methodProto)); |
| } |
| |
| private DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) { |
| Box<DexEncodedMethod> found = new Box<>(); |
| traverse( |
| method -> { |
| if (predicate.test(method)) { |
| found.set(method); |
| return TraversalContinuation.BREAK; |
| } |
| return TraversalContinuation.CONTINUE; |
| }); |
| return found.get(); |
| } |
| |
| @Override |
| DexEncodedMethod getDirectMethod(DexMethod method) { |
| DexEncodedMethod definition = getMethod(method.getProto(), method.getName()); |
| return definition != null && belongsToDirectPool(definition) ? definition : null; |
| } |
| |
| @Override |
| DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) { |
| Predicate<DexEncodedMethod> isDirect = this::belongsToDirectPool; |
| return getMethod(isDirect.and(predicate)); |
| } |
| |
| @Override |
| DexEncodedMethod getVirtualMethod(DexMethod method) { |
| DexEncodedMethod definition = getMethod(method.getProto(), method.getName()); |
| return definition != null && belongsToVirtualPool(definition) ? definition : null; |
| } |
| |
| @Override |
| DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) { |
| Predicate<DexEncodedMethod> isVirtual = this::belongsToVirtualPool; |
| return getMethod(isVirtual.and(predicate)); |
| } |
| |
| @Override |
| void addMethod(DexEncodedMethod method) { |
| DexMethodSignature key = method.getSignature(); |
| DexEncodedMethod old = methodMap.put(key, method); |
| assert old == null; |
| } |
| |
| @Override |
| void addDirectMethod(DexEncodedMethod method) { |
| assert belongsToDirectPool(method); |
| addMethod(method); |
| } |
| |
| @Override |
| void addVirtualMethod(DexEncodedMethod method) { |
| assert belongsToVirtualPool(method); |
| addMethod(method); |
| } |
| |
| @Override |
| void addDirectMethods(Collection<DexEncodedMethod> methods) { |
| for (DexEncodedMethod method : methods) { |
| addDirectMethod(method); |
| } |
| } |
| |
| @Override |
| void addVirtualMethods(Collection<DexEncodedMethod> methods) { |
| for (DexEncodedMethod method : methods) { |
| addVirtualMethod(method); |
| } |
| } |
| |
| @Override |
| void clearDirectMethods() { |
| methodMap.values().removeIf(this::belongsToDirectPool); |
| } |
| |
| @Override |
| void clearVirtualMethods() { |
| methodMap.values().removeIf(this::belongsToVirtualPool); |
| } |
| |
| @Override |
| DexEncodedMethod removeMethod(DexMethod method) { |
| return methodMap.remove(method.getSignature()); |
| } |
| |
| @Override |
| void removeMethods(Set<DexEncodedMethod> methods) { |
| methods.forEach(method -> methodMap.remove(method.getSignature())); |
| } |
| |
| @Override |
| void setDirectMethods(DexEncodedMethod[] methods) { |
| if ((methods == null || methods.length == 0) && methodMap.isEmpty()) { |
| return; |
| } |
| if (methods == null) { |
| methods = DexEncodedMethod.EMPTY_ARRAY; |
| } |
| SortedMap<DexMethodSignature, DexEncodedMethod> newMap = |
| createdLinkedMap(size() + methods.length); |
| forEachMethod( |
| method -> { |
| if (belongsToVirtualPool(method)) { |
| newMap.put(method.getSignature(), method); |
| } |
| }); |
| for (DexEncodedMethod method : methods) { |
| assert belongsToDirectPool(method); |
| newMap.put(method.getSignature(), method); |
| } |
| methodMap = newMap; |
| } |
| |
| @Override |
| void setVirtualMethods(DexEncodedMethod[] methods) { |
| if ((methods == null || methods.length == 0) && methodMap.isEmpty()) { |
| return; |
| } |
| if (methods == null) { |
| methods = DexEncodedMethod.EMPTY_ARRAY; |
| } |
| SortedMap<DexMethodSignature, DexEncodedMethod> newMap = |
| createdLinkedMap(size() + methods.length); |
| forEachMethod( |
| method -> { |
| if (belongsToDirectPool(method)) { |
| newMap.put(method.getSignature(), method); |
| } |
| }); |
| for (DexEncodedMethod method : methods) { |
| assert belongsToVirtualPool(method); |
| newMap.put(method.getSignature(), method); |
| } |
| methodMap = newMap; |
| } |
| |
| @Override |
| void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| // The code assumes that when replacement.apply(method) is called, the map is up-to-date with |
| // the previously replaced methods. We therefore cannot postpone the map updates to the end of |
| // the method. |
| ArrayList<DexEncodedMethod> initialValues = new ArrayList<>(methodMap.values()); |
| for (DexEncodedMethod method : initialValues) { |
| DexEncodedMethod newMethod = replacement.apply(method); |
| if (newMethod != method) { |
| removeMethod(method.getReference()); |
| addMethod(newMethod); |
| } |
| } |
| } |
| |
| @Override |
| void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| replaceMethods(method -> belongsToDirectPool(method) ? replacement.apply(method) : method); |
| } |
| |
| @Override |
| void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| replaceMethods(method -> belongsToVirtualPool(method) ? replacement.apply(method) : method); |
| } |
| |
| @Override |
| void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| List<DexEncodedMethod> oldMethods = Lists.newArrayList(directMethods()); |
| clearDirectMethods(); |
| List<DexEncodedMethod> newMethods = new ArrayList<>(oldMethods.size()); |
| for (DexEncodedMethod method : oldMethods) { |
| newMethods.add(replacement.apply(method)); |
| } |
| addDirectMethods(newMethods); |
| } |
| |
| @Override |
| void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| List<DexEncodedMethod> oldMethods = Lists.newArrayList(virtualMethods()); |
| clearVirtualMethods(); |
| List<DexEncodedMethod> newMethods = new ArrayList<>(oldMethods.size()); |
| for (DexEncodedMethod method : oldMethods) { |
| newMethods.add(replacement.apply(method)); |
| } |
| addVirtualMethods(newMethods); |
| } |
| |
| @Override |
| DexEncodedMethod replaceDirectMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| return replaceMethod(method, replacement, this::belongsToDirectPool); |
| } |
| |
| @Override |
| DexEncodedMethod replaceVirtualMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| return replaceMethod(method, replacement, this::belongsToVirtualPool); |
| } |
| |
| private DexEncodedMethod replaceMethod( |
| DexMethod method, |
| Function<DexEncodedMethod, DexEncodedMethod> replacement, |
| Predicate<DexEncodedMethod> predicate) { |
| DexMethodSignature key = method.getSignature(); |
| DexEncodedMethod existing = methodMap.get(key); |
| if (existing == null || !predicate.test(existing)) { |
| return null; |
| } |
| DexEncodedMethod newMethod = replacement.apply(existing); |
| assert predicate.test(newMethod); |
| replace(key, newMethod); |
| return newMethod; |
| } |
| |
| @Override |
| DexEncodedMethod replaceDirectMethodWithVirtualMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| DexMethodSignature key = method.getSignature(); |
| DexEncodedMethod existing = methodMap.get(key); |
| if (existing == null || belongsToVirtualPool(existing)) { |
| return null; |
| } |
| DexEncodedMethod newMethod = replacement.apply(existing); |
| assert belongsToVirtualPool(newMethod); |
| replace(key, newMethod); |
| return newMethod; |
| } |
| |
| @Override |
| void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) { |
| // This is a no-op as the virtualizer has modified the encoded method bits. |
| assert verifyVirtualizedMethods(privateInstanceMethods); |
| } |
| |
| private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) { |
| for (DexEncodedMethod method : methods) { |
| assert belongsToVirtualPool(method); |
| assert methodMap.get(method.getSignature()) == method; |
| } |
| return true; |
| } |
| } |