Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public Object createImplementation() {
implementation = TestCodenameOneImplementation.getInstance();
implementation.setLocalizationManager(new SafeL10NManager("en", "US"));
}
display = Display.getInstance();
Util.setImplementation(implementation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ public static void setAcceptStaticOnEquals(boolean aAcceptStaticOnEquals) {
private boolean virtualOverriden;
private boolean finalMethod;
private boolean synchronizedMethod;
private final static Set<String> virtualMethodsInvoked = new TreeSet<String>();
private final static Set<String> virtualMethodsInvoked = new TreeSet<String>();
private String desc;
private boolean eliminated;
private Set<String> calledMethodSignatureKeys;


static boolean optimizerOn;
Expand Down Expand Up @@ -347,7 +348,39 @@ public boolean isMethodUsedByNative(String[] nativeSources, ByteCodeClass cls) {
usedByNative = false;
return false;
}


private static String toMethodKey(String signature, String name) {
if (signature == null || name == null) {
return null;
}
if ("__INIT__".equals(name)) {
return signature + ".<init>";
}
if ("__CLINIT__".equals(name)) {
return signature + ".<clinit>";
}
return signature + "." + name;
}

public String getMethodUsageKey() {
return toMethodKey(desc, methodName);
}

public Set<String> getCalledMethodSignatureKeys() {
if (calledMethodSignatureKeys == null) {
calledMethodSignatureKeys = new HashSet<String>();
for (Instruction ins : instructions) {
String name = ins.getMethodName();
String signature = ins.getSignature();
String key = toMethodKey(signature, name);
if (key != null) {
calledMethodSignatureKeys.add(key);
}
}
}
return calledMethodSignatureKeys;
}

private Set<String> usedMethods;
public boolean isMethodUsedOldWay(BytecodeMethod bm) {
if(usedMethods == null) {
Expand Down
103 changes: 87 additions & 16 deletions vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,17 @@ public class Parser extends ClassVisitor {
private String clsName;
private static String[] nativeSources;
private static List<ByteCodeClass> classes = new ArrayList<ByteCodeClass>();
private static Map<String, Integer> methodUsageCounts;
private static Map<BytecodeMethod, Set<String>> methodCallMap;
private static Set<BytecodeMethod> removedFromUsageIndex;
private int lambdaCounter;
public static void cleanup() {
nativeSources = null;
classes.clear();
LabelInstruction.cleanup();
nativeSources = null;
classes.clear();
methodUsageCounts = null;
methodCallMap = null;
removedFromUsageIndex = null;
LabelInstruction.cleanup();
}
public static void parse(File sourceFile) throws Exception {
if(ByteCodeTranslator.verbose) {
Expand Down Expand Up @@ -485,14 +491,83 @@ public boolean accept(File file) {
nativeSources[iter] = new String(dat, "UTF-8");
}
System.out.println("Native files total "+(size/1024)+"K");

}


private static String buildMethodUsageKey(BytecodeMethod method) {
return method.getMethodUsageKey();
}

private static void ensureMethodUsageIndex() {
if (methodUsageCounts != null) {
return;
}
buildMethodUsageIndex();
}

private static Set<String> collectCalledMethods(BytecodeMethod caller) {
Set<String> calls = new HashSet<String>();
String callerKey = buildMethodUsageKey(caller);
for (String key : caller.getCalledMethodSignatureKeys()) {
if (!key.equals(callerKey)) {
calls.add(key);
}
}
return calls;
}

private static void buildMethodUsageIndex() {
methodUsageCounts = new HashMap<String, Integer>();
methodCallMap = new HashMap<BytecodeMethod, Set<String>>();
removedFromUsageIndex = new HashSet<BytecodeMethod>();

for (ByteCodeClass bc : classes) {
for (BytecodeMethod method : bc.getMethods()) {
if (method.isEliminated()) {
continue;
}
Set<String> calls = collectCalledMethods(method);
methodCallMap.put(method, calls);
for (String key : calls) {
Integer count = methodUsageCounts.get(key);
methodUsageCounts.put(key, count == null ? 1 : count + 1);
}
}
}
}

private static void removeFromMethodUsageIndex(BytecodeMethod method) {
if (removedFromUsageIndex == null) {
removedFromUsageIndex = new HashSet<BytecodeMethod>();
}
if (removedFromUsageIndex.contains(method)) {
return;
}
ensureMethodUsageIndex();
Set<String> calls = methodCallMap.remove(method);
if (calls == null) {
calls = collectCalledMethods(method);
}
for (String key : calls) {
Integer count = methodUsageCounts.get(key);
if (count == null) {
continue;
}
if (count <= 1) {
methodUsageCounts.remove(key);
} else {
methodUsageCounts.put(key, count - 1);
}
}
removedFromUsageIndex.add(method);
}

private static int eliminateUnusedMethods() {
return(eliminateUnusedMethods(false, 0));
}

private static int eliminateUnusedMethods(boolean forceFound, int depth) {
ensureMethodUsageIndex();
int nfound = cullMethods();
nfound += cullClasses(nfound>0 || forceFound, depth);
return(nfound);
Expand All @@ -519,6 +594,7 @@ private static int cullMethods() {
continue;
}
mtd.setEliminated(true);
removeFromMethodUsageIndex(mtd);
nfound++;
}
}
Expand Down Expand Up @@ -596,6 +672,9 @@ private static int cullClasses(boolean found, int depth) {
int nfound = 0;
for (ByteCodeClass cls : removedClasses) {
nfound += cls.setEliminated(true);
for (BytecodeMethod method : cls.getMethods()) {
removeFromMethodUsageIndex(method);
}
}
classes = tmp;
return nfound + eliminateUnusedMethods(nfound > 0, depth + 1);
Expand All @@ -613,17 +692,9 @@ 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;
}
}
}
return false;
ensureMethodUsageIndex();
Integer count = methodUsageCounts.get(buildMethodUsageKey(m));
return count != null && count > 0;
}

private static void writeFile(ByteCodeClass cls, File outputDir, ConcatenatingFileOutputStream writeBufferInstead) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.codename1.tools.translator;

import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class CullPerformanceTest {

@Test
void cullsLargeUnusedGraphQuickly() throws Exception {
Parser.cleanup();

Path classesDir = Files.createTempDirectory("cull-stress-classes");
int totalNodes = 300;
List<Path> generated = new ArrayList<Path>();
generated.add(writeStub(classesDir, "java/lang/Object"));

for (int i = 0; i < totalNodes; i++) {
generated.add(writeNode(classesDir, i, totalNodes));
}
generated.add(writeEntryPoint(classesDir, totalNodes));

for (Path classFile : generated) {
Parser.parse(classFile.toFile());
}

List<ByteCodeClass> parsedClasses = getParsedClasses();
for (ByteCodeClass bc : parsedClasses) {
bc.updateAllDependencies();
}
ByteCodeClass.markDependencies(parsedClasses, null);
List<ByteCodeClass> reachable = ByteCodeClass.clearUnmarked(parsedClasses);
setParsedClasses(reachable);

Method eliminateUnusedMethods = Parser.class.getDeclaredMethod("eliminateUnusedMethods");
eliminateUnusedMethods.setAccessible(true);

assertTimeoutPreemptively(Duration.ofSeconds(10), () -> eliminateUnusedMethods.invoke(null));

long remaining = reachable.stream().filter(bc -> !bc.isEliminated()).count();
assertTrue(remaining < totalNodes / 4, "Most generated classes should be culled to keep optimization fast");
}

private Path writeStub(Path classesDir, String internalName) throws Exception {
ClassWriter cw = new ClassWriter(0);
String superName = "java/lang/Object".equals(internalName) ? null : "java/lang/Object";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, internalName, null, superName, null);
addDefaultConstructor(cw, superName);
cw.visitEnd();
return writeClass(classesDir, internalName, cw.toByteArray());
}

private Path writeNode(Path classesDir, int index, int totalNodes) throws Exception {
String name = "com/example/stress/Node" + index;
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, "java/lang/Object", null);

addDefaultConstructor(cw, "java/lang/Object");

MethodVisitor value = cw.visitMethod(Opcodes.ACC_PUBLIC, "value", "(I)I", null, null);
value.visitCode();
value.visitVarInsn(Opcodes.ILOAD, 1);
value.visitIntInsn(Opcodes.SIPUSH, index);
value.visitInsn(Opcodes.IADD);

if (index + 1 < totalNodes && index % 3 == 0) {
String next = "com/example/stress/Node" + (index + 1);
value.visitTypeInsn(Opcodes.NEW, next);
value.visitInsn(Opcodes.DUP);
value.visitMethodInsn(Opcodes.INVOKESPECIAL, next, "<init>", "()V", false);
value.visitVarInsn(Opcodes.ILOAD, 1);
value.visitMethodInsn(Opcodes.INVOKEVIRTUAL, next, "value", "(I)I", false);
value.visitInsn(Opcodes.IADD);
}

value.visitInsn(Opcodes.IRETURN);
value.visitMaxs(3, 2);
value.visitEnd();

cw.visitEnd();
return writeClass(classesDir, name, cw.toByteArray());
}

private Path writeEntryPoint(Path classesDir, int totalNodes) throws Exception {
String name = "com/example/stress/StressEntry";
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, "java/lang/Object", null);

addDefaultConstructor(cw, "java/lang/Object");

MethodVisitor main = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
main.visitCode();
main.visitTypeInsn(Opcodes.NEW, "com/example/stress/Node0");
main.visitInsn(Opcodes.DUP);
main.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/example/stress/Node0", "<init>", "()V", false);
main.visitInsn(Opcodes.ICONST_0);
main.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/example/stress/Node0", "value", "(I)I", false);
main.visitInsn(Opcodes.POP);
main.visitInsn(Opcodes.RETURN);
main.visitMaxs(3, 1);
main.visitEnd();

cw.visitEnd();
return writeClass(classesDir, name, cw.toByteArray());
}

private void addDefaultConstructor(ClassWriter cw, String superName) {
MethodVisitor ctor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
ctor.visitCode();
if (superName != null) {
ctor.visitVarInsn(Opcodes.ALOAD, 0);
ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false);
}
ctor.visitInsn(Opcodes.RETURN);
ctor.visitMaxs(superName != null ? 1 : 0, 1);
ctor.visitEnd();
}

private Path writeClass(Path classesDir, String internalName, byte[] data) throws Exception {
Path classFile = classesDir.resolve(internalName + ".class");
Files.createDirectories(classFile.getParent());
Files.write(classFile, data);
return classFile;
}

@SuppressWarnings("unchecked")
private List<ByteCodeClass> getParsedClasses() throws Exception {
Field f = Parser.class.getDeclaredField("classes");
f.setAccessible(true);
return (List<ByteCodeClass>) f.get(null);
}

private void setParsedClasses(List<ByteCodeClass> updated) throws Exception {
Field f = Parser.class.getDeclaredField("classes");
f.setAccessible(true);
f.set(null, updated);
}
}
Loading