diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index ff008acd94..5fc1041552 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -61,7 +61,8 @@ * * @author Shai Almog */ -public class BytecodeMethod implements SignatureSet { +public class BytecodeMethod implements SignatureSet { + private static MethodDependencyGraph dependencyGraph; /** * @return the acceptStaticOnEquals @@ -76,6 +77,10 @@ public static boolean isAcceptStaticOnEquals() { public static void setAcceptStaticOnEquals(boolean aAcceptStaticOnEquals) { acceptStaticOnEquals = aAcceptStaticOnEquals; } + + public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { + BytecodeMethod.dependencyGraph = dependencyGraph; + } private List arguments = new ArrayList(); private Set localVariables = new HashSet(); private ByteCodeMethodArg returnType; @@ -120,6 +125,9 @@ public BytecodeMethod(String clsName, int access, String name, String desc, Stri staticMethod = (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC; finalMethod = (access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL; synchronizedMethod = (access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED; + if (dependencyGraph != null) { + dependencyGraph.registerMethod(this); + } int pos = desc.lastIndexOf(')'); if (!staticMethod) { if (!dependentClasses.contains("java_lang_NullPointerException")) { @@ -350,20 +358,40 @@ public boolean isMethodUsedByNative(String[] nativeSources, ByteCodeClass cls) { private Set usedMethods; public boolean isMethodUsedOldWay(BytecodeMethod bm) { - if(usedMethods == null) { - usedMethods = new TreeSet(); - for(Instruction ins : instructions) { - String s = ins.getMethodUsed(); - if(s != null && !usedMethods.contains(s)) { - usedMethods.add(s); - } - } - } + ensureUsedMethodsInitialized(); if(bm.methodName.equals("__INIT__")) { return usedMethods.contains(bm.desc + "."); } return usedMethods.contains(bm.desc + "." + bm.methodName); } + + public Set getCalledMethodSignatures() { + ensureUsedMethodsInitialized(); + return usedMethods; + } + + public String getLookupSignature() { + if(methodName.equals("__INIT__")) { + return desc + "."; + } + if(methodName.equals("__CLINIT__")) { + return desc + "."; + } + return desc + "." + methodName; + } + + private void ensureUsedMethodsInitialized() { + if(usedMethods != null) { + return; + } + usedMethods = new TreeSet(); + for(Instruction ins : instructions) { + String s = ins.getMethodUsed(); + if(s != null && !usedMethods.contains(s)) { + usedMethods.add(s); + } + } + } public void findWritableFields(Set outSet) { int len = instructions.size(); @@ -1049,6 +1077,12 @@ public void setMaxes(int maxStack, int maxLocals) { private void addInstruction(Instruction i) { instructions.add(i); i.addDependencies(dependentClasses); + if (dependencyGraph != null) { + String methodUsed = i.getMethodUsed(); + if (methodUsed != null) { + dependencyGraph.recordMethodCall(this, methodUsed); + } + } } public void addVariableOperation(int opcode, int var) { diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/MethodDependencyGraph.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/MethodDependencyGraph.java new file mode 100644 index 0000000000..d21879d4ef --- /dev/null +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/MethodDependencyGraph.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package com.codename1.tools.translator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Maintains a hierarchical dependency index between classes and methods so we + * can cheaply look up callers and prune eliminated elements without rescanning + * bytecode. + */ +public class MethodDependencyGraph { + private final Map> callersByLookupSignature = new HashMap>(); + private final Map> methodToCalls = new HashMap>(); + private final Map> methodsByClass = new HashMap>(); + + public void clear() { + callersByLookupSignature.clear(); + methodToCalls.clear(); + methodsByClass.clear(); + } + + public void registerMethod(BytecodeMethod method) { + Set byClass = methodsByClass.get(method.getClsName()); + if (byClass == null) { + byClass = new HashSet(); + methodsByClass.put(method.getClsName(), byClass); + } + byClass.add(method); + } + + public void recordMethodCall(BytecodeMethod caller, String calleeSignature) { + Set callers = callersByLookupSignature.get(calleeSignature); + if (callers == null) { + callers = new HashSet(); + callersByLookupSignature.put(calleeSignature, callers); + } + callers.add(caller); + + Set calls = methodToCalls.get(caller); + if (calls == null) { + calls = new HashSet(); + methodToCalls.put(caller, calls); + } + calls.add(calleeSignature); + } + + public List getCallers(String calleeSignature) { + Set callers = callersByLookupSignature.get(calleeSignature); + if (callers == null) { + return new ArrayList(); + } + return new ArrayList(callers); + } + + public void removeMethod(BytecodeMethod method) { + Set calls = methodToCalls.remove(method); + if (calls != null) { + for (String call : calls) { + Set callers = callersByLookupSignature.get(call); + if (callers != null) { + callers.remove(method); + if (callers.isEmpty()) { + callersByLookupSignature.remove(call); + } + } + } + } + + Set byClass = methodsByClass.get(method.getClsName()); + if (byClass != null) { + byClass.remove(method); + if (byClass.isEmpty()) { + methodsByClass.remove(method.getClsName()); + } + } + } + + public void removeClass(String clsName) { + Set methods = methodsByClass.remove(clsName); + if (methods != null) { + for (BytecodeMethod method : new ArrayList(methods)) { + removeMethod(method); + } + } + } +} diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index dbc8d9f510..c5c5603190 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -50,16 +50,20 @@ public class Parser extends ClassVisitor { private String clsName; private static String[] nativeSources; private static List classes = new ArrayList(); + private static MethodDependencyGraph dependencyGraph = new MethodDependencyGraph(); private int lambdaCounter; public static void cleanup() { - nativeSources = null; - classes.clear(); - LabelInstruction.cleanup(); + nativeSources = null; + classes.clear(); + dependencyGraph.clear(); + BytecodeMethod.setDependencyGraph(null); + LabelInstruction.cleanup(); } public static void parse(File sourceFile) throws Exception { if(ByteCodeTranslator.verbose) { System.out.println("Parsing: " + sourceFile.getAbsolutePath()); } + BytecodeMethod.setDependencyGraph(dependencyGraph); ClassReader r = new ClassReader(new FileInputStream(sourceFile)); /*if(ByteCodeTranslator.verbose) { System.out.println("Class: " + r.getClassName() + " derives from: " + r.getSuperName() + " interfaces: " + Arrays.asList(r.getInterfaces())); @@ -409,8 +413,8 @@ public static void writeOutput(File outputDirectory) throws Exception { } } } - - // load the native sources (including user native code) + + // load the native sources (including user native code) // We need to load native sources before we clear any unmarked classes // because native source may be the only thing referencing a class, // and the class may be purged before it even has a shot. @@ -499,8 +503,8 @@ private static int eliminateUnusedMethods(boolean forceFound, int depth) { } private static int cullMethods() { - int nfound = 0; - for(ByteCodeClass bc : classes) { + int nfound = 0; + for(ByteCodeClass bc : classes) { bc.unmark(); if(bc.isIsInterface() || bc.getBaseClass() == null) { continue; @@ -519,6 +523,7 @@ private static int cullMethods() { continue; } mtd.setEliminated(true); + dependencyGraph.removeMethod(mtd); nfound++; } } @@ -526,7 +531,7 @@ private static int cullMethods() { } return nfound; } - + private static boolean isMethodUsedByBaseClassOrInterface(BytecodeMethod mtd, ByteCodeClass cls) { boolean b = checkMethodUsedByBaseClassOrInterface(mtd, cls.getBaseClassObject()); if(b) { @@ -596,6 +601,7 @@ private static int cullClasses(boolean found, int depth) { int nfound = 0; for (ByteCodeClass cls : removedClasses) { nfound += cls.setEliminated(true); + dependencyGraph.removeClass(cls.getClsName()); } classes = tmp; return nfound + eliminateUnusedMethods(nfound > 0, depth + 1); @@ -613,14 +619,13 @@ private static boolean isMethodUsed(BytecodeMethod m, ByteCodeClass cls) { if (!m.isEliminated() && m.isMethodUsedByNative(nativeSources, cls)) { return true; } - for(ByteCodeClass bc : classes) { - for(BytecodeMethod mtd : bc.getMethods()) { - if(mtd.isEliminated() || mtd == m) { - continue; - } - if(mtd.isMethodUsed(m)) { - return true; - } + List callers = dependencyGraph.getCallers(m.getLookupSignature()); + for (BytecodeMethod caller : callers) { + if(caller.isEliminated() || caller == m) { + continue; + } + if(caller.isMethodUsed(m)) { + return true; } } return false;