From 6b6c968aeaae65da153b952234aa412d0acbaef1 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 28 Jul 2016 23:51:39 +0300 Subject: [PATCH] Make bytecode parser to cache method references to reduce memory consumption --- .../teavm/dependency/DependencyChecker.java | 3 +- .../dependency/DependencyGraphBuilder.java | 3 +- .../java/org/teavm/dependency/Linker.java | 4 - .../java/org/teavm/model/Instruction.java | 4 - .../org/teavm/model/MethodDescriptor.java | 4 - .../java/org/teavm/model/ReferenceCache.java | 146 ++++++++++++++++++ .../main/java/org/teavm/parsing/Parser.java | 29 ++-- .../java/org/teavm/parsing/ProgramParser.java | 37 +++-- .../resource/ResourceClassHolderMapper.java | 8 +- .../org/teavm/resource/ResourceParser.java | 48 ------ 10 files changed, 190 insertions(+), 96 deletions(-) create mode 100644 core/src/main/java/org/teavm/model/ReferenceCache.java delete mode 100644 core/src/main/java/org/teavm/resource/ResourceParser.java diff --git a/core/src/main/java/org/teavm/dependency/DependencyChecker.java b/core/src/main/java/org/teavm/dependency/DependencyChecker.java index d41be30d8..e9cd537fb 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -48,6 +48,7 @@ import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Program; +import org.teavm.model.ReferenceCache; import org.teavm.model.ValueType; import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; @@ -156,7 +157,7 @@ public class DependencyChecker implements DependencyInfo { ClassNode node = new ClassNode(); org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(data); reader.accept(node, 0); - submitClass(Parser.parseClass(node)); + submitClass(new Parser(new ReferenceCache()).parseClass(node)); return node.name; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index db9c0726c..d8b839cd5 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -364,8 +364,7 @@ class DependencyGraphBuilder { return; } MethodReference methodRef = new MethodReference(className, methodDesc); - final MethodDependency methodDep = checker.linkMethod(methodRef, - new CallLocation(caller.getMethod(), location)); + MethodDependency methodDep = checker.linkMethod(methodRef, new CallLocation(caller.getMethod(), location)); if (!methodDep.isMissing() && knownMethods.add(methodRef)) { methodDep.use(); DependencyNode[] targetParams = methodDep.getVariables(); diff --git a/core/src/main/java/org/teavm/dependency/Linker.java b/core/src/main/java/org/teavm/dependency/Linker.java index 7e9bbd639..f6b7efaa7 100644 --- a/core/src/main/java/org/teavm/dependency/Linker.java +++ b/core/src/main/java/org/teavm/dependency/Linker.java @@ -29,10 +29,6 @@ import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.PutFieldInstruction; -/** - * - * @author Alexey Andreev - */ public class Linker { public void link(DependencyInfo dependency, ClassHolder cls) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { diff --git a/core/src/main/java/org/teavm/model/Instruction.java b/core/src/main/java/org/teavm/model/Instruction.java index 66b006634..4ba442207 100644 --- a/core/src/main/java/org/teavm/model/Instruction.java +++ b/core/src/main/java/org/teavm/model/Instruction.java @@ -17,10 +17,6 @@ package org.teavm.model; import org.teavm.model.instructions.InstructionVisitor; -/** - * - * @author Alexey Andreev - */ public abstract class Instruction { private BasicBlock basicBlock; private InstructionLocation location; diff --git a/core/src/main/java/org/teavm/model/MethodDescriptor.java b/core/src/main/java/org/teavm/model/MethodDescriptor.java index c185888e7..6fdbf3ee7 100644 --- a/core/src/main/java/org/teavm/model/MethodDescriptor.java +++ b/core/src/main/java/org/teavm/model/MethodDescriptor.java @@ -17,10 +17,6 @@ package org.teavm.model; import java.util.Arrays; -/** - * - * @author Alexey Andreev - */ public class MethodDescriptor { private String name; private ValueType[] signature; diff --git a/core/src/main/java/org/teavm/model/ReferenceCache.java b/core/src/main/java/org/teavm/model/ReferenceCache.java new file mode 100644 index 000000000..c8459188a --- /dev/null +++ b/core/src/main/java/org/teavm/model/ReferenceCache.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model; + +import java.util.HashMap; +import java.util.Map; + +public class ReferenceCache { + private Map referenceCache = new HashMap<>(); + private Map fieldRefenceCache = new HashMap<>(); + private Map descriptorCache = new HashMap<>(); + private Map valueTypeCache = new HashMap<>(); + private Map classCache = new HashMap<>(); + private Map referenceParseCache = new HashMap<>(); + private Map descriptorParseCache = new HashMap<>(); + private Map valueTypeParseCache = new HashMap<>(); + + public MethodReference getCached(MethodReference reference) { + MethodReference result = referenceCache.get(reference); + if (result == null) { + MethodDescriptor descriptor = getCached(reference.getDescriptor()); + String className = getCached(reference.getClassName()); + if (descriptor != reference.getDescriptor() || className != reference.getClassName()) { + result = new MethodReference(className, descriptor); + } else { + result = reference; + } + referenceCache.put(result, result); + } + return result; + } + + public MethodDescriptor getCached(MethodDescriptor descriptor) { + MethodDescriptor result = descriptorCache.get(descriptor); + if (result == null) { + result = descriptor; + ValueType[] signature = descriptor.getSignature(); + boolean signatureChanged = false; + for (int i = 0; i < signature.length; ++i) { + ValueType type = signature[i]; + if (type == null) { + continue; + } + ValueType cachedType = getCached(type); + if (type != cachedType) { + signatureChanged = true; + signature[i] = cachedType; + } + } + if (signatureChanged) { + result = new MethodDescriptor(descriptor.getName(), signature); + } + descriptorCache.put(result, result); + } + return result; + } + + public FieldReference getCached(FieldReference reference) { + FieldReference result = fieldRefenceCache.get(reference); + if (result == null) { + result = reference; + String classNameCached = getCached(reference.getClassName()); + String fieldNameCached = getCached(reference.getFieldName()); + if (classNameCached != reference.getClassName() || fieldNameCached != reference.getFieldName()) { + result = new FieldReference(classNameCached, fieldNameCached); + } + fieldRefenceCache.put(result, result); + } + return result; + } + + public ValueType getCached(ValueType valueType) { + if (valueType instanceof ValueType.Primitive) { + return valueType; + } + + ValueType result = valueTypeCache.get(valueType); + if (result == null) { + result = valueType; + if (result instanceof ValueType.Object) { + String className = ((ValueType.Object) result).getClassName(); + String cachedClassName = getCached(className); + if (cachedClassName != className) { + result = ValueType.object(cachedClassName); + } + } else if (result instanceof ValueType.Array) { + ValueType item = ((ValueType.Array) result).getItemType(); + ValueType cachedItem = getCached(item); + if (item != cachedItem) { + result = ValueType.arrayOf(cachedItem); + } + } + valueTypeCache.put(result, result); + } + return result; + } + + public String getCached(String className) { + String result = classCache.get(className); + if (result == null) { + result = className; + classCache.put(result, result); + } + return result; + } + + public MethodReference parseReferenceCached(String value) { + MethodReference result = referenceParseCache.get(value); + if (result == null) { + result = getCached(MethodReference.parse(value)); + referenceParseCache.put(value, result); + } + return result; + } + + public MethodDescriptor parseDescriptorCached(String value) { + MethodDescriptor result = descriptorParseCache.get(value); + if (result == null) { + result = getCached(MethodDescriptor.parse(value)); + descriptorParseCache.put(value, result); + } + return result; + } + + public ValueType parseValueTypeCached(String value) { + ValueType result = valueTypeParseCache.get(value); + if (result == null) { + result = getCached(ValueType.parse(value)); + valueTypeParseCache.put(value, result); + } + return result; + } +} diff --git a/core/src/main/java/org/teavm/parsing/Parser.java b/core/src/main/java/org/teavm/parsing/Parser.java index 0da023abe..4527cebc9 100644 --- a/core/src/main/java/org/teavm/parsing/Parser.java +++ b/core/src/main/java/org/teavm/parsing/Parser.java @@ -35,11 +35,14 @@ import org.teavm.model.util.PhiUpdater; import org.teavm.model.util.ProgramUtils; import org.teavm.optimization.UnreachableBasicBlockEliminator; -public final class Parser { - private Parser() { +public class Parser { + private ReferenceCache referenceCache; + + public Parser(ReferenceCache referenceCache) { + this.referenceCache = referenceCache; } - public static MethodHolder parseMethod(MethodNode node, String className, String fileName) { + public MethodHolder parseMethod(MethodNode node, String className, String fileName) { MethodNode nodeWithoutJsr = new MethodNode(Opcodes.ASM5, node.access, node.name, node.desc, node.signature, node.exceptions.toArray(new String[0])); JSRInlinerAdapter adapter = new JSRInlinerAdapter(nodeWithoutJsr, node.access, node.name, node.desc, @@ -50,7 +53,7 @@ public final class Parser { MethodHolder method = new MethodHolder(node.name, signature); parseModifiers(node.access, method); - ProgramParser programParser = new ProgramParser(); + ProgramParser programParser = new ProgramParser(referenceCache); programParser.setFileName(fileName); Program program = programParser.parse(node, className); new UnreachableBasicBlockEliminator().optimize(program); @@ -75,7 +78,7 @@ public final class Parser { return method; } - private static void applyDebugNames(Program program, PhiUpdater phiUpdater, ProgramParser parser, + private void applyDebugNames(Program program, PhiUpdater phiUpdater, ProgramParser parser, Variable[] argumentMapping) { if (program.basicBlockCount() == 0) { return; @@ -110,7 +113,7 @@ public final class Parser { } } - private static IntIntMap[] getBlockEntryVariableMappings(Program program, PhiUpdater phiUpdater, + private IntIntMap[] getBlockEntryVariableMappings(Program program, PhiUpdater phiUpdater, Variable[] argumentMapping) { class Step { int node; @@ -179,7 +182,7 @@ public final class Parser { return result; } - private static Variable[] applySignature(Program program, ValueType[] arguments) { + private Variable[] applySignature(Program program, ValueType[] arguments) { if (program.variableCount() == 0) { return new Variable[0]; } @@ -204,7 +207,7 @@ public final class Parser { return Arrays.copyOf(variableMap, index); } - public static ClassHolder parseClass(ClassNode node) { + public ClassHolder parseClass(ClassNode node) { ClassHolder cls = new ClassHolder(node.name.replace('/', '.')); parseModifiers(node.access, cls); if (node.superName != null) { @@ -238,7 +241,7 @@ public final class Parser { return cls; } - public static FieldHolder parseField(FieldNode node) { + public FieldHolder parseField(FieldNode node) { FieldHolder field = new FieldHolder(node.name); field.setType(ValueType.parse(node.desc)); field.setInitialValue(node.value); @@ -247,7 +250,7 @@ public final class Parser { return field; } - public static void parseModifiers(int access, ElementHolder member) { + public void parseModifiers(int access, ElementHolder member) { if ((access & Opcodes.ACC_PRIVATE) != 0) { member.setLevel(AccessLevel.PRIVATE); } else if ((access & Opcodes.ACC_PROTECTED) != 0) { @@ -306,7 +309,7 @@ public final class Parser { } } - private static void parseAnnotations(AnnotationContainer annotations, List visibleAnnotations, + private void parseAnnotations(AnnotationContainer annotations, List visibleAnnotations, List invisibleAnnotations) { List annotNodes = new ArrayList<>(); if (visibleAnnotations != null) { @@ -328,7 +331,7 @@ public final class Parser { } } - private static void parseAnnotationValues(AnnotationHolder annot, List values) { + private void parseAnnotationValues(AnnotationHolder annot, List values) { if (values == null) { return; } @@ -339,7 +342,7 @@ public final class Parser { } } - private static AnnotationValue parseAnnotationValue(Object value) { + private AnnotationValue parseAnnotationValue(Object value) { if (value instanceof String[]) { String[] enumInfo = (String[]) value; ValueType.Object object = (ValueType.Object) ValueType.parse(enumInfo[0]); diff --git a/core/src/main/java/org/teavm/parsing/ProgramParser.java b/core/src/main/java/org/teavm/parsing/ProgramParser.java index a6fb86437..19c41bd65 100644 --- a/core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -24,6 +24,7 @@ import org.teavm.model.util.InstructionTransitionExtractor; import org.teavm.model.util.ProgramUtils; public class ProgramParser { + private ReferenceCache referenceCache; private static final byte ROOT = 0; private static final byte SINGLE = 1; private static final byte DOUBLE_FIRST_HALF = 2; @@ -45,6 +46,10 @@ public class ProgramParser { private Map> localVariableMap = new HashMap<>(); private Map> variableDebugNames = new HashMap<>(); + public ProgramParser(ReferenceCache methodReferenceCache) { + this.referenceCache = methodReferenceCache; + } + private static class Step { public final int source; public final int target; @@ -389,9 +394,9 @@ public class ProgramParser { private ValueType parseType(String type) { if (type.startsWith("[")) { - return ValueType.parse(type); + return referenceCache.parseValueTypeCached(type); } else { - return ValueType.object(type.replace('/', '.')); + return referenceCache.getCached(ValueType.object(type.replace('/', '.'))); } } @@ -507,7 +512,8 @@ public class ProgramParser { insn.setReceiver(getVariable(returnType.getSize() == 2 ? pushDouble() : pushSingle())); } - insn.setMethod(new MethodDescriptor(name, MethodDescriptor.parseSignature(desc))); + insn.setMethod(referenceCache.getCached( + new MethodDescriptor(name, MethodDescriptor.parseSignature(desc)))); for (Object bsmArg : bsmArgs) { insn.getBootstrapArguments().add(convertConstant(bsmArg)); } @@ -531,7 +537,7 @@ public class ProgramParser { if (type.getSort() == Type.METHOD) { return new RuntimeConstant(MethodDescriptor.parseSignature(type.getDescriptor())); } else { - return new RuntimeConstant(ValueType.parse(type.getDescriptor())); + return new RuntimeConstant(referenceCache.parseValueTypeCached(type.getDescriptor())); } } else if (value instanceof Handle) { return new RuntimeConstant(parseHandle((Handle) value)); @@ -566,7 +572,8 @@ public class ProgramParser { for (int i = types.length - 1; i >= 0; --i) { args[--j] = types[i].getSize() == 2 ? getVariable(popDouble()) : getVariable(popSingle()); } - MethodDescriptor method = new MethodDescriptor(name, MethodDescriptor.parseSignature(desc)); + MethodDescriptor method = referenceCache.getCached( + new MethodDescriptor(name, MethodDescriptor.parseSignature(desc))); int instance = -1; if (opcode != Opcodes.INVOKESTATIC) { instance = popSingle(); @@ -579,7 +586,7 @@ public class ProgramParser { if (instance == -1) { InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(ownerCls, method)); + insn.setMethod(referenceCache.getCached(new MethodReference(ownerCls, method))); if (result >= 0) { insn.setReceiver(getVariable(result)); } @@ -592,7 +599,7 @@ public class ProgramParser { } else { insn.setType(InvocationType.VIRTUAL); } - insn.setMethod(new MethodReference(ownerCls, method)); + insn.setMethod(referenceCache.getCached(new MethodReference(ownerCls, method))); if (result >= 0) { insn.setReceiver(getVariable(result)); } @@ -656,7 +663,7 @@ public class ProgramParser { addInstruction(insn); } else if (cst instanceof Type) { ClassConstantInstruction insn = new ClassConstantInstruction(); - insn.setConstant(ValueType.parse(((Type) cst).getDescriptor())); + insn.setConstant(referenceCache.getCached(ValueType.parse(((Type) cst).getDescriptor()))); insn.setReceiver(getVariable(pushSingle())); addInstruction(insn); } else { @@ -1652,11 +1659,11 @@ public class ProgramParser { switch (opcode) { case Opcodes.GETFIELD: { int instance = popSingle(); - ValueType type = ValueType.parse(desc); + ValueType type = referenceCache.parseValueTypeCached(desc); int value = desc.equals("D") || desc.equals("J") ? pushDouble() : pushSingle(); GetFieldInstruction insn = new GetFieldInstruction(); insn.setInstance(getVariable(instance)); - insn.setField(new FieldReference(ownerCls, name)); + insn.setField(referenceCache.getCached(new FieldReference(ownerCls, name))); insn.setFieldType(type); insn.setReceiver(getVariable(value)); addInstruction(insn); @@ -1667,17 +1674,17 @@ public class ProgramParser { int instance = popSingle(); PutFieldInstruction insn = new PutFieldInstruction(); insn.setInstance(getVariable(instance)); - insn.setField(new FieldReference(ownerCls, name)); + insn.setField(referenceCache.getCached(new FieldReference(ownerCls, name))); insn.setValue(getVariable(value)); - insn.setFieldType(ValueType.parse(desc)); + insn.setFieldType(referenceCache.parseValueTypeCached(desc)); addInstruction(insn); break; } case Opcodes.GETSTATIC: { - ValueType type = ValueType.parse(desc); + ValueType type = referenceCache.parseValueTypeCached(desc); int value = desc.equals("D") || desc.equals("J") ? pushDouble() : pushSingle(); GetFieldInstruction insn = new GetFieldInstruction(); - insn.setField(new FieldReference(ownerCls, name)); + insn.setField(referenceCache.getCached(new FieldReference(ownerCls, name))); insn.setFieldType(type); insn.setReceiver(getVariable(value)); addInstruction(insn); @@ -1691,7 +1698,7 @@ public class ProgramParser { } int value = desc.equals("D") || desc.equals("J") ? popDouble() : popSingle(); PutFieldInstruction insn = new PutFieldInstruction(); - insn.setField(new FieldReference(ownerCls, name)); + insn.setField(referenceCache.getCached(new FieldReference(ownerCls, name))); insn.setValue(getVariable(value)); addInstruction(insn); break; diff --git a/core/src/main/java/org/teavm/resource/ResourceClassHolderMapper.java b/core/src/main/java/org/teavm/resource/ResourceClassHolderMapper.java index b1c89dfe8..b0a268fe3 100644 --- a/core/src/main/java/org/teavm/resource/ResourceClassHolderMapper.java +++ b/core/src/main/java/org/teavm/resource/ResourceClassHolderMapper.java @@ -21,13 +21,11 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.teavm.common.Mapper; import org.teavm.model.ClassHolder; +import org.teavm.model.ReferenceCache; import org.teavm.parsing.Parser; -/** - * - * @author Alexey Andreev - */ public class ResourceClassHolderMapper implements Mapper { + private Parser parser = new Parser(new ReferenceCache()); private ResourceReader resourceReader; public ResourceClassHolderMapper(ResourceReader resourceReader) { @@ -47,6 +45,6 @@ public class ResourceClassHolderMapper implements Mapper { } catch (IOException e) { throw new RuntimeException(e); } - return Parser.parseClass(clsNode); + return parser.parseClass(clsNode); } } diff --git a/core/src/main/java/org/teavm/resource/ResourceParser.java b/core/src/main/java/org/teavm/resource/ResourceParser.java deleted file mode 100644 index 5d980b30e..000000000 --- a/core/src/main/java/org/teavm/resource/ResourceParser.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.resource; - -import java.io.IOException; -import java.io.InputStream; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.tree.ClassNode; -import org.teavm.common.Mapper; -import org.teavm.model.ClassHolder; -import org.teavm.parsing.Parser; - -/** - * - * @author Alexey Andreev - */ -public class ResourceParser implements Mapper { - private ResourceReader resourceReader; - - public ResourceParser(ResourceReader resourceReader) { - this.resourceReader = resourceReader; - } - - @Override - public ClassHolder map(String name) { - ClassNode clsNode = new ClassNode(); - try (InputStream input = resourceReader.openResource(name.replace('.', '/') + ".class")) { - ClassReader reader = new ClassReader(input); - reader.accept(clsNode, 0); - } catch (IOException e) { - throw new RuntimeException(e); - } - return Parser.parseClass(clsNode); - } -}