From d74bcbe2b9fd1f496ba2647898019dcd24b2c713 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 30 Nov 2018 19:53:12 +0300 Subject: [PATCH] Fast dependency analyzer, fix bugs in incremental compiler --- classlib/pom.xml | 21 + .../impl/ClassForNameTransformer.java | 34 +- .../org/teavm/classlib/impl/JCLPlugin.java | 3 - .../org/teavm/classlib/impl/JavacSupport.java | 50 -- .../impl/NumericClassTransformer.java | 5 +- .../classlib/impl/PlatformMarkerSupport.java | 14 +- .../impl/ReflectionDependencyListener.java | 80 ++-- .../org/teavm/classlib/impl/ScalaHacks.java | 12 +- .../classlib/impl/ServiceLoaderSupport.java | 61 +-- .../classlib/impl/SystemClassTransformer.java | 5 +- .../lambda/LambdaMetafactorySubstitutor.java | 58 ++- .../teavm/classlib/impl/tz/TimeZoneCache.java | 5 +- .../classlib/impl/tz/TimeZoneGenerator.java | 7 +- .../java/lang/ClassDependencyListener.java | 7 +- .../classlib/java/lang/ClassGenerator.java | 3 +- .../java/lang/ObjectDependencyPlugin.java | 3 +- .../java/lang/SystemNativeGenerator.java | 3 +- .../reflect/AnnotationDependencyListener.java | 43 +- .../lang/reflect/ArrayNativeGenerator.java | 22 +- .../cache/InMemoryRegularMethodNodeCache.java | 47 -- .../ast/decompilation/AsyncCallsFinder.java | 40 -- .../teavm/ast/decompilation/Decompiler.java | 55 +-- .../ast/decompilation/StatementGenerator.java | 7 +- .../java/org/teavm/backend/c/CTarget.java | 35 +- .../c/analyze/CDependencyListener.java | 12 +- .../backend/javascript/JavaScriptTarget.java | 35 +- .../NullPointerExceptionTransformer.java | 55 --- .../codegen/DefaultAliasProvider.java | 30 +- .../codegen/DefaultNamingStrategy.java | 2 +- .../rendering/NameFrequencyEstimator.java | 25 +- .../javascript/rendering/Renderer.java | 28 +- .../rendering/StatementRenderer.java | 20 +- .../org/teavm/backend/wasm/WasmTarget.java | 56 +-- .../wasm/generate/WasmDependencyListener.java | 16 +- .../AlwaysFreshCacheStatus.java} | 26 +- .../teavm/cache/AlwaysStaleCacheStatus.java | 35 ++ .../cache/AnnotationAwareCacheStatus.java | 98 ++++ .../teavm/cache/AstDependencyExtractor.java | 61 +++ core/src/main/java/org/teavm/cache/AstIO.java | 10 +- .../java/org/teavm/cache/CacheStatus.java | 24 + .../cache/DiskCachedClassHolderSource.java | 57 ++- ...odeCache.java => DiskMethodNodeCache.java} | 78 +-- .../org/teavm/cache/DiskProgramCache.java | 117 ++--- .../EmptyMethodNodeCache.java} | 20 +- .../org/teavm/cache/EmptyProgramCache.java | 37 ++ .../teavm/cache/InMemoryMethodNodeCache.java | 87 ++++ .../org/teavm/cache/InMemoryProgramCache.java | 57 +++ .../cache/IncrementalDependencyProvider.java | 28 ++ .../IncrementalDependencyRegistration.java | 28 ++ .../{ast => }/cache/MethodNodeCache.java | 13 +- .../cache/ProgramDependencyExtractor.java | 66 +++ .../main/java/org/teavm/cache/ProgramIO.java | 137 ++++-- .../org/teavm/callgraph/DefaultCallGraph.java | 2 +- .../teavm/callgraph/DefaultCallGraphNode.java | 2 +- .../callgraph/DefaultFieldAccessSite.java | 5 +- .../org/teavm/common/OptionalPredicate.java | 20 + .../information/DebugInformation.java | 15 +- .../AbstractDependencyListener.java | 8 +- .../AbstractInstructionAnalyzer.java | 279 +++++++++++ .../org/teavm/dependency/ClassDependency.java | 5 +- .../org/teavm/dependency/DependencyAgent.java | 26 +- .../teavm/dependency/DependencyAnalyzer.java | 452 +++++++++++++----- .../dependency/DependencyAnalyzerFactory.java | 25 + .../dependency/DependencyClassSource.java | 40 +- .../dependency/DependencyGraphBuilder.java | 451 ++--------------- .../org/teavm/dependency/DependencyInfo.java | 3 + .../teavm/dependency/DependencyListener.java | 8 +- .../org/teavm/dependency/DependencyNode.java | 84 ++-- .../teavm/dependency/DependencyPlugin.java | 4 +- .../org/teavm/dependency/DependencyType.java | 1 + .../dependency/DependencyTypeFilter.java | 6 + .../org/teavm/dependency/ExactTypeFilter.java | 10 +- .../dependency/FastDependencyAnalyzer.java | 191 ++++++++ .../dependency/FastInstructionAnalyzer.java | 71 +++ .../dependency/FastVirtualCallConsumer.java | 72 +++ .../org/teavm/dependency/FieldDependency.java | 37 ++ .../teavm/dependency/LocationListener.java | 22 + .../teavm/dependency/MethodDependency.java | 37 ++ .../dependency/PreciseDependencyAnalyzer.java | 106 ++++ .../teavm/dependency/SuperArrayFilter.java | 35 ++ .../teavm/dependency/SuperClassFilter.java | 39 +- .../java/org/teavm/dependency/Transition.java | 24 +- .../java/org/teavm/dependency/TypeSet.java | 35 +- .../teavm/dependency/VirtualCallConsumer.java | 88 ++++ .../java/org/teavm/diagnostics/Problem.java | 4 +- .../java/org/teavm/model/ClassHierarchy.java | 129 +++++ .../org/teavm/model/ClassHolderSource.java | 4 + .../teavm/model/ClassHolderTransformer.java | 4 +- .../model/ClassHolderTransformerContext.java | 27 ++ .../org/teavm/model/ClassReaderSource.java | 24 +- .../java/org/teavm/model/MethodHolder.java | 10 +- .../java/org/teavm/model/MethodReference.java | 2 +- .../java/org/teavm/model/ProgramCache.java | 9 +- .../java/org/teavm/model/ReferenceCache.java | 30 +- .../teavm/model/analysis/ClassInference.java | 9 +- .../org/teavm/model/emit/ProgramEmitter.java | 15 +- .../org/teavm/model/emit/ValueEmitter.java | 17 +- .../lowlevel/ExportDependencyListener.java | 29 +- .../model/optimization/Devirtualization.java | 7 +- .../model/transformation/ClassPatch.java | 5 +- .../java/org/teavm/model/util/ModelUtils.java | 25 +- .../org/teavm/model/util/ProgramUtils.java | 5 +- .../java/org/teavm/parsing/ProgramParser.java | 5 +- core/src/main/java/org/teavm/vm/TeaVM.java | 80 ++-- .../main/java/org/teavm/vm/TeaVMBuilder.java | 15 +- .../org/teavm/vm/TeaVMTargetController.java | 5 +- .../teavm/backend/javascript/simpleThread.js | 4 +- .../slf4j/LoggerFactoryTransformer.java | 19 +- .../main/java/org/teavm/html4j/JCLHacks.java | 19 +- .../html4j/JavaScriptBodyDependency.java | 70 ++- .../html4j/JavaScriptBodyTransformer.java | 5 +- .../html4j/JavaScriptObjectEnhancer.java | 2 +- .../org/teavm/jso/impl/JSClassProcessor.java | 40 +- .../teavm/jso/impl/JSDependencyListener.java | 17 +- .../impl/JSExceptionsDependencyListener.java | 3 +- .../java/org/teavm/jso/impl/JSMethods.java | 115 +++++ .../org/teavm/jso/impl/JSNativeGenerator.java | 31 +- .../jso/impl/JSObjectClassTransformer.java | 28 +- .../org/teavm/jso/impl/JSValueMarshaller.java | 69 ++- .../metaprogramming/Metaprogramming.java | 4 + .../MetaprogrammingDependencyListener.java | 32 +- .../impl/MetaprogrammingImpl.java | 23 +- .../metaprogramming/impl/UsageGenerator.java | 89 ++-- .../impl/model/MethodDescriber.java | 26 +- .../impl/reflect/AnnotationProxy.java | 18 +- .../reflect/ReflectAnnotatedElementImpl.java | 2 +- .../impl/reflect/ReflectClassImpl.java | 5 +- .../impl/reflect/ReflectContext.java | 11 +- .../plugin/AnnotationDependencySupport.java | 28 +- .../plugin/AsyncDependencyListener.java | 5 +- .../platform/plugin/AsyncMethodGenerator.java | 16 +- .../platform/plugin/AsyncMethodProcessor.java | 9 +- .../plugin/ClassLookupDependencySupport.java | 8 +- .../plugin/EnumDependencySupport.java | 12 +- .../plugin/MetadataProviderTransformer.java | 27 +- .../plugin/NewInstanceDependencySupport.java | 23 +- .../plugin/PlatformDependencyListener.java | 5 +- .../platform/plugin/PlatformGenerator.java | 5 +- .../plugin/PlatformQueueGenerator.java | 5 +- .../ResourceAccessorDependencyListener.java | 3 +- .../plugin/ResourceAccessorTransformer.java | 5 +- .../plugin/ResourceProgramTransformer.java | 10 +- .../platform/plugin/ResourceTransformer.java | 11 +- .../StringAmplifierDependencyPlugin.java | 3 +- .../plugin/StringAmplifierTransformer.java | 12 +- .../dependency/DependencyTestPatcher.java | 5 +- .../org/teavm/incremental/EntryPoint.java | 32 ++ .../incremental/EntryPointGenerator.java | 32 ++ .../incremental/EntryPointTransformer.java | 64 +++ .../teavm/incremental/IncrementalTest.java | 327 +++++++++++++ .../java/org/teavm/incremental/Update.java | 8 +- .../teavm/incremental/data/lambda/Bar.java | 28 ++ .../teavm/incremental/data/lambda/BarNew.java | 25 + .../teavm/incremental/data/lambda/Foo.java | 27 ++ .../teavm/incremental/data/lambda/Main.java | 25 + .../incremental/data/lambdaunchanged/Foo.java | 27 ++ .../data/lambdaunchanged/Main.java | 25 + .../org/teavm/incremental/data/meta/Main.java | 45 ++ .../teavm/incremental/data/simple/Foo.java | 28 ++ .../teavm/incremental/data/simple/FooNew.java | 25 + .../teavm/incremental/data/simple/Main.java | 29 ++ .../test/MetaprogrammingClass.java | 26 + .../test/MetaprogrammingTest.java | 86 +++- .../teavm/metaprogramming/test/ProxyTest.java | 17 + .../java/org/teavm/tooling/TeaVMTool.java | 51 +- .../teavm/tooling/builder/BuildStrategy.java | 2 + .../builder/InProcessBuildStrategy.java | 7 + .../tooling/builder/RemoteBuildStrategy.java | 5 + .../org/teavm/tooling/daemon/BuildDaemon.java | 12 +- .../tooling/daemon/RemoteBuildRequest.java | 1 + .../java/org/teavm/junit/TeaVMTestRunner.java | 18 +- .../junit/TestEntryPointTransformer.java | 29 +- .../TestExceptionDependencyListener.java | 7 +- .../org/teavm/maven/TeaVMCompileMojo.java | 4 + 174 files changed, 4498 insertions(+), 1879 deletions(-) delete mode 100644 classlib/src/main/java/org/teavm/classlib/impl/JavacSupport.java delete mode 100644 core/src/main/java/org/teavm/ast/cache/InMemoryRegularMethodNodeCache.java delete mode 100644 core/src/main/java/org/teavm/ast/decompilation/AsyncCallsFinder.java delete mode 100644 core/src/main/java/org/teavm/backend/javascript/NullPointerExceptionTransformer.java rename core/src/main/java/org/teavm/{model/InMemoryProgramCache.java => cache/AlwaysFreshCacheStatus.java} (52%) create mode 100644 core/src/main/java/org/teavm/cache/AlwaysStaleCacheStatus.java create mode 100644 core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java create mode 100644 core/src/main/java/org/teavm/cache/AstDependencyExtractor.java create mode 100644 core/src/main/java/org/teavm/cache/CacheStatus.java rename core/src/main/java/org/teavm/cache/{DiskRegularMethodNodeCache.java => DiskMethodNodeCache.java} (67%) rename core/src/main/java/org/teavm/{ast/cache/EmptyRegularMethodNodeCache.java => cache/EmptyMethodNodeCache.java} (70%) create mode 100644 core/src/main/java/org/teavm/cache/EmptyProgramCache.java create mode 100644 core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java create mode 100644 core/src/main/java/org/teavm/cache/InMemoryProgramCache.java create mode 100644 core/src/main/java/org/teavm/cache/IncrementalDependencyProvider.java create mode 100644 core/src/main/java/org/teavm/cache/IncrementalDependencyRegistration.java rename core/src/main/java/org/teavm/{ast => }/cache/MethodNodeCache.java (70%) create mode 100644 core/src/main/java/org/teavm/cache/ProgramDependencyExtractor.java create mode 100644 core/src/main/java/org/teavm/common/OptionalPredicate.java create mode 100644 core/src/main/java/org/teavm/dependency/AbstractInstructionAnalyzer.java create mode 100644 core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java create mode 100644 core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java create mode 100644 core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java create mode 100644 core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java create mode 100644 core/src/main/java/org/teavm/dependency/LocationListener.java create mode 100644 core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java create mode 100644 core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java create mode 100644 core/src/main/java/org/teavm/model/ClassHierarchy.java create mode 100644 core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java create mode 100644 tests/src/test/java/org/teavm/incremental/EntryPoint.java create mode 100644 tests/src/test/java/org/teavm/incremental/EntryPointGenerator.java create mode 100644 tests/src/test/java/org/teavm/incremental/EntryPointTransformer.java create mode 100644 tests/src/test/java/org/teavm/incremental/IncrementalTest.java rename core/src/main/java/org/teavm/cache/NoCache.java => tests/src/test/java/org/teavm/incremental/Update.java (86%) create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambda/Bar.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambda/BarNew.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambda/Foo.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambda/Main.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Foo.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Main.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/meta/Main.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/simple/Foo.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/simple/FooNew.java create mode 100644 tests/src/test/java/org/teavm/incremental/data/simple/Main.java create mode 100644 tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingClass.java diff --git a/classlib/pom.xml b/classlib/pom.xml index 931e108c5..dd6286a49 100644 --- a/classlib/pom.xml +++ b/classlib/pom.xml @@ -99,6 +99,27 @@ + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + generate-tz-cache + + java + + process-classes + + org.teavm.classlib.impl.tz.TimeZoneCache + + ${project.build.directory}/classes/org/teavm/classlib/impl/tz/cache + + + + + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java b/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java index 9dd68b055..c56741f35 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java @@ -17,11 +17,11 @@ package org.teavm.classlib.impl; import java.util.Arrays; import org.teavm.common.DisjointSet; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReference; @@ -43,16 +43,20 @@ public class ClassForNameTransformer implements ClassHolderTransformer { private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class); @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { Program program = method.getProgram(); if (program != null) { - transformProgram(program, innerSource); + transformProgram(program, context.getHierarchy()); } } } - private void transformProgram(Program program, ClassReaderSource classSource) { + private void transformProgram(Program program, ClassHierarchy hierarchy) { + if (!hasForNameCall(program)) { + return; + } + DisjointSet varSet = new DisjointSet(); for (int i = 0; i < program.variableCount(); i++) { varSet.create(); @@ -116,7 +120,7 @@ public class ClassForNameTransformer implements ClassHolderTransformer { if (nameIndex >= 0) { representative = program.variableAt(nameRepresentatives[nameIndex]); } else if (constant != null) { - if (classSource.get(constant) == null || !filterClassName(constant)) { + if (hierarchy.getClassSource().get(constant) == null || !filterClassName(constant)) { continue; } ClassConstantInstruction classConstant = new ClassConstantInstruction(); @@ -149,6 +153,24 @@ public class ClassForNameTransformer implements ClassHolderTransformer { } } + private boolean hasForNameCall(Program program) { + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + if (!(instruction instanceof InvokeInstruction)) { + continue; + } + + InvokeInstruction invoke = (InvokeInstruction) instruction; + + if (invoke.getMethod().equals(forNameMethod) || invoke.getMethod().equals(forNameShortMethod)) { + return true; + } + } + } + + return false; + } + private boolean filterClassName(String className) { switch (className) { // It's a hack for Kotlin. Kotlin full reflection library is too heavyweight for TeaVM. diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index c5894b90a..b75b0a3fc 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -48,9 +48,6 @@ public class JCLPlugin implements TeaVMPlugin { jsExtension.add(loadServicesMethod, serviceLoaderSupp); jsExtension.addVirtualMethods(new AnnotationVirtualMethods()); } - - JavacSupport javacSupport = new JavacSupport(); - host.add(javacSupport); } if (!isBootstrap()) { diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JavacSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/JavacSupport.java deleted file mode 100644 index 7021a559b..000000000 --- a/classlib/src/main/java/org/teavm/classlib/impl/JavacSupport.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2014 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.classlib.impl; - -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.*; -import org.teavm.model.instructions.ConstructInstruction; -import org.teavm.model.instructions.ExitInstruction; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; - -public class JavacSupport implements ClassHolderTransformer { - @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { - if (cls.getName().equals("javax.tools.ToolProvider")) { - MethodHolder method = cls.getMethod(new MethodDescriptor("getSystemJavaCompiler", - ValueType.object("javax.tools.JavaCompiler"))); - Program program = new Program(); - BasicBlock block = program.createBasicBlock(); - program.createVariable(); - Variable var = program.createVariable(); - ConstructInstruction construct = new ConstructInstruction(); - construct.setReceiver(var); - construct.setType("com.sun.tools.javac.api.JavacTool"); - block.add(construct); - InvokeInstruction init = new InvokeInstruction(); - init.setInstance(var); - init.setType(InvocationType.SPECIAL); - init.setMethod(new MethodReference("com.sun.tools.javac.api.JavacTool", "", ValueType.VOID)); - block.add(init); - ExitInstruction exit = new ExitInstruction(); - exit.setValueToReturn(var); - block.add(exit); - method.setProgram(program); - } - } -} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/NumericClassTransformer.java b/classlib/src/main/java/org/teavm/classlib/impl/NumericClassTransformer.java index 4b23fbcf1..a83e06212 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/NumericClassTransformer.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/NumericClassTransformer.java @@ -15,11 +15,10 @@ */ package org.teavm.classlib.impl; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; @@ -33,7 +32,7 @@ import org.teavm.model.instructions.NumericOperandType; public class NumericClassTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { switch (cls.getName()) { case "java.lang.Integer": transformInteger(cls); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java index 91f56c1a9..19adfc23d 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/PlatformMarkerSupport.java @@ -21,9 +21,10 @@ import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; @@ -50,16 +51,17 @@ public class PlatformMarkerSupport implements ClassHolderTransformer { } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { - transformProgram(method.getReference(), method.getProgram(), innerSource, diagnostics); + transformProgram(method.getReference(), method.getProgram(), context.getHierarchy(), + context.getDiagnostics()); } } } private void transformProgram(MethodReference containingMethod, Program program, - ClassReaderSource innerSource, Diagnostics diagnostics) { + ClassHierarchy hierarchy, Diagnostics diagnostics) { boolean hasChanges = false; for (BasicBlock block : program.getBasicBlocks()) { @@ -68,7 +70,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer { MarkerKind kind; if (instruction instanceof InvokeInstruction) { MethodReference methodRef = ((InvokeInstruction) instruction).getMethod(); - MethodReader method = innerSource.resolve(methodRef); + MethodReader method = hierarchy.getClassSource().resolveImplementation(methodRef); if (method == null) { continue; } @@ -92,7 +94,7 @@ public class PlatformMarkerSupport implements ClassHolderTransformer { receiver = ((InvokeInstruction) instruction).getReceiver(); } else if (instruction instanceof GetFieldInstruction) { FieldReference fieldRef = ((GetFieldInstruction) instruction).getField(); - FieldReader field = innerSource.resolve(fieldRef); + FieldReader field = hierarchy.getClassSource().resolve(fieldRef); if (field == null) { continue; } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java index fca9cbf56..a1f57eb60 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java @@ -63,10 +63,6 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { Class[].class); private MethodReference constructorGetParameterTypes = new MethodReference(Constructor.class, "getParameterTypes", Class[].class); - private boolean fieldGetHandled; - private boolean fieldSetHandled; - private boolean newInstanceHandled; - private boolean invokeHandled; private Map> accessibleFieldCache = new LinkedHashMap<>(); private Map> accessibleMethodCache = new LinkedHashMap<>(); private Set classesWithReflectableFields = new LinkedHashSet<>(); @@ -101,20 +97,20 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { allClasses.propagate(agent.getType(className)); } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getReference().equals(fieldGet)) { - handleFieldGet(agent, method, location); + handleFieldGet(agent, method); } else if (method.getReference().equals(fieldSet)) { - handleFieldSet(agent, method, location); + handleFieldSet(agent, method); } else if (method.getReference().equals(newInstance)) { - handleNewInstance(agent, method, location); + handleNewInstance(agent, method); } else if (method.getReference().equals(invokeMethod)) { - handleInvoke(agent, method, location); + handleInvoke(agent, method); } else if (method.getReference().equals(getFields)) { method.getVariable(0).getClassValueNode().addConsumer(type -> { if (!type.getName().startsWith("[")) { @@ -156,13 +152,11 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { } } - private void handleFieldGet(DependencyAgent agent, MethodDependency method, CallLocation location) { - if (fieldGetHandled) { - return; - } - fieldGetHandled = true; - - DependencyNode classValueNode = agent.linkMethod(getFields, location).getVariable(0).getClassValueNode(); + private void handleFieldGet(DependencyAgent agent, MethodDependency method) { + CallLocation location = new CallLocation(method.getReference()); + DependencyNode classValueNode = agent.linkMethod(getFields) + .addLocation(location) + .getVariable(0).getClassValueNode(); classValueNode.addConsumer(reflectedType -> { if (reflectedType.getName().startsWith("[")) { return; @@ -171,20 +165,19 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { ClassReader cls = agent.getClassSource().get(reflectedType.getName()); for (String fieldName : accessibleFields) { FieldReader field = cls.getField(fieldName); - FieldDependency fieldDep = agent.linkField(field.getReference(), location); + FieldDependency fieldDep = agent.linkField(field.getReference()) + .addLocation(location); propagateGet(agent, field.getType(), fieldDep.getValue(), method.getResult(), location); linkClassIfNecessary(agent, field, location); } }); } - private void handleFieldSet(DependencyAgent agent, MethodDependency method, CallLocation location) { - if (fieldSetHandled) { - return; - } - fieldSetHandled = true; - - DependencyNode classValueNode = agent.linkMethod(getFields, location).getVariable(0).getClassValueNode(); + private void handleFieldSet(DependencyAgent agent, MethodDependency method) { + CallLocation location = new CallLocation(method.getReference()); + DependencyNode classValueNode = agent.linkMethod(getFields) + .addLocation(location) + .getVariable(0).getClassValueNode(); classValueNode.addConsumer(reflectedType -> { if (reflectedType.getName().startsWith("[")) { return; @@ -194,20 +187,19 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { ClassReader cls = agent.getClassSource().get(reflectedType.getName()); for (String fieldName : accessibleFields) { FieldReader field = cls.getField(fieldName); - FieldDependency fieldDep = agent.linkField(field.getReference(), location); + FieldDependency fieldDep = agent.linkField(field.getReference()).addLocation(location); propagateSet(agent, field.getType(), method.getVariable(2), fieldDep.getValue(), location); linkClassIfNecessary(agent, field, location); } }); } - private void handleNewInstance(DependencyAgent agent, MethodDependency method, CallLocation location) { - if (newInstanceHandled) { - return; - } - newInstanceHandled = true; + private void handleNewInstance(DependencyAgent agent, MethodDependency method) { + CallLocation location = new CallLocation(method.getReference()); - DependencyNode classValueNode = agent.linkMethod(getConstructors, location).getVariable(0).getClassValueNode(); + DependencyNode classValueNode = agent.linkMethod(getConstructors) + .addLocation(location) + .getVariable(0).getClassValueNode(); classValueNode.addConsumer(reflectedType -> { if (reflectedType.getName().startsWith("[")) { return; @@ -217,7 +209,7 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { ClassReader cls = agent.getClassSource().get(reflectedType.getName()); for (MethodDescriptor methodDescriptor : accessibleMethods) { MethodReader calledMethod = cls.getMethod(methodDescriptor); - MethodDependency calledMethodDep = agent.linkMethod(calledMethod.getReference(), location); + MethodDependency calledMethodDep = agent.linkMethod(calledMethod.getReference()).addLocation(location); calledMethodDep.use(); for (int i = 0; i < calledMethod.parameterCount(); ++i) { propagateSet(agent, methodDescriptor.parameterType(i), method.getVariable(1).getArrayItem(), @@ -230,13 +222,11 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { classValueNode.connect(method.getResult()); } - private void handleInvoke(DependencyAgent agent, MethodDependency method, CallLocation location) { - if (invokeHandled) { - return; - } - invokeHandled = true; - - DependencyNode classValueNode = agent.linkMethod(getMethods, location).getVariable(0).getClassValueNode(); + private void handleInvoke(DependencyAgent agent, MethodDependency method) { + CallLocation location = new CallLocation(method.getReference()); + DependencyNode classValueNode = agent.linkMethod(getMethods) + .addLocation(location) + .getVariable(0).getClassValueNode(); classValueNode.addConsumer(reflectedType -> { if (reflectedType.getName().startsWith("[")) { return; @@ -246,7 +236,7 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { ClassReader cls = agent.getClassSource().get(reflectedType.getName()); for (MethodDescriptor methodDescriptor : accessibleMethods) { MethodReader calledMethod = cls.getMethod(methodDescriptor); - MethodDependency calledMethodDep = agent.linkMethod(calledMethod.getReference(), location); + MethodDependency calledMethodDep = agent.linkMethod(calledMethod.getReference()).addLocation(location); calledMethodDep.use(); for (int i = 0; i < calledMethod.parameterCount(); ++i) { propagateSet(agent, methodDescriptor.parameterType(i), method.getVariable(2).getArrayItem(), @@ -267,14 +257,14 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { } if (type instanceof ValueType.Object) { String className = ((ValueType.Object) type).getClassName(); - agent.linkClass(className, null); + agent.linkClass(className); typesInReflectableSignaturesNode.propagate(agent.getType(className)); } } private void linkClassIfNecessary(DependencyAgent agent, MemberReader member, CallLocation location) { if (member.hasModifier(ElementModifier.STATIC)) { - agent.linkClass(member.getOwnerName(), location).initClass(location); + agent.linkClass(member.getOwnerName()).initClass(location); } } @@ -333,7 +323,7 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { default: throw new AssertionError(type.toString()); } - MethodDependency boxMethodDep = agent.linkMethod(boxMethod, location); + MethodDependency boxMethodDep = agent.linkMethod(boxMethod).addLocation(location); boxMethodDep.use(); boxMethodDep.getResult().connect(targetNode); } else if (type instanceof ValueType.Array || type instanceof ValueType.Object) { @@ -370,7 +360,7 @@ public class ReflectionDependencyListener extends AbstractDependencyListener { default: throw new AssertionError(type.toString()); } - MethodDependency unboxMethodDep = agent.linkMethod(unboxMethod, location); + MethodDependency unboxMethodDep = agent.linkMethod(unboxMethod).addLocation(location); unboxMethodDep.use(); sourceNode.connect(unboxMethodDep.getResult()); } else if (type instanceof ValueType.Array || type instanceof ValueType.Object) { diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ScalaHacks.java b/classlib/src/main/java/org/teavm/classlib/impl/ScalaHacks.java index 470726f84..d7721628a 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ScalaHacks.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ScalaHacks.java @@ -16,11 +16,11 @@ package org.teavm.classlib.impl; import java.util.Properties; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.FieldHolder; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; @@ -33,10 +33,10 @@ import org.teavm.model.instructions.PutFieldInstruction; public class ScalaHacks implements ClassHolderTransformer { private static final String ATTR_NAME_CLASS = "java.util.jar.Attributes$Name"; @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { switch (cls.getName()) { case "scala.util.PropertiesTrait$class": - transformPropertiesTrait(cls, innerSource); + transformPropertiesTrait(cls, context.getHierarchy()); break; case "scala.util.Properties$": transformProperties(cls); @@ -44,10 +44,10 @@ public class ScalaHacks implements ClassHolderTransformer { } } - private void transformPropertiesTrait(ClassHolder cls, ClassReaderSource innerSource) { + private void transformPropertiesTrait(ClassHolder cls, ClassHierarchy hierarchy) { for (MethodHolder method : cls.getMethods()) { if (method.getName().equals("scalaProps")) { - ProgramEmitter pe = ProgramEmitter.create(method, innerSource); + ProgramEmitter pe = ProgramEmitter.create(method, hierarchy); pe.construct(Properties.class).returnValue(); } } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java index 44d38671e..ffdbe2abf 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java @@ -24,11 +24,9 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; -import java.util.Set; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; @@ -42,9 +40,9 @@ import org.teavm.model.MethodReference; import org.teavm.model.ValueType; public class ServiceLoaderSupport extends AbstractDependencyListener implements Generator { - private Set reachedClasses = new HashSet<>(); + private static final MethodReference LOAD_METHOD = new MethodReference(ServiceLoader.class, "load", Class.class, + ServiceLoader.class); private Map> serviceMap = new HashMap<>(); - private DependencyNode allClassesNode; private ClassLoader classLoader; public ServiceLoaderSupport(ClassLoader classLoader) { @@ -87,30 +85,8 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements writer.append("return result;").softNewLine(); } - @Override - public void started(DependencyAgent agent) { - allClassesNode = agent.createNode(); - } - - @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { - if (!reachedClasses.add(className)) { - return; - } - try { - Enumeration resources = classLoader.getResources("META-INF/services/" + className); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - try (InputStream stream = resource.openStream()) { - parseServiceFile(agent, className, stream); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void parseServiceFile(DependencyAgent agent, String service, InputStream input) throws IOException { + private void parseServiceFile(DependencyAgent agent, DependencyNode targetNode, String service, + InputStream input, CallLocation location) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); while (true) { String line = reader.readLine(); @@ -122,24 +98,37 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements continue; } serviceMap.computeIfAbsent(service, k -> new ArrayList<>()).add(line); - allClassesNode.propagate(agent.getType(line)); + + MethodReference ctor = new MethodReference(line, new MethodDescriptor("", ValueType.VOID)); + agent.linkMethod(ctor).addLocation(location).use(); + targetNode.propagate(agent.getType(line)); } } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { method.getResult().propagate(agent.getType("[Ljava/lang/Object;")); - DependencyNode sourceNode = agent.linkMethod(new MethodReference(ServiceLoader.class, "load", Class.class, - ServiceLoader.class), null).getVariable(1).getClassValueNode(); + DependencyNode sourceNode = agent.linkMethod(LOAD_METHOD).getVariable(1).getClassValueNode(); sourceNode.connect(method.getResult().getArrayItem()); - sourceNode.addConsumer(type -> initConstructor(agent, type.getName(), location)); + sourceNode.addConsumer(type -> initConstructor(agent, method.getResult().getArrayItem(), + type.getName(), new CallLocation(LOAD_METHOD))); } } - private void initConstructor(DependencyAgent agent, String type, CallLocation location) { - MethodReference ctor = new MethodReference(type, new MethodDescriptor("", ValueType.VOID)); - agent.linkMethod(ctor, location).use(); + private void initConstructor(DependencyAgent agent, DependencyNode targetNode, String type, + CallLocation location) { + try { + Enumeration resources = classLoader.getResources("META-INF/services/" + type); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + try (InputStream stream = resource.openStream()) { + parseServiceFile(agent, targetNode, type, stream, location); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/SystemClassTransformer.java b/classlib/src/main/java/org/teavm/classlib/impl/SystemClassTransformer.java index fcf5f98cf..bdf496b49 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/SystemClassTransformer.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/SystemClassTransformer.java @@ -15,11 +15,10 @@ */ package org.teavm.classlib.impl; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.FieldReference; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; @@ -31,7 +30,7 @@ import org.teavm.model.instructions.InvokeInstruction; public class SystemClassTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { transformProgram(method.getProgram()); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java b/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java index b3d678b3b..4fbf9826b 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java @@ -15,14 +15,15 @@ */ package org.teavm.classlib.impl.lambda; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.teavm.cache.NoCache; import org.teavm.dependency.BootstrapMethodSubstitutor; import org.teavm.dependency.DynamicCallSite; import org.teavm.model.AccessLevel; -import org.teavm.model.AnnotationHolder; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; @@ -31,6 +32,8 @@ import org.teavm.model.FieldHolder; import org.teavm.model.MethodHandle; import org.teavm.model.MethodHandleType; import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; import org.teavm.model.PrimitiveType; import org.teavm.model.TextLocation; import org.teavm.model.ValueType; @@ -42,7 +45,7 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor private static final int FLAG_SERIALIZABLE = 1; private static final int FLAG_MARKERS = 2; private static final int FLAG_BRIDGES = 4; - private Map lambdaIdsByMethod = new HashMap<>(); + private Map lambdaIdsByMethod = new HashMap<>(); @Override public ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter callerPe) { @@ -52,14 +55,24 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor ValueType[] instantiatedMethodType = callSite.getBootstrapArguments().get(2).getMethodType(); String samName = ((ValueType.Object) callSite.getCalledMethod().getResultType()).getClassName(); - ClassReaderSource classSource = callSite.getAgent().getClassSource(); - ClassReader samClass = classSource.get(samName); + ClassHierarchy hierarchy = callSite.getAgent().getClassHierarchy(); + ClassReader samClass = hierarchy.getClassSource().get(samName); String key = callSite.getCaller().getClassName() + "$" + callSite.getCaller().getName(); - int id = lambdaIdsByMethod.getOrDefault(key, 0); - lambdaIdsByMethod.put(key, id + 1); + ClassReaderSource classSource = callSite.getAgent().getClassSource(); + ClassReader callerClass = classSource.get(callSite.getCaller().getClassName()); + int id = 0; + for (MethodReader callerMethod : callerClass.getMethods()) { + if (callerMethod.getDescriptor().equals(callSite.getCaller().getDescriptor())) { + break; + } + ++id; + } + + int subId = lambdaIdsByMethod.getOrDefault(callSite.getCaller(), 0); + ClassHolder implementor = new ClassHolder(key + "$lambda$_" + id + "_" + subId); + lambdaIdsByMethod.put(callSite.getCaller(), subId + 1); - ClassHolder implementor = new ClassHolder(key + "$lambda$_" + id); implementor.setLevel(AccessLevel.PUBLIC); if (samClass != null && samClass.hasModifier(ElementModifier.INTERFACE)) { implementor.setParent("java.lang.Object"); @@ -69,16 +82,14 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor } int capturedVarCount = callSite.getCalledMethod().parameterCount(); - MethodHolder ctor = createConstructor(classSource, implementor, + MethodHolder ctor = createConstructor(hierarchy, implementor, Arrays.copyOfRange(invokedType, 0, capturedVarCount), callerPe.getCurrentLocation()); - ctor.getAnnotations().add(new AnnotationHolder(NoCache.class.getName())); - createBridge(classSource, implementor, callSite.getCalledMethod().getName(), instantiatedMethodType, + createBridge(hierarchy, implementor, callSite.getCalledMethod().getName(), instantiatedMethodType, samMethodType, callerPe.getCurrentLocation()); MethodHolder worker = new MethodHolder(callSite.getCalledMethod().getName(), instantiatedMethodType); - worker.getAnnotations().add(new AnnotationHolder(NoCache.class.getName())); worker.setLevel(AccessLevel.PUBLIC); - ProgramEmitter pe = ProgramEmitter.create(worker, callSite.getAgent().getClassSource()); + ProgramEmitter pe = ProgramEmitter.create(worker, callSite.getAgent().getClassHierarchy()); pe.setCurrentLocation(callerPe.getCurrentLocation()); ValueEmitter thisVar = pe.var(0, implementor); ValueEmitter[] arguments = new ValueEmitter[instantiatedMethodType.length - 1]; @@ -128,13 +139,23 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor int bridgeCount = callSite.getBootstrapArguments().get(bootstrapArgIndex++).getInt(); for (int i = 0; i < bridgeCount; ++i) { ValueType[] bridgeType = callSite.getBootstrapArguments().get(bootstrapArgIndex++).getMethodType(); - createBridge(classSource, implementor, callSite.getCalledMethod().getName(), instantiatedMethodType, + createBridge(hierarchy, implementor, callSite.getCalledMethod().getName(), instantiatedMethodType, bridgeType, callerPe.getCurrentLocation()); } } } + List dependencies = new ArrayList<>(); + dependencies.add(callSite.getCaller().getClassName()); + dependencies.addAll(implementor.getInterfaces()); + if (!implementor.getParent().equals("java.lang.Object")) { + dependencies.add(implementor.getParent()); + } + callSite.getAgent().submitClass(implementor); + callSite.getAgent().getIncrementalCache().addDependencies(implementor.getName(), + dependencies.toArray(new String[0])); + return callerPe.construct(ctor.getOwnerName(), callSite.getArguments().toArray(new ValueEmitter[0])); } @@ -273,14 +294,14 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor } } - private MethodHolder createConstructor(ClassReaderSource classSource, ClassHolder implementor, ValueType[] types, + private MethodHolder createConstructor(ClassHierarchy hierarchy, ClassHolder implementor, ValueType[] types, TextLocation location) { ValueType[] signature = Arrays.copyOf(types, types.length + 1); signature[types.length] = ValueType.VOID; MethodHolder ctor = new MethodHolder("", signature); ctor.setLevel(AccessLevel.PUBLIC); - ProgramEmitter pe = ProgramEmitter.create(ctor, classSource); + ProgramEmitter pe = ProgramEmitter.create(ctor, hierarchy); pe.setCurrentLocation(location); ValueEmitter thisVar = pe.var(0, implementor); thisVar.invokeSpecial(implementor.getParent(), ""); @@ -298,17 +319,16 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor return ctor; } - private void createBridge(ClassReaderSource classSource, ClassHolder implementor, String name, ValueType[] types, + private void createBridge(ClassHierarchy hierarchy, ClassHolder implementor, String name, ValueType[] types, ValueType[] bridgeTypes, TextLocation location) { if (Arrays.equals(types, bridgeTypes)) { return; } MethodHolder bridge = new MethodHolder(name, bridgeTypes); - bridge.getAnnotations().add(new AnnotationHolder(NoCache.class.getName())); bridge.setLevel(AccessLevel.PUBLIC); bridge.getModifiers().add(ElementModifier.BRIDGE); - ProgramEmitter pe = ProgramEmitter.create(bridge, classSource); + ProgramEmitter pe = ProgramEmitter.create(bridge, hierarchy); pe.setCurrentLocation(location); ValueEmitter thisVar = pe.var(0, implementor); ValueEmitter[] arguments = new ValueEmitter[bridgeTypes.length - 1]; diff --git a/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneCache.java b/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneCache.java index 6ec22e5a8..aec3b3d5e 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneCache.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneCache.java @@ -16,13 +16,14 @@ package org.teavm.classlib.impl.tz; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.*; import org.teavm.classlib.impl.Base46; import org.teavm.classlib.impl.CharFlow; public class TimeZoneCache { public void write(OutputStream output, Collection timeZones) throws IOException { - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (StorableDateTimeZone timeZone : timeZones) { writer.append(timeZone.getID()).append(' '); @@ -36,7 +37,7 @@ public class TimeZoneCache { public Map read(InputStream input) throws IOException { Map result = new HashMap<>(); - BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); List aliasLines = new ArrayList<>(); while (true) { String line = reader.readLine(); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java b/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java index 86f69659a..267f93cf7 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java @@ -20,6 +20,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -50,11 +51,13 @@ public class TimeZoneGenerator implements MetadataGenerator { case "northamerica": case "pacificnew": case "southamerica": - compiler.parseDataFile(new BufferedReader(new InputStreamReader(zip, "UTF-8")), false); + compiler.parseDataFile(new BufferedReader( + new InputStreamReader(zip, StandardCharsets.UTF_8)), false); break; case "backward": case "backzone": - compiler.parseDataFile(new BufferedReader(new InputStreamReader(zip, "UTF-8")), true); + compiler.parseDataFile(new BufferedReader( + new InputStreamReader(zip, StandardCharsets.UTF_8)), true); break; } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassDependencyListener.java index da2115848..19ab518da 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassDependencyListener.java @@ -18,16 +18,13 @@ package org.teavm.classlib.java.lang; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; public class ClassDependencyListener implements DependencyPlugin { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getMethod().getName()) { case "initialize": - method.getVariable(0).getClassValueNode().addConsumer(type -> agent - .linkClass(type.getName(), location) - .initClass(location)); + method.getVariable(0).getClassValueNode().addConsumer(type -> agent.linkClass(type.getName())); break; case "getSimpleNameCacheLowLevel": method.getResult().propagate(agent.getType("java.lang.String")); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java index 3a53f861a..c1ef07694 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassGenerator.java @@ -28,7 +28,6 @@ import org.teavm.classlib.impl.ReflectionDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReader; @@ -65,7 +64,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "newEmptyInstance": method.getVariable(0).getClassValueNode().connect(method.getResult()); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ObjectDependencyPlugin.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ObjectDependencyPlugin.java index ac264bf72..bbbd219b1 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ObjectDependencyPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ObjectDependencyPlugin.java @@ -18,11 +18,10 @@ package org.teavm.classlib.java.lang; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; public class ObjectDependencyPlugin implements DependencyPlugin { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getMethod().getName()) { case "clone": method.getVariable(0).connect(method.getResult()); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java index 4090c0b5f..74c81629b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java @@ -23,7 +23,6 @@ import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; public class SystemNativeGenerator implements Generator, DependencyPlugin { @@ -40,7 +39,7 @@ public class SystemNativeGenerator implements Generator, DependencyPlugin { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "doArrayCopy": reachArrayCopy(method); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/AnnotationDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/AnnotationDependencyListener.java index c102c2f8f..2568e7890 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/AnnotationDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/AnnotationDependencyListener.java @@ -18,7 +18,9 @@ package org.teavm.classlib.java.lang.reflect; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; @@ -26,10 +28,9 @@ import org.teavm.dependency.MethodDependency; import org.teavm.model.AccessLevel; import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; -import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.MethodHolder; @@ -41,16 +42,18 @@ import org.teavm.model.emit.ValueEmitter; import org.teavm.platform.PlatformAnnotationProvider; public class AnnotationDependencyListener extends AbstractDependencyListener { + private Set reachedMethods = new HashSet<>(); + private String getAnnotationImplementor(DependencyAgent agent, String annotationType) { String implementorName = annotationType + "$$_impl"; if (agent.getClassSource().get(implementorName) == null) { - ClassHolder implementor = createImplementor(agent.getClassSource(), annotationType, implementorName); + ClassHolder implementor = createImplementor(agent.getClassHierarchy(), annotationType, implementorName); agent.submitClass(implementor); } return implementorName; } - private ClassHolder createImplementor(ClassReaderSource classSource, String annotationType, + private ClassHolder createImplementor(ClassHierarchy hierarchy, String annotationType, String implementorName) { ClassHolder implementor = new ClassHolder(implementorName); implementor.setParent("java.lang.Object"); @@ -58,7 +61,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { implementor.getModifiers().add(ElementModifier.FINAL); implementor.setLevel(AccessLevel.PUBLIC); - ClassReader annotation = classSource.get(annotationType); + ClassReader annotation = hierarchy.getClassSource().get(annotationType); if (annotation == null) { return implementor; } @@ -74,7 +77,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { implementor.addField(field); MethodHolder accessor = new MethodHolder(methodDecl.getDescriptor()); - ProgramEmitter pe = ProgramEmitter.create(accessor, classSource); + ProgramEmitter pe = ProgramEmitter.create(accessor, hierarchy); ValueEmitter thisVal = pe.var(0, implementor); ValueEmitter result = thisVal.getField(field.getName(), field.getType()); if (field.getType() instanceof ValueType.Array) { @@ -87,8 +90,8 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { } ctorSignature.add(ValueType.VOID); - MethodHolder ctor = new MethodHolder("", ctorSignature.toArray(new ValueType[ctorSignature.size()])); - ProgramEmitter pe = ProgramEmitter.create(ctor, classSource); + MethodHolder ctor = new MethodHolder("", ctorSignature.toArray(new ValueType[0])); + ProgramEmitter pe = ProgramEmitter.create(ctor, hierarchy); ValueEmitter thisVar = pe.var(0, implementor); thisVar.invokeSpecial(Object.class, ""); int index = 1; @@ -103,7 +106,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { implementor.addMethod(ctor); MethodHolder annotTypeMethod = new MethodHolder("annotationType", ValueType.parse(Class.class)); - pe = ProgramEmitter.create(annotTypeMethod, classSource); + pe = ProgramEmitter.create(annotTypeMethod, hierarchy); pe.constant(ValueType.object(annotationType)).returnValue(); implementor.addMethod(annotTypeMethod); @@ -111,7 +114,11 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (!reachedMethods.add(method.getReference())) { + return; + } + ValueType type = method.getMethod().getResultType(); while (type instanceof ValueType.Array) { type = ((ValueType.Array) type).getItemType(); @@ -120,7 +127,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { String className = ((ValueType.Object) type).getClassName(); ClassReader cls = agent.getClassSource().get(className); if (cls != null && cls.hasModifier(ElementModifier.ANNOTATION)) { - agent.linkClass(className, location); + agent.linkClass(className); } } @@ -129,18 +136,18 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { ClassReader cls = agent.getClassSource().get(method.getReference().getClassName()); if (cls != null) { for (AnnotationReader annotation : cls.getAnnotations().all()) { - agent.linkClass(annotation.getType(), location); + agent.linkClass(annotation.getType()); } } } MethodReference methodRef = method.getMethod().getReference(); if (methodRef.getClassName().equals("java.lang.Class") && methodRef.getName().equals("getAnnotations")) { - reachGetAnnotations(agent, location, method.getVariable(0)); + reachGetAnnotations(agent, method.getVariable(0)); } } - private void reachGetAnnotations(DependencyAgent agent, CallLocation location, DependencyNode node) { + private void reachGetAnnotations(DependencyAgent agent, DependencyNode node) { node.getClassValueNode().addConsumer(type -> { String className = type.getName(); @@ -150,7 +157,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { } for (AnnotationReader annotation : cls.getAnnotations().all()) { - agent.linkClass(annotation.getType(), location); + agent.linkClass(annotation.getType()); } createAnnotationClass(agent, className); @@ -170,7 +177,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { MethodHolder ctor = new MethodHolder("", ValueType.VOID); ctor.setLevel(AccessLevel.PUBLIC); - ProgramEmitter pe = ProgramEmitter.create(ctor, agent.getClassSource()); + ProgramEmitter pe = ProgramEmitter.create(ctor, agent.getClassHierarchy()); ValueEmitter thisVar = pe.var(0, cls); thisVar.invokeSpecial(Object.class, "").exit(); @@ -184,7 +191,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { private MethodHolder addReader(DependencyAgent agent, ClassReader cls) { MethodHolder readerMethod = new MethodHolder("getAnnotations", ValueType.parse(Annotation[].class)); readerMethod.setLevel(AccessLevel.PUBLIC); - ProgramEmitter pe = ProgramEmitter.create(readerMethod, agent.getClassSource()); + ProgramEmitter pe = ProgramEmitter.create(readerMethod, agent.getClassHierarchy()); List annotations = new ArrayList<>(); for (AnnotationReader annot : cls.getAnnotations().all()) { @@ -230,7 +237,7 @@ public class AnnotationDependencyListener extends AbstractDependencyListener { .cast(methodDecl.getResultType())); } - return pe.construct(className, params.toArray(new ValueEmitter[params.size()])); + return pe.construct(className, params.toArray(new ValueEmitter[0])); } private ValueEmitter generateAnnotationValue(DependencyAgent agent, ProgramEmitter pe, ValueType type, diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java index 74a09ef00..cea4ad923 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java @@ -16,13 +16,14 @@ package org.teavm.classlib.java.lang.reflect; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -35,8 +36,13 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { private static final ValueType[] primitiveTypes = { ValueType.BYTE, ValueType.SHORT, ValueType.CHARACTER, ValueType.INTEGER, ValueType.LONG, ValueType.FLOAT, ValueType.DOUBLE, ValueType.BOOLEAN }; + private Set reachedMethods = new HashSet<>(); + @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (!reachedMethods.add(method.getReference())) { + return; + } switch (method.getReference().getName()) { case "getLength": reachGetLength(agent, method); @@ -51,7 +57,9 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { } else { arrayTypeName = ValueType.object(t.getName()).toString(); } - method.getResult().propagate(agent.getType("[" + arrayTypeName)); + if (!arrayTypeName.startsWith("[[[")) { + method.getResult().propagate(agent.getType("[" + arrayTypeName)); + } }); break; case "getImpl": @@ -91,11 +99,11 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { writer.append("return " + array + ".data.length;").softNewLine(); } - private void reachGetLength(final DependencyAgent agent, final MethodDependency method) { + private void reachGetLength(DependencyAgent agent, MethodDependency method) { method.getVariable(1).addConsumer(type -> { if (!type.getName().startsWith("[")) { MethodReference cons = new MethodReference(IllegalArgumentException.class, "", void.class); - agent.linkMethod(cons, null).use(); + agent.linkMethod(cons).use(); } }); } @@ -170,7 +178,7 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { String wrapper = "java.lang." + primitiveWrappers[i]; MethodReference methodRef = new MethodReference(wrapper, "valueOf", primitiveTypes[i], ValueType.object(wrapper)); - agent.linkMethod(methodRef, null).use(); + agent.linkMethod(methodRef).use(); method.getResult().propagate(agent.getType("java.lang." + primitiveWrappers[i])); } } @@ -188,7 +196,7 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { String wrapper = "java.lang." + primitiveWrappers[i]; MethodReference methodRef = new MethodReference(wrapper, primitives[i].toLowerCase() + "Value", primitiveTypes[i]); - agent.linkMethod(methodRef, null).use(); + agent.linkMethod(methodRef).use(); } } } diff --git a/core/src/main/java/org/teavm/ast/cache/InMemoryRegularMethodNodeCache.java b/core/src/main/java/org/teavm/ast/cache/InMemoryRegularMethodNodeCache.java deleted file mode 100644 index ba8bd9321..000000000 --- a/core/src/main/java/org/teavm/ast/cache/InMemoryRegularMethodNodeCache.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.ast.cache; - -import java.util.HashMap; -import java.util.Map; -import org.teavm.ast.AsyncMethodNode; -import org.teavm.ast.RegularMethodNode; -import org.teavm.model.MethodReference; - -public class InMemoryRegularMethodNodeCache implements MethodNodeCache { - private Map cache = new HashMap<>(); - private Map asyncCache = new HashMap<>(); - - @Override - public RegularMethodNode get(MethodReference methodReference) { - return cache.get(methodReference); - } - - @Override - public void store(MethodReference methodReference, RegularMethodNode node) { - cache.put(methodReference, node); - } - - @Override - public AsyncMethodNode getAsync(MethodReference methodReference) { - return asyncCache.get(methodReference); - } - - @Override - public void storeAsync(MethodReference methodReference, AsyncMethodNode node) { - asyncCache.put(methodReference, node); - } -} diff --git a/core/src/main/java/org/teavm/ast/decompilation/AsyncCallsFinder.java b/core/src/main/java/org/teavm/ast/decompilation/AsyncCallsFinder.java deleted file mode 100644 index 4b3cedba4..000000000 --- a/core/src/main/java/org/teavm/ast/decompilation/AsyncCallsFinder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.ast.decompilation; - -import java.util.HashSet; -import java.util.Set; -import org.teavm.ast.AssignmentStatement; -import org.teavm.ast.InvocationExpr; -import org.teavm.ast.RecursiveVisitor; -import org.teavm.model.MethodReference; - -class AsyncCallsFinder extends RecursiveVisitor { - final Set asyncCalls = new HashSet<>(); - final Set allCalls = new HashSet<>(); - - @Override - public void visit(AssignmentStatement statement) { - InvocationExpr invocation = (InvocationExpr) statement.getRightValue(); - asyncCalls.add(invocation.getMethod()); - } - - @Override - public void visit(InvocationExpr expr) { - super.visit(expr); - allCalls.add(expr.getMethod()); - } -} diff --git a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java index 793c3a95d..7c7db9474 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java +++ b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java @@ -42,12 +42,13 @@ import org.teavm.ast.Statement; import org.teavm.ast.TryCatchStatement; import org.teavm.ast.VariableNode; import org.teavm.ast.WhileStatement; -import org.teavm.ast.cache.MethodNodeCache; import org.teavm.ast.optimization.Optimizer; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.InjectedBy; -import org.teavm.cache.NoCache; +import org.teavm.cache.AstDependencyExtractor; +import org.teavm.cache.CacheStatus; +import org.teavm.cache.MethodNodeCache; import org.teavm.common.Graph; import org.teavm.common.GraphIndexer; import org.teavm.common.Loop; @@ -74,6 +75,7 @@ import org.teavm.model.util.TypeInferer; public class Decompiler { private ClassHolderSource classSource; private ClassLoader classLoader; + private CacheStatus cacheStatus; private Graph graph; private LoopGraph loopGraph; private GraphIndexer indexer; @@ -90,15 +92,18 @@ public class Decompiler { private Set asyncMethods; private Set splitMethods = new HashSet<>(); private List tryCatchBookmarks = new ArrayList<>(); + private final AstDependencyExtractor astDependencyExtractor = new AstDependencyExtractor(); private Deque stack; private Program program; private boolean friendlyToDebugger; private boolean moveConstants; - public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set asyncMethods, - Set asyncFamilyMethods, boolean friendlyToDebugger, boolean moveConstants) { + public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, + CacheStatus cacheStatus, Set asyncMethods, Set asyncFamilyMethods, + boolean friendlyToDebugger, boolean moveConstants) { this.classSource = classSource; this.classLoader = classLoader; + this.cacheStatus = cacheStatus; this.asyncMethods = asyncMethods; splitMethods.addAll(asyncMethods); splitMethods.addAll(asyncFamilyMethods); @@ -239,8 +244,7 @@ public class Decompiler { + " for native method " + method.getOwnerName() + "." + method.getDescriptor()); } } - NativeMethodNode methodNode = new NativeMethodNode(new MethodReference(method.getOwnerName(), - method.getDescriptor())); + NativeMethodNode methodNode = new NativeMethodNode(method.getReference()); methodNode.getModifiers().addAll(method.getModifiers()); methodNode.setGenerator(generator); methodNode.setAsync(asyncMethods.contains(method.getReference())); @@ -253,13 +257,16 @@ public class Decompiler { } public RegularMethodNode decompileRegular(MethodHolder method) { - if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) { + if (regularMethodCache == null) { return decompileRegularCacheMiss(method); } - RegularMethodNode node = regularMethodCache.get(method.getReference()); + RegularMethodNode node = !cacheStatus.isStaleMethod(method.getReference()) + ? regularMethodCache.get(method.getReference(), cacheStatus) + : null; if (node == null) { node = decompileRegularCacheMiss(method); - regularMethodCache.store(method.getReference(), node); + RegularMethodNode finalNode = node; + regularMethodCache.store(method.getReference(), node, () -> astDependencyExtractor.extract(finalNode)); } return node; } @@ -293,36 +300,20 @@ public class Decompiler { } public AsyncMethodNode decompileAsync(MethodHolder method) { - if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) { + if (regularMethodCache == null) { return decompileAsyncCacheMiss(method); } - AsyncMethodNode node = regularMethodCache.getAsync(method.getReference()); - if (node == null || !checkAsyncRelevant(node)) { + AsyncMethodNode node = !cacheStatus.isStaleMethod(method.getReference()) + ? regularMethodCache.getAsync(method.getReference(), cacheStatus) + : null; + if (node == null) { node = decompileAsyncCacheMiss(method); - regularMethodCache.storeAsync(method.getReference(), node); + AsyncMethodNode finalNode = node; + regularMethodCache.storeAsync(method.getReference(), node, () -> astDependencyExtractor.extract(finalNode)); } return node; } - private boolean checkAsyncRelevant(AsyncMethodNode node) { - AsyncCallsFinder asyncCallsFinder = new AsyncCallsFinder(); - for (AsyncMethodPart part : node.getBody()) { - part.getStatement().acceptVisitor(asyncCallsFinder); - } - for (MethodReference asyncCall : asyncCallsFinder.asyncCalls) { - if (!splitMethods.contains(asyncCall)) { - return false; - } - } - asyncCallsFinder.allCalls.removeAll(asyncCallsFinder.asyncCalls); - for (MethodReference asyncCall : asyncCallsFinder.allCalls) { - if (splitMethods.contains(asyncCall)) { - return false; - } - } - return true; - } - private AsyncMethodNode decompileAsyncCacheMiss(MethodHolder method) { AsyncMethodNode node = new AsyncMethodNode(method.getReference()); AsyncProgramSplitter splitter = new AsyncProgramSplitter(classSource, splitMethods); diff --git a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java index 37a11d9c5..9052f70cc 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java +++ b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java @@ -44,11 +44,9 @@ import org.teavm.common.GraphIndexer; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolderSource; import org.teavm.model.InvokeDynamicInstruction; -import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.TextLocation; -import org.teavm.model.ValueType; import org.teavm.model.Variable; import org.teavm.model.instructions.ArrayElementType; import org.teavm.model.instructions.ArrayLengthInstruction; @@ -93,6 +91,7 @@ import org.teavm.model.instructions.SwitchTableEntry; import org.teavm.model.instructions.UnwrapArrayInstruction; class StatementGenerator implements InstructionVisitor { + private static final MethodReference CLONE_METHOD = new MethodReference(Object.class, "clone", Object.class); private int lastSwitchId; final List statements = new ArrayList<>(); GraphIndexer indexer; @@ -432,9 +431,7 @@ class StatementGenerator implements InstructionVisitor { @Override public void visit(CloneArrayInstruction insn) { - MethodDescriptor cloneMethodDesc = new MethodDescriptor("clone", ValueType.object("java.lang.Object")); - MethodReference cloneMethod = new MethodReference("java.lang.Object", cloneMethodDesc); - assign(Expr.invoke(cloneMethod, Expr.var(insn.getArray().getIndex()), new Expr[0]), insn.getReceiver()); + assign(Expr.invoke(CLONE_METHOD, Expr.var(insn.getArray().getIndex()), new Expr[0]), insn.getReceiver()); } @Override diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index b3c9473b6..690bf44b3 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -59,6 +59,7 @@ import org.teavm.backend.c.intrinsic.PlatformObjectIntrinsic; import org.teavm.backend.c.intrinsic.RuntimeClassIntrinsic; import org.teavm.backend.c.intrinsic.ShadowStackIntrinsic; import org.teavm.backend.c.intrinsic.StructureIntrinsic; +import org.teavm.cache.AlwaysStaleCacheStatus; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; @@ -164,34 +165,34 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { @Override public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocate", - RuntimeClass.class, Address.class), null).use(); + RuntimeClass.class, Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateArray", - RuntimeClass.class, int.class, Address.class), null).use(); + RuntimeClass.class, int.class, Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateMultiArray", - RuntimeClass.class, Address.class, int.class, RuntimeArray.class), null).use(); + RuntimeClass.class, Address.class, int.class, RuntimeArray.class)).use(); - dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "", void.class), null).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "", void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwException", - Throwable.class, void.class), null).use(); + Throwable.class, void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwClassCastException", - void.class), null).use(); + void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwNullPointerException", - void.class), null).use(); + void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "catchException", - Throwable.class), null).use(); + Throwable.class)).use(); - dependencyAnalyzer.linkClass("java.lang.String", null); - dependencyAnalyzer.linkClass("java.lang.Class", null); - dependencyAnalyzer.linkField(new FieldReference("java.lang.String", "hashCode"), null); + dependencyAnalyzer.linkClass("java.lang.String"); + dependencyAnalyzer.linkClass("java.lang.Class"); + dependencyAnalyzer.linkField(new FieldReference("java.lang.String", "hashCode")); - ClassDependency runtimeClassDep = dependencyAnalyzer.linkClass(RuntimeClass.class.getName(), null); - ClassDependency runtimeObjectDep = dependencyAnalyzer.linkClass(RuntimeObject.class.getName(), null); - ClassDependency runtimeArrayDep = dependencyAnalyzer.linkClass(RuntimeArray.class.getName(), null); + ClassDependency runtimeClassDep = dependencyAnalyzer.linkClass(RuntimeClass.class.getName()); + ClassDependency runtimeObjectDep = dependencyAnalyzer.linkClass(RuntimeObject.class.getName()); + ClassDependency runtimeArrayDep = dependencyAnalyzer.linkClass(RuntimeArray.class.getName()); for (ClassDependency classDep : Arrays.asList(runtimeClassDep, runtimeObjectDep, runtimeArrayDep)) { for (FieldReader field : classDep.getClassReader().getFields()) { - dependencyAnalyzer.linkField(field.getReference(), null); + dependencyAnalyzer.linkField(field.getReference()); } } } @@ -216,8 +217,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { TagRegistry tagRegistry = new TagRegistry(classes); StringPool stringPool = new StringPool(); - Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), - new HashSet<>(), false, true); + Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), + AlwaysStaleCacheStatus.INSTANCE, new HashSet<>(), new HashSet<>(), false, true); Characteristics characteristics = new Characteristics(controller.getUnprocessedClassSource()); NameProvider nameProvider = new NameProvider(controller.getUnprocessedClassSource()); diff --git a/core/src/main/java/org/teavm/backend/c/analyze/CDependencyListener.java b/core/src/main/java/org/teavm/backend/c/analyze/CDependencyListener.java index b1cf1cc6d..58d7c8957 100644 --- a/core/src/main/java/org/teavm/backend/c/analyze/CDependencyListener.java +++ b/core/src/main/java/org/teavm/backend/c/analyze/CDependencyListener.java @@ -18,21 +18,19 @@ package org.teavm.backend.c.analyze; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.MethodDependency; -import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.DelegateTo; import org.teavm.model.AnnotationReader; -import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; public class CDependencyListener extends AbstractDependencyListener implements ClassHolderTransformer { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { AnnotationReader delegateAnnot = method.getMethod().getAnnotations().get(DelegateTo.class.getName()); if (delegateAnnot != null) { String delegateMethodName = delegateAnnot.getValue("value").getString(); @@ -40,7 +38,9 @@ public class CDependencyListener extends AbstractDependencyListener implements C for (MethodReader delegate : cls.getMethods()) { if (delegate.getName().equals(delegateMethodName)) { if (delegate != method.getMethod()) { - agent.linkMethod(delegate.getReference(), location).use(); + MethodDependency delegateDep = agent.linkMethod(delegate.getReference()); + method.addLocationListener(delegateDep::addLocation); + delegateDep.use(); } } } @@ -48,7 +48,7 @@ public class CDependencyListener extends AbstractDependencyListener implements C } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { AnnotationReader delegateAnnot = method.getAnnotations().get(DelegateTo.class.getName()); if (delegateAnnot != null) { diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index d60bc941e..f6af8d342 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -35,8 +35,6 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; import org.teavm.ast.ClassNode; -import org.teavm.ast.cache.EmptyRegularMethodNodeCache; -import org.teavm.ast.cache.MethodNodeCache; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.javascript.codegen.AliasProvider; import org.teavm.backend.javascript.codegen.DefaultAliasProvider; @@ -53,6 +51,8 @@ import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; +import org.teavm.cache.EmptyMethodNodeCache; +import org.teavm.cache.MethodNodeCache; import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.SourceLocation; @@ -107,7 +107,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private final List> injectorProviders = new ArrayList<>(); private final List rendererListeners = new ArrayList<>(); private DebugInformationEmitter debugEmitter; - private MethodNodeCache astCache = new EmptyRegularMethodNodeCache(); + private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE; private final Set asyncMethods = new HashSet<>(); private final Set asyncFamilyMethods = new HashSet<>(); private ClassInitializerInsertionTransformer clinitInsertionTransformer; @@ -211,42 +211,41 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { DependencyType stringType = dependencyAnalyzer.getType("java.lang.String"); dep = dependencyAnalyzer.linkMethod(new MethodReference(Class.class.getName(), "getClass", - ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null); + ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class))); dep.getVariable(0).propagate(dependencyAnalyzer.getType("org.teavm.platform.PlatformClass")); dep.getResult().propagate(dependencyAnalyzer.getType("java.lang.Class")); dep.use(); - dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "", char[].class, void.class), - null); + dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "", char[].class, void.class)); dep.getVariable(0).propagate(stringType); dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C")); dep.use(); - dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters"), null); + dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters")); - dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class), null); + dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class)); MethodDependency exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( - NoClassDefFoundError.class, "", String.class, void.class), null); + NoClassDefFoundError.class, "", String.class, void.class)); - dep = dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "toString", String.class), null); + dep = dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "toString", String.class)); dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.Object")); dep.use(); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoClassDefFoundError.class.getName())); exceptionCons.getVariable(1).propagate(stringType); exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchFieldError.class, "", - String.class, void.class), null); + String.class, void.class)); exceptionCons.use(); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchFieldError.class.getName())); exceptionCons.getVariable(1).propagate(stringType); exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchMethodError.class, "", - String.class, void.class), null); + String.class, void.class)); exceptionCons.use(); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchMethodError.class.getName())); exceptionCons.getVariable(1).propagate(stringType); exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( - RuntimeException.class, "", String.class, void.class), null); + RuntimeException.class, "", String.class, void.class)); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(RuntimeException.class.getName())); exceptionCons.getVariable(1).propagate(stringType); exceptionCons.use(); @@ -263,7 +262,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { dep = dependencyAnalyzer.linkMethod(new MethodReference( StackTraceElement.class, "", String.class, String.class, String.class, - int.class, void.class), null); + int.class, void.class)); dep.getVariable(0).propagate(dependencyAnalyzer.getType(StackTraceElement.class.getName())); dep.getVariable(1).propagate(stringType); dep.getVariable(2).propagate(stringType); @@ -271,7 +270,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { dep.use(); dep = dependencyAnalyzer.linkMethod(new MethodReference( - Throwable.class, "setStackTrace", StackTraceElement[].class, void.class), null); + Throwable.class, "setStackTrace", StackTraceElement[].class, void.class)); dep.getVariable(0).propagate(dependencyAnalyzer.getType(Throwable.class.getName())); dep.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/StackTraceElement;")); dep.getVariable(1).getArrayItem().propagate(dependencyAnalyzer.getType(StackTraceElement.class.getName())); @@ -440,9 +439,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { asyncMethods.addAll(asyncFinder.getAsyncMethods()); asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods()); - Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods, - controller.isFriendlyToDebugger(), false); - decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null); + Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), controller.getCacheStatus(), + asyncMethods, asyncFamilyMethods, controller.isFriendlyToDebugger(), false); + decompiler.setRegularMethodCache(astCache); for (Map.Entry entry : methodGenerators.entrySet()) { decompiler.addGenerator(entry.getKey(), entry.getValue()); diff --git a/core/src/main/java/org/teavm/backend/javascript/NullPointerExceptionTransformer.java b/core/src/main/java/org/teavm/backend/javascript/NullPointerExceptionTransformer.java deleted file mode 100644 index 475566f48..000000000 --- a/core/src/main/java/org/teavm/backend/javascript/NullPointerExceptionTransformer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.backend.javascript; - -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.*; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; -import org.teavm.model.instructions.NullCheckInstruction; - -public class NullPointerExceptionTransformer implements ClassHolderTransformer { - @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { - for (MethodHolder method : cls.getMethods()) { - Program program = method.getProgram(); - if (program == null) { - continue; - } - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - transformBlock(block); - } - } - } - - private void transformBlock(BasicBlock block) { - for (Instruction insn : block) { - if (insn instanceof InvokeInstruction) { - InvokeInstruction invoke = (InvokeInstruction) insn; - if (invoke.getType() != InvocationType.VIRTUAL) { - continue; - } - NullCheckInstruction nullCheck = new NullCheckInstruction(); - nullCheck.setValue(invoke.getInstance()); - Variable var = block.getProgram().createVariable(); - nullCheck.setReceiver(var); - invoke.setInstance(var); - insn.insertPrevious(nullCheck); - } - } - } -} diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java index 8affb173d..cb68d218c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java @@ -15,6 +15,8 @@ */ package org.teavm.backend.javascript.codegen; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -25,12 +27,15 @@ import org.teavm.model.MethodReference; public class DefaultAliasProvider implements AliasProvider { private final Map classAliases = new HashMap<>(); - private final Set knownAliases = new HashSet<>(); - private final Set knownVirtualAliases = new HashSet<>(); + private final Set knownAliases = new HashSet<>(200, 0.5f); + private final ObjectIntMap knowAliasesCounter = new ObjectIntHashMap<>(); + private final Set knownVirtualAliases = new HashSet<>(200, 0.5f); + private final ObjectIntMap knowVirtualAliasesCounter = new ObjectIntHashMap<>(); @Override public String getClassAlias(String cls) { - return classAliases.computeIfAbsent(cls, key -> makeUnique(knownAliases, suggestAliasForClass(key))); + return classAliases.computeIfAbsent(cls, key -> makeUnique(knownAliases, knowAliasesCounter, + suggestAliasForClass(key))); } private static String suggestAliasForClass(String cls) { @@ -80,7 +85,7 @@ public class DefaultAliasProvider implements AliasProvider { alias = "$" + alias; break; } - return makeUnique(knownVirtualAliases, alias); + return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, alias); } @Override @@ -95,17 +100,18 @@ public class DefaultAliasProvider implements AliasProvider { break; } - return makeUnique(knownAliases, getClassAlias(method.getClassName()) + "_" + alias); + return makeUnique(knownAliases, knowAliasesCounter, getClassAlias(method.getClassName()) + "_" + alias); } @Override public String getFieldAlias(FieldReference field) { - return makeUnique(knownVirtualAliases, "$" + field.getFieldName()); + return makeUnique(knownVirtualAliases, knowVirtualAliasesCounter, "$" + field.getFieldName()); } @Override public String getStaticFieldAlias(FieldReference field) { - return makeUnique(knownAliases, getClassAlias(field.getClassName()) + "_" + field.getFieldName()); + return makeUnique(knownAliases, knowAliasesCounter, + getClassAlias(field.getClassName()) + "_" + field.getFieldName()); } @Override @@ -115,15 +121,19 @@ public class DefaultAliasProvider implements AliasProvider { @Override public String getClassInitAlias(String className) { - return makeUnique(knownAliases, suggestAliasForClass(className) + "_$callClinit"); + return makeUnique(knownAliases, knowAliasesCounter, suggestAliasForClass(className) + "_$callClinit"); } - private String makeUnique(Set knowAliases, String alias) { + private String makeUnique(Set knowAliases, ObjectIntMap indexMap, String alias) { String uniqueAlias = alias; - int index = 1; + int index = indexMap.get(alias); + if (index > 0) { + uniqueAlias = alias + index++; + } while (!knowAliases.add(uniqueAlias)) { uniqueAlias = alias + index++; } + indexMap.put(alias, index); return uniqueAlias; } } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java index fcdb927f0..233723c55 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultNamingStrategy.java @@ -120,7 +120,7 @@ public class DefaultNamingStrategy implements NamingStrategy { if (method.getLevel() == AccessLevel.PRIVATE && !className.equals(methodRef.getClassName())) { return null; } - return new MethodReference(className, method.getDescriptor()); + return method.getReference(); } className = cls.getParent(); } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java index ef28ef3b3..36cf0f885 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java @@ -51,6 +51,15 @@ import org.teavm.model.MethodReference; import org.teavm.model.ValueType; class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisitor { + static final MethodReference MONITOR_ENTER_METHOD = new MethodReference(Object.class, + "monitorEnter", Object.class, void.class); + static final MethodReference MONITOR_ENTER_SYNC_METHOD = new MethodReference(Object.class, + "monitorEnterSync", Object.class, void.class); + static final MethodReference MONITOR_EXIT_METHOD = new MethodReference(Object.class, + "monitorExit", Object.class, void.class); + static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class, + "monitorExitSync", Object.class, void.class); + private final NameFrequencyConsumer consumer; private final ClassReaderSource classSource; private boolean async; @@ -167,14 +176,10 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit public void visit(MonitorEnterStatement statement) { super.visit(statement); if (async) { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnter", Object.class, void.class); - consumer.consume(monitorEnterRef); + consumer.consume(MONITOR_ENTER_METHOD); consumer.consumeFunction("$rt_suspending"); } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnterSync", Object.class, void.class); - consumer.consume(monitorEnterRef); + consumer.consume(MONITOR_ENTER_SYNC_METHOD); } } @@ -182,13 +187,9 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit public void visit(MonitorExitStatement statement) { super.visit(statement); if (async) { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorExit", Object.class, void.class); - consumer.consume(monitorEnterRef); + consumer.consume(MONITOR_EXIT_METHOD); } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorExitSync", Object.class, void.class); - consumer.consume(monitorEnterRef); + consumer.consume(MONITOR_EXIT_SYNC_METHOD); } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 58ed9a0cf..c368dd47e 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -836,7 +836,29 @@ public class Renderer implements RenderingManager { statementRenderer.setEnd(true); statementRenderer.setCurrentPart(0); + + if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD); + writer.append("("); + appendMonitor(statementRenderer, method); + writer.append(");").softNewLine(); + + writer.append("try").ws().append("{").softNewLine().indent(); + } + method.getBody().acceptVisitor(statementRenderer); + + if (method.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { + writer.outdent().append("}").ws().append("finally").ws().append("{").indent().softNewLine(); + + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD); + writer.append("("); + appendMonitor(statementRenderer, method); + writer.append(");").softNewLine(); + + writer.outdent().append("}").softNewLine(); + } + } catch (IOException e) { throw new RenderingException("IO error occurred", e); } @@ -913,8 +935,7 @@ public class Renderer implements RenderingManager { for (int i = 0; i < methodNode.getBody().size(); ++i) { writer.append("case ").append(i).append(":").indent().softNewLine(); if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { - writer.appendMethodBody(new MethodReference(Object.class, "monitorEnter", - Object.class, void.class)); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD); writer.append("("); appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); @@ -932,8 +953,7 @@ public class Renderer implements RenderingManager { writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine(); writer.append("if").ws().append("(!").appendFunction("$rt_suspending").append("())") .ws().append("{").indent().softNewLine(); - writer.appendMethodBody(new MethodReference(Object.class, "monitorExit", - Object.class, void.class)); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD); writer.append("("); appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java index 7dd517ed0..0bc748d6d 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -1459,6 +1459,10 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { } first = false; + + if (defaultHandlerOccurred) { + break; + } } if (!defaultHandlerOccurred) { writer.ws().append("else").ws().append("{").indent().softNewLine(); @@ -1492,17 +1496,13 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { public void visit(MonitorEnterStatement statement) { try { if (async) { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnter", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append("("); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_METHOD).append("("); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); emitSuspendChecker(); } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnterSync", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append('('); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_ENTER_SYNC_METHOD).append('('); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); @@ -1523,16 +1523,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { public void visit(MonitorExitStatement statement) { try { if (async) { - MethodReference monitorExitRef = new MethodReference( - Object.class, "monitorExit", Object.class, void.class); - writer.appendMethodBody(monitorExitRef).append("("); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_METHOD).append("("); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorExitSync", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append('('); + writer.appendMethodBody(NameFrequencyEstimator.MONITOR_EXIT_SYNC_METHOD).append('('); precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 15500d784..a086db882 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -91,6 +91,7 @@ import org.teavm.backend.wasm.render.WasmCRenderer; import org.teavm.backend.wasm.render.WasmRenderer; import org.teavm.backend.wasm.transformation.IndirectCallTraceTransformation; import org.teavm.backend.wasm.transformation.MemoryAccessTraceTransformation; +import org.teavm.cache.AlwaysStaleCacheStatus; import org.teavm.common.ServiceRepository; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyAnalyzer; @@ -241,61 +242,60 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { for (Class type : Arrays.asList(int.class, long.class, float.class, double.class)) { MethodReference method = new MethodReference(WasmRuntime.class, "compare", type, type, int.class); - dependencyAnalyzer.linkMethod(method, null).use(); + dependencyAnalyzer.linkMethod(method).use(); } for (Class type : Arrays.asList(float.class, double.class)) { MethodReference method = new MethodReference(WasmRuntime.class, "remainder", type, type, type); - dependencyAnalyzer.linkMethod(method, null).use(); + dependencyAnalyzer.linkMethod(method).use(); } dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "align", Address.class, int.class, - Address.class), null).use(); + Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "fillZero", Address.class, int.class, - void.class), null).use(); + void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "moveMemoryBlock", Address.class, - Address.class, int.class, void.class), null).use(); + Address.class, int.class, void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "allocStack", - int.class, Address.class), null).use(); - dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackTop", Address.class), - null) .use(); + int.class, Address.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackTop", Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getNextStackFrame", Address.class, - Address.class), null).use(); + Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackRootCount", Address.class, - int.class), null).use(); + int.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackRootPointer", Address.class, - Address.class), null).use(); + Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "setExceptionHandlerId", Address.class, - int.class, void.class), null).use(); + int.class, void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getCallSiteId", Address.class, - int.class), null).use(); + int.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "resourceMapKeys", Address.class, - String[].class), null).use(); + String[].class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class, - String.class, Address.class), null).use(); + String.class, Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocate", - RuntimeClass.class, Address.class), null).use(); + RuntimeClass.class, Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateArray", - RuntimeClass.class, int.class, Address.class), null).use(); + RuntimeClass.class, int.class, Address.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateMultiArray", - RuntimeClass.class, Address.class, int.class, RuntimeArray.class), null).use(); + RuntimeClass.class, Address.class, int.class, RuntimeArray.class)).use(); - dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "", void.class), null).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "", void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwException", - Throwable.class, void.class), null).use(); + Throwable.class, void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "catchException", - Throwable.class), null).use(); + Throwable.class)).use(); - dependencyAnalyzer.linkField(new FieldReference("java.lang.Object", "monitor"), null); + dependencyAnalyzer.linkField(new FieldReference("java.lang.Object", "monitor")); - ClassDependency runtimeClassDep = dependencyAnalyzer.linkClass(RuntimeClass.class.getName(), null); - ClassDependency runtimeObjectDep = dependencyAnalyzer.linkClass(RuntimeObject.class.getName(), null); - ClassDependency runtimeArrayDep = dependencyAnalyzer.linkClass(RuntimeArray.class.getName(), null); + ClassDependency runtimeClassDep = dependencyAnalyzer.linkClass(RuntimeClass.class.getName()); + ClassDependency runtimeObjectDep = dependencyAnalyzer.linkClass(RuntimeObject.class.getName()); + ClassDependency runtimeArrayDep = dependencyAnalyzer.linkClass(RuntimeArray.class.getName()); for (ClassDependency classDep : Arrays.asList(runtimeClassDep, runtimeObjectDep, runtimeArrayDep)) { for (FieldReader field : classDep.getClassReader().getFields()) { - dependencyAnalyzer.linkField(field.getReference(), null); + dependencyAnalyzer.linkField(field.getReference()); } } } @@ -325,8 +325,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { WasmClassGenerator classGenerator = new WasmClassGenerator(classes, controller.getUnprocessedClassSource(), vtableProvider, tagRegistry, binaryWriter, names); - Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), - new HashSet<>(), false, true); + Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), + AlwaysStaleCacheStatus.INSTANCE, new HashSet<>(), new HashSet<>(), false, true); WasmStringPool stringPool = classGenerator.getStringPool(); WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(), vtableProvider, tagRegistry, stringPool, names); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmDependencyListener.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmDependencyListener.java index 84b770b4f..d9ae95a26 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmDependencyListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmDependencyListener.java @@ -18,32 +18,30 @@ package org.teavm.backend.wasm.generate; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.MethodDependency; -import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.DelegateTo; import org.teavm.interop.Export; import org.teavm.model.AnnotationReader; -import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; public class WasmDependencyListener extends AbstractDependencyListener implements ClassHolderTransformer { @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { for (MethodReader reader : agent.getClassSource().get(className).getMethods()) { AnnotationReader annotation = reader.getAnnotations().get(Export.class.getName()); if (annotation != null) { - agent.linkMethod(reader.getReference(), null).use(); + agent.linkMethod(reader.getReference()).use(); } } } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { AnnotationReader delegateAnnot = method.getMethod().getAnnotations().get(DelegateTo.class.getName()); if (delegateAnnot != null) { String delegateMethodName = delegateAnnot.getValue("value").getString(); @@ -51,7 +49,9 @@ public class WasmDependencyListener extends AbstractDependencyListener implement for (MethodReader delegate : cls.getMethods()) { if (delegate.getName().equals(delegateMethodName)) { if (delegate != method.getMethod()) { - agent.linkMethod(delegate.getReference(), location).use(); + MethodDependency dep = agent.linkMethod(delegate.getReference()); + dep.use(); + method.addLocationListener(dep::addLocation); } } } @@ -59,7 +59,7 @@ public class WasmDependencyListener extends AbstractDependencyListener implement } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { AnnotationReader delegateAnnot = method.getAnnotations().get(DelegateTo.class.getName()); if (delegateAnnot != null) { diff --git a/core/src/main/java/org/teavm/model/InMemoryProgramCache.java b/core/src/main/java/org/teavm/cache/AlwaysFreshCacheStatus.java similarity index 52% rename from core/src/main/java/org/teavm/model/InMemoryProgramCache.java rename to core/src/main/java/org/teavm/cache/AlwaysFreshCacheStatus.java index f1b475aa3..7484134d8 100644 --- a/core/src/main/java/org/teavm/model/InMemoryProgramCache.java +++ b/core/src/main/java/org/teavm/cache/AlwaysFreshCacheStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.model; +package org.teavm.cache; -import java.util.HashMap; -import java.util.Map; -import org.teavm.model.util.ProgramUtils; +import org.teavm.model.MethodReference; -public class InMemoryProgramCache implements ProgramCache { - private Map cache = new HashMap<>(); +public class AlwaysFreshCacheStatus implements CacheStatus { + public static final AlwaysFreshCacheStatus INSTANCE = new AlwaysFreshCacheStatus(); - @Override - public Program get(MethodReference method) { - Program program = cache.get(method); - return program != null ? ProgramUtils.copy(program) : null; + private AlwaysFreshCacheStatus() { } @Override - public void store(MethodReference method, Program program) { - cache.put(method, ProgramUtils.copy(program)); + public boolean isStaleClass(String className) { + return false; + } + + @Override + public boolean isStaleMethod(MethodReference method) { + return false; } } diff --git a/core/src/main/java/org/teavm/cache/AlwaysStaleCacheStatus.java b/core/src/main/java/org/teavm/cache/AlwaysStaleCacheStatus.java new file mode 100644 index 000000000..ac5aad1cc --- /dev/null +++ b/core/src/main/java/org/teavm/cache/AlwaysStaleCacheStatus.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 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.cache; + +import org.teavm.model.MethodReference; + +public class AlwaysStaleCacheStatus implements CacheStatus { + public static final AlwaysStaleCacheStatus INSTANCE = new AlwaysStaleCacheStatus(); + + private AlwaysStaleCacheStatus() { + } + + @Override + public boolean isStaleClass(String className) { + return true; + } + + @Override + public boolean isStaleMethod(MethodReference method) { + return true; + } +} diff --git a/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java b/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java new file mode 100644 index 000000000..2beabe74f --- /dev/null +++ b/core/src/main/java/org/teavm/cache/AnnotationAwareCacheStatus.java @@ -0,0 +1,98 @@ +/* + * Copyright 2018 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.cache; + +import com.carrotsearch.hppc.ObjectByteHashMap; +import com.carrotsearch.hppc.ObjectByteMap; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import org.teavm.model.MethodReference; + +public final class AnnotationAwareCacheStatus implements CacheStatus { + private static final byte UNKNOWN = 0; + private static final byte STALE = 1; + private static final byte UNDECIDED = 2; + private static final byte FRESH = 3; + + private CacheStatus underlyingStatus; + private IncrementalDependencyProvider dependencyProvider; + private List> synthesizedClasses = new ArrayList<>(); + private ObjectByteMap classStatusCache = new ObjectByteHashMap<>(); + + public AnnotationAwareCacheStatus(CacheStatus underlyingStatus, IncrementalDependencyProvider dependencyProvider) { + this.underlyingStatus = underlyingStatus; + this.dependencyProvider = dependencyProvider; + } + + public void addSynthesizedClasses(Predicate synthesizedClasses) { + this.synthesizedClasses.add(synthesizedClasses); + } + + @Override + public boolean isStaleClass(String className) { + return getClassStatus(className) == STALE; + } + + private byte getClassStatus(String className) { + byte status = classStatusCache.getOrDefault(className, UNKNOWN); + if (status == UNKNOWN) { + classStatusCache.put(className, UNDECIDED); + status = computeClassStatus(className); + classStatusCache.put(className, status); + } + return status; + } + + private byte computeClassStatus(String className) { + if (!isSynthesizedClass(className)) { + if (underlyingStatus.isStaleClass(className)) { + return STALE; + } + } + + if (dependencyProvider.isNoCache(className)) { + return STALE; + } + + if (hasStaleDependencies(dependencyProvider.getDependencies(className))) { + return STALE; + } + + return FRESH; + } + + @Override + public boolean isStaleMethod(MethodReference method) { + return isStaleClass(method.getClassName()) || dependencyProvider.isNoCache(method) + || hasStaleDependencies(dependencyProvider.getDependencies(method)); + } + + private boolean hasStaleDependencies(String[] dependencies) { + for (String dependency : dependencies) { + byte dependencyStatus = getClassStatus(dependency); + if (dependencyStatus == STALE) { + return true; + } + } + + return false; + } + + private boolean isSynthesizedClass(String className) { + return synthesizedClasses.stream().anyMatch(p -> p.test(className)); + } +} diff --git a/core/src/main/java/org/teavm/cache/AstDependencyExtractor.java b/core/src/main/java/org/teavm/cache/AstDependencyExtractor.java new file mode 100644 index 000000000..08bfa1303 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/AstDependencyExtractor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 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.cache; + +import java.util.HashSet; +import java.util.Set; +import org.teavm.ast.AsyncMethodNode; +import org.teavm.ast.AsyncMethodPart; +import org.teavm.ast.InvocationExpr; +import org.teavm.ast.QualificationExpr; +import org.teavm.ast.RecursiveVisitor; +import org.teavm.ast.RegularMethodNode; + +public class AstDependencyExtractor extends RecursiveVisitor { + private final ExtractingVisitor visitor = new ExtractingVisitor(); + + public String[] extract(RegularMethodNode node) { + node.getBody().acceptVisitor(visitor); + String[] result = visitor.dependencies.toArray(new String[0]); + visitor.dependencies.clear(); + return result; + } + + public String[] extract(AsyncMethodNode node) { + for (AsyncMethodPart part : node.getBody()) { + part.getStatement().acceptVisitor(visitor); + } + String[] result = visitor.dependencies.toArray(new String[0]); + visitor.dependencies.clear(); + return result; + } + + static final class ExtractingVisitor extends RecursiveVisitor { + final Set dependencies = new HashSet<>(); + + @Override + public void visit(InvocationExpr expr) { + super.visit(expr); + dependencies.add(expr.getMethod().getClassName()); + } + + @Override + public void visit(QualificationExpr expr) { + super.visit(expr); + dependencies.add(expr.getField().getClassName()); + } + } +} diff --git a/core/src/main/java/org/teavm/cache/AstIO.java b/core/src/main/java/org/teavm/cache/AstIO.java index ff021333d..2aba47f03 100644 --- a/core/src/main/java/org/teavm/cache/AstIO.java +++ b/core/src/main/java/org/teavm/cache/AstIO.java @@ -83,6 +83,8 @@ public class AstIO { private final SymbolTable symbolTable; private final SymbolTable fileTable; private final Map statementMap = new HashMap<>(); + private Map methodDescriptorCache = new HashMap<>(); + private Map> methodReferenceCache = new HashMap<>(); public AstIO(SymbolTable symbolTable, SymbolTable fileTable) { this.symbolTable = symbolTable; @@ -949,8 +951,12 @@ public class AstIO { InvocationExpr expr = new InvocationExpr(); expr.setType(invocationType); String className = symbolTable.at(input.readInt()); - MethodDescriptor method = MethodDescriptor.parse(symbolTable.at(input.readInt())); - expr.setMethod(new MethodReference(className, method)); + MethodDescriptor method = methodDescriptorCache.computeIfAbsent(symbolTable.at(input.readInt()), + MethodDescriptor::parse); + MethodReference methodRef = methodReferenceCache + .computeIfAbsent(className, k -> new HashMap<>()) + .computeIfAbsent(method, k -> new MethodReference(className, k)); + expr.setMethod(methodRef); int argCount = input.readShort(); for (int i = 0; i < argCount; ++i) { expr.getArguments().add(readExpr(input)); diff --git a/core/src/main/java/org/teavm/cache/CacheStatus.java b/core/src/main/java/org/teavm/cache/CacheStatus.java new file mode 100644 index 000000000..f9154b5e2 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/CacheStatus.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 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.cache; + +import org.teavm.model.MethodReference; + +public interface CacheStatus { + boolean isStaleClass(String className); + + boolean isStaleMethod(MethodReference method); +} diff --git a/core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java b/core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java index 1ed20f574..83d20f5ef 100644 --- a/core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java +++ b/core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java @@ -15,12 +15,43 @@ */ package org.teavm.cache; -import java.io.*; -import java.util.*; -import org.teavm.model.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.model.AccessLevel; +import org.teavm.model.AnnotationContainer; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldHolder; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; import org.teavm.parsing.ClassDateProvider; -public class DiskCachedClassHolderSource implements ClassHolderSource { +public class DiskCachedClassHolderSource implements ClassHolderSource, CacheStatus { private static AccessLevel[] accessLevels = AccessLevel.values(); private static ElementModifier[] elementModifiers = ElementModifier.values(); private File directory; @@ -42,6 +73,20 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { @Override public ClassHolder get(String name) { + return getItemFromCache(name).cls; + } + + @Override + public boolean isStaleClass(String className) { + return getItemFromCache(className).dirty; + } + + @Override + public boolean isStaleMethod(MethodReference method) { + return isStaleClass(method.getClassName()); + } + + private Item getItemFromCache(String name) { Item item = cache.get(name); if (item == null) { item = new Item(); @@ -59,15 +104,17 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } } if (item.cls == null) { + item.dirty = true; item.cls = innerSource.get(name); newClasses.add(name); } } - return item.cls; + return item; } private static class Item { ClassHolder cls; + boolean dirty; } public void flush() throws IOException { diff --git a/core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java b/core/src/main/java/org/teavm/cache/DiskMethodNodeCache.java similarity index 67% rename from core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java rename to core/src/main/java/org/teavm/cache/DiskMethodNodeCache.java index f187a3723..efcfca36c 100644 --- a/core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java +++ b/core/src/main/java/org/teavm/cache/DiskMethodNodeCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,39 +25,30 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.teavm.ast.AsyncMethodNode; -import org.teavm.ast.AsyncMethodPart; -import org.teavm.ast.InvocationExpr; -import org.teavm.ast.QualificationExpr; -import org.teavm.ast.RecursiveVisitor; import org.teavm.ast.RegularMethodNode; -import org.teavm.ast.cache.MethodNodeCache; import org.teavm.model.MethodReference; -import org.teavm.parsing.ClassDateProvider; -public class DiskRegularMethodNodeCache implements MethodNodeCache { +public class DiskMethodNodeCache implements MethodNodeCache { private final File directory; private final AstIO astIO; - private final ClassDateProvider classDateProvider; private final Map cache = new HashMap<>(); private final Map asyncCache = new HashMap<>(); private final Set newMethods = new HashSet<>(); private final Set newAsyncMethods = new HashSet<>(); - public DiskRegularMethodNodeCache(File directory, SymbolTable symbolTable, SymbolTable fileTable, - ClassDateProvider classDateProvider) { + public DiskMethodNodeCache(File directory, SymbolTable symbolTable, SymbolTable fileTable) { this.directory = directory; astIO = new AstIO(symbolTable, fileTable); - this.classDateProvider = classDateProvider; } @Override - public RegularMethodNode get(MethodReference methodReference) { + public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) { Item item = cache.get(methodReference); if (item == null) { item = new Item(); @@ -66,7 +57,7 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { if (file.exists()) { try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) { DataInput input = new DataInputStream(stream); - if (!checkIfDependenciesChanged(input, file)) { + if (!checkIfDependenciesChanged(input, cacheStatus)) { item.node = astIO.read(input, methodReference); } } catch (IOException e) { @@ -78,15 +69,16 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { } @Override - public void store(MethodReference methodReference, RegularMethodNode node) { + public void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies) { Item item = new Item(); item.node = node; + item.dependencies = dependencies.get().clone(); cache.put(methodReference, item); newMethods.add(methodReference); } @Override - public AsyncMethodNode getAsync(MethodReference methodReference) { + public AsyncMethodNode getAsync(MethodReference methodReference, CacheStatus cacheStatus) { AsyncItem item = asyncCache.get(methodReference); if (item == null) { item = new AsyncItem(); @@ -95,7 +87,7 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { if (file.exists()) { try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) { DataInput input = new DataInputStream(stream); - if (!checkIfDependenciesChanged(input, file)) { + if (!checkIfDependenciesChanged(input, cacheStatus)) { item.node = astIO.readAsync(input, methodReference); } } catch (IOException e) { @@ -106,12 +98,11 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { return item.node; } - private boolean checkIfDependenciesChanged(DataInput input, File file) throws IOException { + private boolean checkIfDependenciesChanged(DataInput input, CacheStatus cacheStatus) throws IOException { int depCount = input.readShort(); for (int i = 0; i < depCount; ++i) { String depClass = input.readUTF(); - Date depDate = classDateProvider.getModificationDate(depClass); - if (depDate == null || depDate.after(new Date(file.lastModified()))) { + if (cacheStatus.isStaleClass(depClass)) { return true; } } @@ -119,9 +110,10 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { } @Override - public void storeAsync(MethodReference methodReference, AsyncMethodNode node) { + public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier depenencies) { AsyncItem item = new AsyncItem(); item.node = node; + item.dependencies = depenencies.get().clone(); asyncCache.put(methodReference, item); newAsyncMethods.add(methodReference); } @@ -129,32 +121,24 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { public void flush() throws IOException { for (MethodReference method : newMethods) { File file = getMethodFile(method, true); - AstDependencyAnalyzer analyzer = new AstDependencyAnalyzer(); - RegularMethodNode node = cache.get(method).node; - node.getBody().acceptVisitor(analyzer); - analyzer.dependencies.add(method.getClassName()); + Item item = cache.get(method); try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - output.writeShort(analyzer.dependencies.size()); - for (String dependency : analyzer.dependencies) { + output.writeShort(item.dependencies.length); + for (String dependency : item.dependencies) { output.writeUTF(dependency); } - astIO.write(output, node); + astIO.write(output, item.node); } } for (MethodReference method : newAsyncMethods) { File file = getMethodFile(method, true); - AstDependencyAnalyzer analyzer = new AstDependencyAnalyzer(); - AsyncMethodNode node = asyncCache.get(method).node; - for (AsyncMethodPart part : node.getBody()) { - part.getStatement().acceptVisitor(analyzer); - } - analyzer.dependencies.add(method.getClassName()); + AsyncItem item = asyncCache.get(method); try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - output.writeShort(analyzer.dependencies.size()); - for (String dependency : analyzer.dependencies) { + output.writeShort(item.dependencies.length); + for (String dependency : item.dependencies) { output.writeUTF(dependency); } - astIO.writeAsync(output, node); + astIO.writeAsync(output, item.node); } } } @@ -165,27 +149,13 @@ public class DiskRegularMethodNodeCache implements MethodNodeCache { + (async ? "-async" : "")); } - private static class AstDependencyAnalyzer extends RecursiveVisitor { - final Set dependencies = new HashSet<>(); - - @Override - public void visit(InvocationExpr expr) { - super.visit(expr); - dependencies.add(expr.getMethod().getClassName()); - } - - @Override - public void visit(QualificationExpr expr) { - super.visit(expr); - dependencies.add(expr.getField().getClassName()); - } - } - private static class Item { RegularMethodNode node; + String[] dependencies; } private static class AsyncItem { AsyncMethodNode node; + String[] dependencies; } } diff --git a/core/src/main/java/org/teavm/cache/DiskProgramCache.java b/core/src/main/java/org/teavm/cache/DiskProgramCache.java index 5da4a43be..4fd70e52c 100644 --- a/core/src/main/java/org/teavm/cache/DiskProgramCache.java +++ b/core/src/main/java/org/teavm/cache/DiskProgramCache.java @@ -15,28 +15,42 @@ */ package org.teavm.cache; -import java.io.*; -import java.util.*; -import org.teavm.model.*; -import org.teavm.model.instructions.*; -import org.teavm.parsing.ClassDateProvider; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ProgramCache; public class DiskProgramCache implements ProgramCache { private File directory; private ProgramIO programIO; private Map cache = new HashMap<>(); private Set newMethods = new HashSet<>(); - private ClassDateProvider classDateProvider; - public DiskProgramCache(File directory, SymbolTable symbolTable, SymbolTable fileTable, - ClassDateProvider classDateProvider) { + public DiskProgramCache(File directory, SymbolTable symbolTable, SymbolTable fileTable) { this.directory = directory; programIO = new ProgramIO(symbolTable, fileTable); - this.classDateProvider = classDateProvider; } @Override - public Program get(MethodReference method) { + public Program get(MethodReference method, CacheStatus cacheStatus) { Item item = cache.get(method); if (item == null) { item = new Item(); @@ -49,8 +63,7 @@ public class DiskProgramCache implements ProgramCache { boolean dependenciesChanged = false; for (int i = 0; i < depCount; ++i) { String depClass = input.readUTF(); - Date depDate = classDateProvider.getModificationDate(depClass); - if (depDate == null || depDate.after(new Date(file.lastModified()))) { + if (cacheStatus.isStaleClass(depClass)) { dependenciesChanged = true; break; } @@ -67,33 +80,29 @@ public class DiskProgramCache implements ProgramCache { } @Override - public void store(MethodReference method, Program program) { + public void store(MethodReference method, Program program, Supplier dependencies) { Item item = new Item(); cache.put(method, item); item.program = program; + item.dependencies = dependencies.get().clone(); newMethods.add(method); } - public void flush() throws IOException { + public void flush(ClassReaderSource classSource) throws IOException { + Date currentTime = new Date(); for (MethodReference method : newMethods) { + Item item = cache.get(method); File file = getMethodFile(method); - ProgramDependencyAnalyzer analyzer = new ProgramDependencyAnalyzer(); - analyzer.dependencies.add(method.getClassName()); - Program program = cache.get(method).program; - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - for (Instruction insn : block) { - insn.acceptVisitor(analyzer); - } - } file.getParentFile().mkdirs(); + try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(file))) { DataOutput output = new DataOutputStream(stream); - output.writeShort(analyzer.dependencies.size()); - for (String dep : analyzer.dependencies) { + + output.writeShort(item.dependencies.length); + for (String dep : item.dependencies) { output.writeUTF(dep); } - programIO.write(program, stream); + programIO.write(item.program, stream); } } } @@ -105,60 +114,6 @@ public class DiskProgramCache implements ProgramCache { static class Item { Program program; - } - - static class ProgramDependencyAnalyzer implements InstructionVisitor { - Set dependencies = new HashSet<>(); - @Override public void visit(GetFieldInstruction insn) { - dependencies.add(insn.getField().getClassName()); - } - @Override public void visit(PutFieldInstruction insn) { - dependencies.add(insn.getField().getClassName()); - } - @Override public void visit(InvokeInstruction insn) { - dependencies.add(insn.getMethod().getClassName()); - } - @Override - public void visit(InvokeDynamicInstruction insn) { - for (RuntimeConstant cst : insn.getBootstrapArguments()) { - if (cst.getKind() == RuntimeConstant.METHOD_HANDLE) { - MethodHandle handle = cst.getMethodHandle(); - dependencies.add(handle.getClassName()); - } - } - } - @Override public void visit(EmptyInstruction insn) { } - @Override public void visit(ClassConstantInstruction insn) { } - @Override public void visit(NullConstantInstruction insn) { } - @Override public void visit(IntegerConstantInstruction insn) { } - @Override public void visit(LongConstantInstruction insn) { } - @Override public void visit(FloatConstantInstruction insn) { } - @Override public void visit(DoubleConstantInstruction insn) { } - @Override public void visit(StringConstantInstruction insn) { } - @Override public void visit(BinaryInstruction insn) { } - @Override public void visit(NegateInstruction insn) { } - @Override public void visit(AssignInstruction insn) { } - @Override public void visit(CastInstruction insn) { } - @Override public void visit(CastNumberInstruction insn) { } - @Override public void visit(CastIntegerInstruction insn) { } - @Override public void visit(BranchingInstruction insn) { } - @Override public void visit(BinaryBranchingInstruction insn) { } - @Override public void visit(JumpInstruction insn) { } - @Override public void visit(SwitchInstruction insn) { } - @Override public void visit(ExitInstruction insn) { } - @Override public void visit(RaiseInstruction insn) { } - @Override public void visit(ConstructArrayInstruction insn) { } - @Override public void visit(ConstructInstruction insn) { } - @Override public void visit(ConstructMultiArrayInstruction insn) { } - @Override public void visit(ArrayLengthInstruction insn) { } - @Override public void visit(CloneArrayInstruction insn) { } - @Override public void visit(UnwrapArrayInstruction insn) { } - @Override public void visit(GetElementInstruction insn) { } - @Override public void visit(PutElementInstruction insn) { } - @Override public void visit(IsInstanceInstruction insn) { } - @Override public void visit(InitClassInstruction insn) { } - @Override public void visit(NullCheckInstruction insn) { } - @Override public void visit(MonitorEnterInstruction insn) { } - @Override public void visit(MonitorExitInstruction insn) { } + String[] dependencies; } } diff --git a/core/src/main/java/org/teavm/ast/cache/EmptyRegularMethodNodeCache.java b/core/src/main/java/org/teavm/cache/EmptyMethodNodeCache.java similarity index 70% rename from core/src/main/java/org/teavm/ast/cache/EmptyRegularMethodNodeCache.java rename to core/src/main/java/org/teavm/cache/EmptyMethodNodeCache.java index 4d8c077d0..68574b5d7 100644 --- a/core/src/main/java/org/teavm/ast/cache/EmptyRegularMethodNodeCache.java +++ b/core/src/main/java/org/teavm/cache/EmptyMethodNodeCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,28 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.ast.cache; +package org.teavm.cache; +import java.util.function.Supplier; import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.RegularMethodNode; import org.teavm.model.MethodReference; -public class EmptyRegularMethodNodeCache implements MethodNodeCache { +public class EmptyMethodNodeCache implements MethodNodeCache { + public static final EmptyMethodNodeCache INSTANCE = new EmptyMethodNodeCache(); + + private EmptyMethodNodeCache() { + } + @Override - public RegularMethodNode get(MethodReference methodReference) { + public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) { return null; } @Override - public void store(MethodReference methodReference, RegularMethodNode node) { + public void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies) { } @Override - public AsyncMethodNode getAsync(MethodReference methodReference) { + public AsyncMethodNode getAsync(MethodReference methodReference, CacheStatus cacheStatus) { return null; } @Override - public void storeAsync(MethodReference methodReference, AsyncMethodNode node) { + public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier dependencies) { } } diff --git a/core/src/main/java/org/teavm/cache/EmptyProgramCache.java b/core/src/main/java/org/teavm/cache/EmptyProgramCache.java new file mode 100644 index 000000000..80e46b52d --- /dev/null +++ b/core/src/main/java/org/teavm/cache/EmptyProgramCache.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 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.cache; + +import java.util.function.Supplier; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ProgramCache; + +public class EmptyProgramCache implements ProgramCache { + public static final EmptyProgramCache INSTANCE = new EmptyProgramCache(); + + private EmptyProgramCache() { + } + + @Override + public Program get(MethodReference method, CacheStatus status) { + return null; + } + + @Override + public void store(MethodReference method, Program program, Supplier dependencies) { + } +} diff --git a/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java new file mode 100644 index 000000000..f1a78daee --- /dev/null +++ b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 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.cache; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.teavm.ast.AsyncMethodNode; +import org.teavm.ast.RegularMethodNode; +import org.teavm.model.MethodReference; + +public class InMemoryMethodNodeCache implements MethodNodeCache { + private Map cache = new HashMap<>(); + private Map asyncCache = new HashMap<>(); + + @Override + public RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus) { + RegularItem item = cache.get(methodReference); + if (item == null) { + return null; + } + + if (Arrays.stream(item.dependencies).anyMatch(cacheStatus::isStaleClass)) { + return null; + } + + return item.node; + } + + @Override + public void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies) { + cache.put(methodReference, new RegularItem(node, dependencies.get().clone())); + } + + @Override + public AsyncMethodNode getAsync(MethodReference methodReference, CacheStatus cacheStatus) { + AsyncItem item = asyncCache.get(methodReference); + if (item == null) { + return null; + } + + if (Arrays.stream(item.dependencies).anyMatch(cacheStatus::isStaleClass)) { + return null; + } + + return item.node; + } + + @Override + public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier dependencies) { + asyncCache.put(methodReference, new AsyncItem(node, dependencies.get().clone())); + } + + static final class RegularItem { + final RegularMethodNode node; + final String[] dependencies; + + RegularItem(RegularMethodNode node, String[] dependencies) { + this.node = node; + this.dependencies = dependencies; + } + } + + static final class AsyncItem { + final AsyncMethodNode node; + final String[] dependencies; + + AsyncItem(AsyncMethodNode node, String[] dependencies) { + this.node = node; + this.dependencies = dependencies; + } + } +} diff --git a/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java new file mode 100644 index 000000000..919714072 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 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.cache; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ProgramCache; + +public class InMemoryProgramCache implements ProgramCache { + private Map cache = new HashMap<>(); + + @Override + public Program get(MethodReference method, CacheStatus cacheStatus) { + Item item = cache.get(method); + if (item == null) { + return null; + } + + if (Arrays.stream(item.dependencies).anyMatch(cacheStatus::isStaleClass)) { + return null; + } + + return item.program; + } + + @Override + public void store(MethodReference method, Program program, Supplier dependencies) { + cache.put(method, new Item(program, dependencies.get().clone())); + } + + static final class Item { + final Program program; + final String[] dependencies; + + Item(Program program, String[] dependencies) { + this.program = program; + this.dependencies = dependencies; + } + } +} diff --git a/core/src/main/java/org/teavm/cache/IncrementalDependencyProvider.java b/core/src/main/java/org/teavm/cache/IncrementalDependencyProvider.java new file mode 100644 index 000000000..32b15ac2b --- /dev/null +++ b/core/src/main/java/org/teavm/cache/IncrementalDependencyProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.cache; + +import org.teavm.model.MethodReference; + +public interface IncrementalDependencyProvider { + boolean isNoCache(String className); + + boolean isNoCache(MethodReference method); + + String[] getDependencies(String className); + + String[] getDependencies(MethodReference method); +} diff --git a/core/src/main/java/org/teavm/cache/IncrementalDependencyRegistration.java b/core/src/main/java/org/teavm/cache/IncrementalDependencyRegistration.java new file mode 100644 index 000000000..f7d77ea54 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/IncrementalDependencyRegistration.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.cache; + +import org.teavm.model.MethodReference; + +public interface IncrementalDependencyRegistration { + void setNoCache(String className); + + void setNoCache(MethodReference method); + + void addDependencies(String className, String... dependencies); + + void addDependencies(MethodReference method, String... dependencies); +} diff --git a/core/src/main/java/org/teavm/ast/cache/MethodNodeCache.java b/core/src/main/java/org/teavm/cache/MethodNodeCache.java similarity index 70% rename from core/src/main/java/org/teavm/ast/cache/MethodNodeCache.java rename to core/src/main/java/org/teavm/cache/MethodNodeCache.java index 8f189c738..232daa0fa 100644 --- a/core/src/main/java/org/teavm/ast/cache/MethodNodeCache.java +++ b/core/src/main/java/org/teavm/cache/MethodNodeCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.ast.cache; +package org.teavm.cache; +import java.util.function.Supplier; import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.RegularMethodNode; import org.teavm.model.MethodReference; public interface MethodNodeCache { - RegularMethodNode get(MethodReference methodReference); + RegularMethodNode get(MethodReference methodReference, CacheStatus cacheStatus); - void store(MethodReference methodReference, RegularMethodNode node); + void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies); - AsyncMethodNode getAsync(MethodReference methodReference); + AsyncMethodNode getAsync(MethodReference methodReference, CacheStatus cacheStatus); - void storeAsync(MethodReference methodReference, AsyncMethodNode node); + void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier dependencies); } diff --git a/core/src/main/java/org/teavm/cache/ProgramDependencyExtractor.java b/core/src/main/java/org/teavm/cache/ProgramDependencyExtractor.java new file mode 100644 index 000000000..7ad463039 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/ProgramDependencyExtractor.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 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.cache; + +import java.util.LinkedHashSet; +import java.util.Set; +import org.teavm.model.BasicBlock; +import org.teavm.model.Instruction; +import org.teavm.model.InvokeDynamicInstruction; +import org.teavm.model.MethodHandle; +import org.teavm.model.Program; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.PutFieldInstruction; + +public class ProgramDependencyExtractor extends AbstractInstructionVisitor { + private final AnalyzingVisitor visitor = new AnalyzingVisitor(); + + public String[] extractDependencies(Program program) { + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + instruction.acceptVisitor(visitor); + } + } + String[] result = visitor.dependencies.toArray(new String[0]); + visitor.dependencies.clear(); + return result; + } + + class AnalyzingVisitor extends AbstractInstructionVisitor { + Set dependencies = new LinkedHashSet<>(); + @Override public void visit(GetFieldInstruction insn) { + dependencies.add(insn.getField().getClassName()); + } + @Override public void visit(PutFieldInstruction insn) { + dependencies.add(insn.getField().getClassName()); + } + @Override public void visit(InvokeInstruction insn) { + dependencies.add(insn.getMethod().getClassName()); + } + @Override + public void visit(InvokeDynamicInstruction insn) { + for (RuntimeConstant cst : insn.getBootstrapArguments()) { + if (cst.getKind() == RuntimeConstant.METHOD_HANDLE) { + MethodHandle handle = cst.getMethodHandle(); + dependencies.add(handle.getClassName()); + } + } + } + } +} diff --git a/core/src/main/java/org/teavm/cache/ProgramIO.java b/core/src/main/java/org/teavm/cache/ProgramIO.java index dfcc7b562..25a8ab3f6 100644 --- a/core/src/main/java/org/teavm/cache/ProgramIO.java +++ b/core/src/main/java/org/teavm/cache/ProgramIO.java @@ -15,14 +15,81 @@ */ package org.teavm.cache; -import java.io.*; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Objects; -import org.teavm.model.*; -import org.teavm.model.instructions.*; +import org.teavm.model.BasicBlock; +import org.teavm.model.FieldReference; +import org.teavm.model.Incoming; +import org.teavm.model.Instruction; +import org.teavm.model.InvokeDynamicInstruction; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHandle; +import org.teavm.model.MethodReference; +import org.teavm.model.Phi; +import org.teavm.model.Program; +import org.teavm.model.ReferenceCache; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.TextLocation; +import org.teavm.model.TryCatchBlock; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ArrayElementType; +import org.teavm.model.instructions.ArrayLengthInstruction; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.BinaryBranchingCondition; +import org.teavm.model.instructions.BinaryBranchingInstruction; +import org.teavm.model.instructions.BinaryInstruction; +import org.teavm.model.instructions.BinaryOperation; +import org.teavm.model.instructions.BranchingCondition; +import org.teavm.model.instructions.BranchingInstruction; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.CastIntegerDirection; +import org.teavm.model.instructions.CastIntegerInstruction; +import org.teavm.model.instructions.CastNumberInstruction; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.CloneArrayInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; +import org.teavm.model.instructions.DoubleConstantInstruction; +import org.teavm.model.instructions.EmptyInstruction; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.FloatConstantInstruction; +import org.teavm.model.instructions.GetElementInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.InstructionVisitor; +import org.teavm.model.instructions.IntegerConstantInstruction; +import org.teavm.model.instructions.IntegerSubtype; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.IsInstanceInstruction; +import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.MonitorExitInstruction; +import org.teavm.model.instructions.NegateInstruction; +import org.teavm.model.instructions.NullCheckInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.instructions.NumericOperandType; +import org.teavm.model.instructions.PutElementInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.instructions.SwitchInstruction; +import org.teavm.model.instructions.SwitchTableEntry; +import org.teavm.model.instructions.UnwrapArrayInstruction; public class ProgramIO { private SymbolTable symbolTable; private SymbolTable fileTable; + private ReferenceCache referenceCache = new ReferenceCache(); private static BinaryOperation[] binaryOperations = BinaryOperation.values(); private static NumericOperandType[] numericOperandTypes = NumericOperandType.values(); private static IntegerSubtype[] integerSubtypes = IntegerSubtype.values(); @@ -733,7 +800,7 @@ public class ProgramIO { case 1: { ClassConstantInstruction insn = new ClassConstantInstruction(); insn.setReceiver(program.variableAt(input.readShort())); - insn.setConstant(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setConstant(parseValueType(symbolTable.at(input.readInt()))); return insn; } case 2: { @@ -798,7 +865,7 @@ public class ProgramIO { case 11: { CastInstruction insn = new CastInstruction(); insn.setReceiver(program.variableAt(input.readShort())); - insn.setTargetType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setTargetType(parseValueType(symbolTable.at(input.readInt()))); insn.setValue(program.variableAt(input.readShort())); return insn; } @@ -870,7 +937,7 @@ public class ProgramIO { case 21: { ConstructArrayInstruction insn = new ConstructArrayInstruction(); insn.setReceiver(program.variableAt(input.readShort())); - insn.setItemType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setItemType(parseValueType(symbolTable.at(input.readInt()))); insn.setSize(program.variableAt(input.readShort())); return insn; } @@ -883,7 +950,7 @@ public class ProgramIO { case 23: { ConstructMultiArrayInstruction insn = new ConstructMultiArrayInstruction(); insn.setReceiver(program.variableAt(input.readShort())); - insn.setItemType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setItemType(parseValueType(symbolTable.at(input.readInt()))); int dimensionCount = input.readByte(); for (int i = 0; i < dimensionCount; ++i) { insn.getDimensions().add(program.variableAt(input.readShort())); @@ -897,7 +964,7 @@ public class ProgramIO { String className = symbolTable.at(input.readInt()); String fieldName = symbolTable.at(input.readInt()); insn.setField(new FieldReference(className, fieldName)); - insn.setFieldType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setFieldType(parseValueType(symbolTable.at(input.readInt()))); return insn; } case 25: { @@ -906,7 +973,7 @@ public class ProgramIO { String className = symbolTable.at(input.readInt()); String fieldName = symbolTable.at(input.readInt()); insn.setField(new FieldReference(className, fieldName)); - insn.setFieldType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setFieldType(parseValueType(symbolTable.at(input.readInt()))); return insn; } case 26: { @@ -914,7 +981,7 @@ public class ProgramIO { insn.setInstance(program.variableAt(input.readShort())); String className = symbolTable.at(input.readInt()); String fieldName = symbolTable.at(input.readInt()); - ValueType type = ValueType.parse(symbolTable.at(input.readInt())); + ValueType type = parseValueType(symbolTable.at(input.readInt())); insn.setField(new FieldReference(className, fieldName)); insn.setValue(program.variableAt(input.readShort())); insn.setFieldType(type); @@ -924,7 +991,7 @@ public class ProgramIO { PutFieldInstruction insn = new PutFieldInstruction(); String className = symbolTable.at(input.readInt()); String fieldName = symbolTable.at(input.readInt()); - ValueType type = ValueType.parse(symbolTable.at(input.readInt())); + ValueType type = parseValueType(symbolTable.at(input.readInt())); insn.setField(new FieldReference(className, fieldName)); insn.setValue(program.variableAt(input.readShort())); insn.setFieldType(type); @@ -969,8 +1036,8 @@ public class ProgramIO { int receiverIndex = input.readShort(); insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); String className = symbolTable.at(input.readInt()); - MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); - insn.setMethod(new MethodReference(className, methodDesc)); + MethodDescriptor methodDesc = parseMethodDescriptor(symbolTable.at(input.readInt())); + insn.setMethod(createMethodReference(className, methodDesc)); int paramCount = insn.getMethod().getDescriptor().parameterCount(); for (int i = 0; i < paramCount; ++i) { insn.getArguments().add(program.variableAt(input.readShort())); @@ -984,8 +1051,8 @@ public class ProgramIO { insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); insn.setInstance(program.variableAt(input.readShort())); String className = symbolTable.at(input.readInt()); - MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); - insn.setMethod(new MethodReference(className, methodDesc)); + MethodDescriptor methodDesc = parseMethodDescriptor(symbolTable.at(input.readInt())); + insn.setMethod(createMethodReference(className, methodDesc)); int paramCount = insn.getMethod().getDescriptor().parameterCount(); for (int i = 0; i < paramCount; ++i) { insn.getArguments().add(program.variableAt(input.readShort())); @@ -999,8 +1066,8 @@ public class ProgramIO { insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); insn.setInstance(program.variableAt(input.readShort())); String className = symbolTable.at(input.readInt()); - MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); - insn.setMethod(new MethodReference(className, methodDesc)); + MethodDescriptor methodDesc = parseMethodDescriptor(symbolTable.at(input.readInt())); + insn.setMethod(createMethodReference(className, methodDesc)); int paramCount = insn.getMethod().getDescriptor().parameterCount(); for (int i = 0; i < paramCount; ++i) { insn.getArguments().add(program.variableAt(input.readShort())); @@ -1010,7 +1077,7 @@ public class ProgramIO { case 36: { IsInstanceInstruction insn = new IsInstanceInstruction(); insn.setReceiver(program.variableAt(input.readShort())); - insn.setType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setType(parseValueType(symbolTable.at(input.readInt()))); insn.setValue(program.variableAt(input.readShort())); return insn; } @@ -1041,7 +1108,7 @@ public class ProgramIO { short instance = input.readShort(); insn.setReceiver(receiver >= 0 ? program.variableAt(receiver) : null); insn.setInstance(instance >= 0 ? program.variableAt(instance) : null); - insn.setMethod(MethodDescriptor.parse(symbolTable.at(input.readInt()))); + insn.setMethod(parseMethodDescriptor(symbolTable.at(input.readInt()))); int argsCount = insn.getMethod().parameterCount(); for (int i = 0; i < argsCount; ++i) { insn.getArguments().add(program.variableAt(input.readShort())); @@ -1063,31 +1130,31 @@ public class ProgramIO { switch (kind) { case 0: return MethodHandle.fieldGetter(symbolTable.at(input.readInt()), symbolTable.at(input.readInt()), - ValueType.parse(symbolTable.at(input.readInt()))); + parseValueType(symbolTable.at(input.readInt()))); case 1: return MethodHandle.staticFieldGetter(symbolTable.at(input.readInt()), symbolTable.at(input.readInt()), - ValueType.parse(symbolTable.at(input.readInt()))); + parseValueType(symbolTable.at(input.readInt()))); case 2: return MethodHandle.fieldSetter(symbolTable.at(input.readInt()), symbolTable.at(input.readInt()), - ValueType.parse(symbolTable.at(input.readInt()))); + parseValueType(symbolTable.at(input.readInt()))); case 3: return MethodHandle.staticFieldSetter(symbolTable.at(input.readInt()), symbolTable.at(input.readInt()), - ValueType.parse(symbolTable.at(input.readInt()))); + parseValueType(symbolTable.at(input.readInt()))); case 4: return MethodHandle.virtualCaller(symbolTable.at(input.readInt()), - MethodDescriptor.parse(symbolTable.at(input.readInt()))); + parseMethodDescriptor(symbolTable.at(input.readInt()))); case 5: return MethodHandle.staticCaller(symbolTable.at(input.readInt()), - MethodDescriptor.parse(symbolTable.at(input.readInt()))); + parseMethodDescriptor(symbolTable.at(input.readInt()))); case 6: return MethodHandle.specialCaller(symbolTable.at(input.readInt()), - MethodDescriptor.parse(symbolTable.at(input.readInt()))); + parseMethodDescriptor(symbolTable.at(input.readInt()))); case 7: return MethodHandle.constructorCaller(symbolTable.at(input.readInt()), - MethodDescriptor.parse(symbolTable.at(input.readInt()))); + parseMethodDescriptor(symbolTable.at(input.readInt()))); case 8: return MethodHandle.interfaceCaller(symbolTable.at(input.readInt()), - MethodDescriptor.parse(symbolTable.at(input.readInt()))); + parseMethodDescriptor(symbolTable.at(input.readInt()))); default: throw new IllegalArgumentException("Unexpected method handle type: " + kind); } @@ -1107,7 +1174,7 @@ public class ProgramIO { case 4: return new RuntimeConstant(input.readUTF()); case 5: - return new RuntimeConstant(ValueType.parse(symbolTable.at(input.readInt()))); + return new RuntimeConstant(parseValueType(symbolTable.at(input.readInt()))); case 6: return new RuntimeConstant(MethodDescriptor.parseSignature(symbolTable.at(input.readInt()))); case 7: @@ -1116,4 +1183,16 @@ public class ProgramIO { throw new IllegalArgumentException("Unexpected runtime constant type: " + kind); } } + + private MethodDescriptor parseMethodDescriptor(String key) { + return referenceCache.parseDescriptorCached(key); + } + + private ValueType parseValueType(String key) { + return referenceCache.parseValueTypeCached(key); + } + + private MethodReference createMethodReference(String className, MethodDescriptor method) { + return referenceCache.getCached(className, method); + } } diff --git a/core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java b/core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java index 09622f134..341a8a4aa 100644 --- a/core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java +++ b/core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java @@ -38,7 +38,7 @@ public class DefaultCallGraph implements CallGraph, Serializable { @Override public DefaultCallGraphNode getNode(MethodReference method) { - return nodes.computeIfAbsent(method, k -> new DefaultCallGraphNode(this, method)); + return nodes.computeIfAbsent(method, k -> new DefaultCallGraphNode(this, k)); } @Override diff --git a/core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java b/core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java index 2cc32c775..180edacdd 100644 --- a/core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java +++ b/core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java @@ -28,7 +28,7 @@ import org.teavm.model.TextLocation; public class DefaultCallGraphNode implements CallGraphNode { private DefaultCallGraph graph; private MethodReference method; - private Set callSites = new LinkedHashSet<>(); + private Set callSites = new LinkedHashSet<>(10, 0.5f); private Set safeCallSites; private List callerCallSites = new ArrayList<>(); private List safeCallersCallSites; diff --git a/core/src/main/java/org/teavm/callgraph/DefaultFieldAccessSite.java b/core/src/main/java/org/teavm/callgraph/DefaultFieldAccessSite.java index 7a3196164..a7621719a 100644 --- a/core/src/main/java/org/teavm/callgraph/DefaultFieldAccessSite.java +++ b/core/src/main/java/org/teavm/callgraph/DefaultFieldAccessSite.java @@ -48,7 +48,7 @@ public class DefaultFieldAccessSite implements FieldAccessSite, Serializable { @Override public int hashCode() { - return Objects.hash(location, callee, field); + return Objects.hash(location, field); } @Override @@ -60,7 +60,6 @@ public class DefaultFieldAccessSite implements FieldAccessSite, Serializable { return false; } DefaultFieldAccessSite other = (DefaultFieldAccessSite) obj; - return Objects.equals(location, other.location) && Objects.equals(callee, other.callee) - && Objects.equals(field, other.field); + return Objects.equals(location, other.location) && Objects.equals(field, other.field); } } diff --git a/core/src/main/java/org/teavm/common/OptionalPredicate.java b/core/src/main/java/org/teavm/common/OptionalPredicate.java new file mode 100644 index 000000000..a5a425adf --- /dev/null +++ b/core/src/main/java/org/teavm/common/OptionalPredicate.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018 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.common; + +public interface OptionalPredicate { + boolean test(T value, boolean defaultResult); +} diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java index 808568a7e..268fe8ab1 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java @@ -22,6 +22,7 @@ import org.teavm.common.RecordArray; import org.teavm.common.RecordArrayBuilder; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; +import org.teavm.model.ReferenceCache; public class DebugInformation { String[] fileNames; @@ -50,6 +51,7 @@ public class DebugInformation { Map classMetadataByJsName; RecordArray methodEntrances; MethodTree methodTree; + ReferenceCache referenceCache = new ReferenceCache(); public String[] getFilesNames() { return fileNames.clone(); @@ -82,13 +84,13 @@ public class DebugInformation { public MethodDescriptor[] getMethods() { MethodDescriptor[] descriptors = new MethodDescriptor[methods.length]; for (int i = 0; i < descriptors.length; ++i) { - descriptors[i] = MethodDescriptor.parse(methods[i]); + descriptors[i] = referenceCache.parseDescriptorCached(methods[i]); } return descriptors; } public MethodDescriptor getMethod(int methodId) { - return MethodDescriptor.parse(methods[methodId]); + return referenceCache.parseDescriptorCached(methods[methodId]); } public MethodReference[] getExactMethods() { @@ -115,7 +117,8 @@ public class DebugInformation { long item = exactMethods[index]; int classIndex = (int) (item >>> 32); int methodIndex = (int) item; - return new MethodReference(classNames[classIndex], MethodDescriptor.parse(methods[methodIndex])); + return referenceCache.getCached(classNames[classIndex], referenceCache.parseDescriptorCached( + methods[methodIndex])); } public int getExactMethodId(int classNameId, int methodId) { @@ -186,7 +189,7 @@ public class DebugInformation { if (method == null) { return null; } - return new MethodReference(className, MethodDescriptor.parse(method)); + return referenceCache.getCached(className, referenceCache.parseDescriptorCached(method)); } public MethodReference getMethodAt(int line, int column) { @@ -678,8 +681,8 @@ public class DebugInformation { long item = exactMethods[data[start + i]]; int classIndex = (int) (item >>> 32); int methodIndex = (int) item; - references[i] = new MethodReference(classNames[classIndex], - MethodDescriptor.parse(methods[methodIndex])); + references[i] = referenceCache.getCached(classNames[classIndex], + referenceCache.parseDescriptorCached(methods[methodIndex])); } return references; } diff --git a/core/src/main/java/org/teavm/dependency/AbstractDependencyListener.java b/core/src/main/java/org/teavm/dependency/AbstractDependencyListener.java index da335f9b4..f9e6b99a0 100644 --- a/core/src/main/java/org/teavm/dependency/AbstractDependencyListener.java +++ b/core/src/main/java/org/teavm/dependency/AbstractDependencyListener.java @@ -15,23 +15,21 @@ */ package org.teavm.dependency; -import org.teavm.model.CallLocation; - public abstract class AbstractDependencyListener implements DependencyListener { @Override public void started(DependencyAgent agent) { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { } @Override - public void fieldReached(DependencyAgent agent, FieldDependency field, CallLocation location) { + public void fieldReached(DependencyAgent agent, FieldDependency field) { } @Override diff --git a/core/src/main/java/org/teavm/dependency/AbstractInstructionAnalyzer.java b/core/src/main/java/org/teavm/dependency/AbstractInstructionAnalyzer.java new file mode 100644 index 000000000..8d7dfc024 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/AbstractInstructionAnalyzer.java @@ -0,0 +1,279 @@ +/* + * Copyright 2018 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.dependency; + +import java.util.List; +import java.util.Objects; +import org.teavm.model.CallLocation; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHandle; +import org.teavm.model.MethodReference; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.model.instructions.InvocationType; + +abstract class AbstractInstructionAnalyzer extends AbstractInstructionReader { + private static final MethodReference STRING_INIT_FROM_CHARS_METHOD = new MethodReference(String.class, + "", char[].class, void.class); + static final MethodReference CLONE_METHOD = new MethodReference(Object.class, "clone", Object.class); + private static final MethodReference NPE_INIT_METHOD = new MethodReference(NullPointerException.class, + "", void.class); + static final MethodReference MONITOR_ENTER_METHOD = new MethodReference(Object.class, + "monitorEnter", Object.class, void.class); + static final MethodReference MONITOR_ENTER_SYNC_METHOD = new MethodReference(Object.class, + "monitorEnterSync", Object.class, void.class); + static final MethodReference MONITOR_EXIT_METHOD = new MethodReference(Object.class, + "monitorExit", Object.class, void.class); + static final MethodReference MONITOR_EXIT_SYNC_METHOD = new MethodReference(Object.class, + "monitorExitSync", Object.class, void.class); + + protected TextLocation location; + protected MethodReference caller; + protected CallLocation callLocation; + + public void setCaller(MethodReference caller) { + this.caller = caller; + callLocation = null; + } + + @Override + public void location(TextLocation location) { + if (!Objects.equals(this.location, location)) { + this.location = location; + callLocation = null; + } + } + + @Override + public void classConstant(VariableReader receiver, ValueType cst) { + DependencyNode node = getNode(receiver); + if (node != null) { + node.propagate(getAnalyzer().getType("java.lang.Class")); + if (!(cst instanceof ValueType.Primitive)) { + StringBuilder sb = new StringBuilder(); + if (cst instanceof ValueType.Object) { + sb.append(((ValueType.Object) cst).getClassName()); + } else { + sb.append(cst.toString()); + } + node.getClassValueNode().propagate(getAnalyzer().getType(sb.toString())); + } else { + node.getClassValueNode().propagate(getAnalyzer().getType("~" + cst.toString())); + } + } + while (cst instanceof ValueType.Array) { + cst = ((ValueType.Array) cst).getItemType(); + } + if (cst instanceof ValueType.Object) { + String className = ((ValueType.Object) cst).getClassName(); + getAnalyzer().linkClass(className); + } + } + + @Override + public void stringConstant(VariableReader receiver, String cst) { + DependencyNode node = getNode(receiver); + if (node != null) { + node.propagate(getAnalyzer().getType("java.lang.String")); + } + MethodDependency method = getAnalyzer().linkMethod(STRING_INIT_FROM_CHARS_METHOD); + method.addLocation(getCallLocation()); + method.use(); + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { + DependencyNode node = getNode(receiver); + if (node != null) { + node.propagate(getAnalyzer().getType("[" + itemType)); + } + String className = extractClassName(itemType); + if (className != null) { + getAnalyzer().linkClass(className); + } + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, List dimensions) { + DependencyNode node = getNode(receiver); + for (int i = 0; i < dimensions.size(); ++i) { + if (node == null) { + break; + } + String itemTypeStr; + if (itemType instanceof ValueType.Object) { + itemTypeStr = ((ValueType.Object) itemType).getClassName(); + } else { + itemTypeStr = itemType.toString(); + } + node.propagate(getAnalyzer().getType(itemTypeStr)); + node = node.getArrayItem(); + itemType = ((ValueType.Array) itemType).getItemType(); + } + String className = extractClassName(itemType); + if (className != null) { + getAnalyzer().linkClass(className); + } + } + + protected final String extractClassName(ValueType itemType) { + while (itemType instanceof ValueType.Array) { + itemType = ((ValueType.Array) itemType).getItemType(); + } + return itemType instanceof ValueType.Object ? ((ValueType.Object) itemType).getClassName() : null; + } + + @Override + public void create(VariableReader receiver, String type) { + getAnalyzer().linkClass(type); + DependencyNode node = getNode(receiver); + if (node != null) { + node.propagate(getAnalyzer().getType(type)); + } + } + + @Override + public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { + FieldDependency fieldDep = getAnalyzer().linkField(field); + fieldDep.addLocation(getCallLocation()); + if (!(fieldType instanceof ValueType.Primitive)) { + DependencyNode receiverNode = getNode(receiver); + if (receiverNode != null) { + fieldDep.getValue().connect(receiverNode); + } + } + initClass(field.getClassName()); + } + + @Override + public void putField(VariableReader instance, FieldReference field, VariableReader value, + ValueType fieldType) { + FieldDependency fieldDep = getAnalyzer().linkField(field); + fieldDep.addLocation(getCallLocation()); + if (!(fieldType instanceof ValueType.Primitive)) { + DependencyNode valueNode = getNode(value); + if (valueNode != null) { + valueNode.connect(fieldDep.getValue()); + } + } + initClass(field.getClassName()); + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + if (instance == null) { + invokeSpecial(receiver, null, method, arguments); + } else { + switch (type) { + case SPECIAL: + invokeSpecial(receiver, instance, method, arguments); + break; + case VIRTUAL: + invokeVirtual(receiver, instance, method, arguments); + break; + } + if (method.getName().equals("getClass") && method.parameterCount() == 0 + && method.getReturnType().isObject(Class.class) && receiver != null) { + getNode(instance).connect(getNode(receiver).getClassValueNode()); + } + } + } + + protected abstract void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments); + + protected abstract void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments); + + @Override + public void invokeDynamic(VariableReader receiver, VariableReader instance, MethodDescriptor method, + List arguments, MethodHandle bootstrapMethod, + List bootstrapArguments) { + // Should be eliminated by processInvokeDynamic method + } + + @Override + public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { + String className = extractClassName(type); + if (className != null) { + getAnalyzer().linkClass(className); + } + } + + @Override + public void initClass(String className) { + getAnalyzer().linkClass(className).initClass(getCallLocation()); + } + + @Override + public void nullCheck(VariableReader receiver, VariableReader value) { + DependencyNode valueNode = getNode(value); + DependencyNode receiverNode = getNode(receiver); + if (valueNode != null) { + valueNode.connect(receiverNode); + } + MethodDependency npeMethod = getAnalyzer().linkMethod(NPE_INIT_METHOD); + npeMethod.addLocation(getCallLocation()); + npeMethod.use(); + } + + @Override + public void monitorEnter(VariableReader objectRef) { + if (getAnalyzer().asyncSupported) { + MethodDependency methodDep = getAnalyzer().linkMethod(MONITOR_ENTER_METHOD); + methodDep.addLocation(getCallLocation()); + getNode(objectRef).connect(methodDep.getVariable(1)); + methodDep.use(); + } + + MethodDependency methodDep = getAnalyzer().linkMethod(MONITOR_ENTER_SYNC_METHOD); + methodDep.addLocation(getCallLocation()); + getNode(objectRef).connect(methodDep.getVariable(1)); + methodDep.use(); + } + + @Override + public void monitorExit(VariableReader objectRef) { + if (getAnalyzer().asyncSupported) { + MethodDependency methodDep = getAnalyzer().linkMethod(MONITOR_EXIT_METHOD); + methodDep.addLocation(getCallLocation()); + getNode(objectRef).connect(methodDep.getVariable(1)); + methodDep.use(); + } + + MethodDependency methodDep = getAnalyzer().linkMethod(MONITOR_EXIT_SYNC_METHOD); + methodDep.addLocation(getCallLocation()); + getNode(objectRef).connect(methodDep.getVariable(1)); + methodDep.use(); + } + + protected abstract DependencyNode getNode(VariableReader variable); + + protected abstract DependencyAnalyzer getAnalyzer(); + + protected CallLocation getCallLocation() { + if (callLocation == null) { + callLocation = new CallLocation(caller, location); + } + return callLocation; + } +} diff --git a/core/src/main/java/org/teavm/dependency/ClassDependency.java b/core/src/main/java/org/teavm/dependency/ClassDependency.java index 5c0ada184..0bc91cfda 100644 --- a/core/src/main/java/org/teavm/dependency/ClassDependency.java +++ b/core/src/main/java/org/teavm/dependency/ClassDependency.java @@ -22,6 +22,7 @@ public class ClassDependency implements ClassDependencyInfo { private DependencyAnalyzer analyzer; private String className; private ClassReader classReader; + boolean activated; ClassDependency(DependencyAnalyzer analyzer, String className, ClassReader classReader) { this.analyzer = analyzer; @@ -43,9 +44,9 @@ public class ClassDependency implements ClassDependencyInfo { return classReader; } - public void initClass(CallLocation callLocation) { + public void initClass(CallLocation location) { if (!isMissing()) { - analyzer.initClass(this, callLocation); + analyzer.initClass(this, location); } } } diff --git a/core/src/main/java/org/teavm/dependency/DependencyAgent.java b/core/src/main/java/org/teavm/dependency/DependencyAgent.java index b044ec403..bed54fac3 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAgent.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAgent.java @@ -16,6 +16,7 @@ package org.teavm.dependency; import java.util.Collection; +import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.callgraph.CallGraph; import org.teavm.common.ServiceRepository; import org.teavm.diagnostics.Diagnostics; @@ -52,16 +53,20 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository { analyzer.submitMethod(method, program); } - public MethodDependency linkMethod(MethodReference methodRef, CallLocation callLocation) { - return analyzer.linkMethod(methodRef, callLocation); + public MethodDependency linkMethod(MethodReference methodRef) { + return analyzer.linkMethod(methodRef); } - public ClassDependency linkClass(String className, CallLocation callLocation) { - return analyzer.linkClass(className, callLocation); + public MethodDependency linkMethod(String className, MethodDescriptor descriptor) { + return analyzer.linkMethod(className, descriptor); } - public FieldDependency linkField(FieldReference fieldRef, CallLocation callLocation) { - return analyzer.linkField(fieldRef, callLocation); + public ClassDependency linkClass(String className) { + return analyzer.linkClass(className); + } + + public FieldDependency linkField(FieldReference fieldRef) { + return analyzer.linkField(fieldRef); } public Diagnostics getDiagnostics() { @@ -83,6 +88,11 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository { return analyzer.getClassLoader(); } + @Override + public ClassHierarchy getClassHierarchy() { + return analyzer.getClassHierarchy(); + } + @Override public Collection getReachableMethods() { return analyzer.getReachableMethods(); @@ -122,4 +132,8 @@ public class DependencyAgent implements DependencyInfo, ServiceRepository { public CallGraph getCallGraph() { return analyzer.getCallGraph(); } + + public IncrementalDependencyRegistration getIncrementalCache() { + return analyzer.incrementalCache; + } } diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index 042018743..49d6f6a78 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,24 @@ import com.carrotsearch.hppc.IntSet; import com.carrotsearch.hppc.cursors.IntCursor; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.Set; import org.objectweb.asm.tree.ClassNode; +import org.teavm.cache.IncrementalDependencyProvider; +import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.callgraph.CallGraph; +import org.teavm.callgraph.CallGraphNode; import org.teavm.callgraph.DefaultCallGraph; import org.teavm.common.CachedMapper; import org.teavm.common.Mapper; @@ -38,7 +45,9 @@ import org.teavm.common.ServiceRepository; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.PlatformMarker; import org.teavm.model.AnnotationReader; +import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; @@ -47,6 +56,8 @@ import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; +import org.teavm.model.Instruction; +import org.teavm.model.InvokeDynamicInstruction; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; @@ -54,23 +65,32 @@ import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.ReferenceCache; import org.teavm.model.ValueType; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.model.emit.ValueEmitter; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.optimization.UnreachableBasicBlockEliminator; +import org.teavm.model.util.BasicBlockSplitter; import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.parsing.Parser; -public class DependencyAnalyzer implements DependencyInfo { +public abstract class DependencyAnalyzer implements DependencyInfo { private static final int PROPAGATION_STACK_THRESHOLD = 50; + private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("", void.class); static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); static final boolean shouldTag = System.getProperty("org.teavm.tagDependencies", "false").equals("true") || shouldLog; static final boolean dependencyReport = System.getProperty("org.teavm.dependencyReport", "false").equals("true"); private int classNameSuffix; + private ClassReaderSource unprocessedClassSource; private DependencyClassSource classSource; private ClassLoader classLoader; - private Mapper methodReaderCache; + private Map>> methodReaderCache = new HashMap<>(1000, 0.5f); private Mapper fieldReaderCache; - private CachedMapper methodCache; + private Map> methodCache = new HashMap<>(); + private Set reachedMethods = new LinkedHashSet<>(); + private Set readonlyReachedMethods = Collections.unmodifiableSet(reachedMethods); private CachedMapper fieldCache; private CachedMapper classCache; private List listeners = new ArrayList<>(); @@ -90,23 +110,19 @@ public class DependencyAnalyzer implements DependencyInfo { private boolean completing; private Map superClassFilters = new HashMap<>(); private List allNodes = new ArrayList<>(); + private ClassHierarchy classHierarchy; + IncrementalCache incrementalCache = new IncrementalCache(); boolean asyncSupported; - public DependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, + DependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, Diagnostics diagnostics) { + unprocessedClassSource = classSource; this.diagnostics = diagnostics; - this.classSource = new DependencyClassSource(classSource, diagnostics); + this.classSource = new DependencyClassSource(classSource, diagnostics, incrementalCache); + classHierarchy = new ClassHierarchy(this.classSource); this.classLoader = classLoader; this.services = services; - methodReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutableImplementation(preimage)); fieldReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutable(preimage)); - methodCache = new CachedMapper<>(preimage -> { - MethodHolder method = methodReaderCache.map(preimage); - if (method != null && !method.getReference().equals(preimage)) { - return methodCache.map(method.getReference()); - } - return createMethodDep(preimage, method); - }); fieldCache = new CachedMapper<>(preimage -> { FieldReader field = fieldReaderCache.map(preimage); if (field != null && !field.getReference().equals(preimage)) { @@ -150,10 +166,32 @@ public class DependencyAnalyzer implements DependencyInfo { type = new DependencyType(this, name, types.size()); types.add(type); typeMap.put(name, type); + + if (!name.startsWith("[") && !name.startsWith("~")) { + markSupertypesAsHavingSubtypes(name); + } } return type; } + private void markSupertypesAsHavingSubtypes(String name) { + ClassReader cls = unprocessedClassSource.get(name); + if (cls == null) { + cls = classSource.get(name); + if (cls == null) { + return; + } + } + + if (cls.getParent() != null) { + getType(cls.getParent()).subtypeExists = true; + } + + for (String itf : cls.getInterfaces()) { + getType(itf).subtypeExists = true; + } + } + public DependencyNode createNode() { return createNode(null); } @@ -172,6 +210,15 @@ public class DependencyAnalyzer implements DependencyInfo { return classSource; } + public boolean isSynthesizedClass(String className) { + return classSource.isGeneratedClass(className); + } + + @Override + public ClassHierarchy getClassHierarchy() { + return classHierarchy; + } + @Override public ClassLoader getClassLoader() { return classLoader; @@ -231,8 +278,8 @@ public class DependencyAnalyzer implements DependencyInfo { dep.used = false; lock(dep, false); deferredTasks.add(() -> { - DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyAnalyzer.this); - graphBuilder.buildGraph(dep); + processInvokeDynamic(dep); + processMethod(dep); dep.used = true; }); @@ -240,6 +287,8 @@ public class DependencyAnalyzer implements DependencyInfo { } } + protected abstract void processMethod(MethodDependency methodDep); + public void addDependencyListener(DependencyListener listener) { listeners.add(listener); listener.started(agent); @@ -254,7 +303,7 @@ public class DependencyAnalyzer implements DependencyInfo { if (parameters.length + 1 != argumentTypes.length) { throw new IllegalArgumentException("argumentTypes length does not match the number of method's arguments"); } - MethodDependency method = linkMethod(methodRef, null); + MethodDependency method = linkMethod(methodRef); method.use(); DependencyNode[] varNodes = method.getVariables(); varNodes[0].propagate(getType(methodRef.getClassName())); @@ -344,34 +393,33 @@ public class DependencyAnalyzer implements DependencyInfo { } } - private Set classesAddedByRoot = new HashSet<>(); - public void defer(Runnable task) { deferredTasks.add(task); } - public ClassDependency linkClass(String className, CallLocation callLocation) { + public ClassDependency linkClass(String className) { if (completing && getClass(className) == null) { throw new IllegalStateException("Can't link class during completion phase"); } ClassDependency dep = classCache.map(className); - boolean added = true; - if (callLocation == null || callLocation.getMethod() == null) { - added = classesAddedByRoot.add(className); - } - if (!dep.isMissing() && added) { - deferredTasks.add(() -> { - for (DependencyListener listener : listeners) { - listener.classReached(agent, className, callLocation); - } - }); + if (!dep.activated) { + dep.activated = true; + if (!dep.isMissing()) { + deferredTasks.add(() -> { + for (DependencyListener listener : listeners) { + listener.classReached(agent, className); + } + }); - ClassReader cls = dep.getClassReader(); - if (cls.getParent() != null) { - linkClass(cls.getParent(), callLocation); - } - for (String iface : cls.getInterfaces()) { - linkClass(iface, callLocation); + ClassReader cls = dep.getClassReader(); + if (cls.getParent() != null && !classCache.caches(cls.getParent())) { + linkClass(cls.getParent()); + } + for (String iface : cls.getInterfaces()) { + if (!classCache.caches(iface)) { + linkClass(iface); + } + } } } @@ -381,54 +429,39 @@ public class DependencyAnalyzer implements DependencyInfo { private ClassDependency createClassDependency(String className) { ClassReader cls = classSource.get(className); ClassDependency dependency = new ClassDependency(this, className, cls); - if (!dependency.isMissing()) { - if (cls.getParent() != null) { - linkClass(cls.getParent(), null); - } - for (String ifaceName : cls.getInterfaces()) { - linkClass(ifaceName, null); - } - } return dependency; } - private Set methodsAddedByRoot = new HashSet<>(); + public MethodDependency linkMethod(String className, MethodDescriptor descriptor) { + MethodDependency dep = getMethodDependency(className, descriptor); - public MethodDependency linkMethod(MethodReference methodRef, CallLocation callLocation) { - if (methodRef == null) { - throw new IllegalArgumentException(); - } - MethodReader methodReader = methodReaderCache.map(methodRef); - if (methodReader != null) { - methodRef = methodReader.getReference(); - } - - if (completing && getMethod(methodRef) == null) { - throw new IllegalStateException("Can't submit class during completion phase"); - } - callGraph.getNode(methodRef); - boolean added; - if (callLocation != null && callLocation.getMethod() != null) { - added = callGraph.getNode(callLocation.getMethod()).addCallSite(methodRef, - callLocation.getSourceLocation()); - } else { - added = methodsAddedByRoot.add(methodRef); - } - MethodDependency graph = methodCache.map(methodRef); - if (!graph.isMissing() && added) { - for (DependencyListener listener : listeners) { - listener.methodReached(agent, graph, callLocation); + if (!dep.activated) { + dep.activated = true; + if (!dep.isMissing()) { + for (DependencyListener listener : listeners) { + listener.methodReached(agent, dep); + } + activateDependencyPlugin(dep); } - activateDependencyPlugin(graph, callLocation); } - return graph; + return dep; } - void initClass(ClassDependency cls, CallLocation callLocation) { + public MethodDependency linkMethod(MethodReference method) { + return linkMethod(method.getClassName(), method.getDescriptor()); + } + + void initClass(ClassDependency cls, CallLocation location) { ClassReader reader = cls.getClassReader(); - MethodReader method = reader.getMethod(new MethodDescriptor("", void.class)); + MethodReader method = reader.getMethod(CLINIT_METHOD); if (method != null) { - deferredTasks.add(() -> linkMethod(method.getReference(), callLocation).use()); + deferredTasks.add(() -> { + MethodDependency initMethod = linkMethod(method.getReference()); + if (location != null) { + initMethod.addLocation(location); + } + initMethod.use(); + }); } } @@ -437,55 +470,49 @@ public class DependencyAnalyzer implements DependencyInfo { int paramCount = arguments.length + 1; DependencyNode[] parameterNodes = new DependencyNode[arguments.length + 1]; - parameterNodes[0] = createNode(ValueType.object(methodRef.getClassName())); - parameterNodes[0].method = methodRef; - if (shouldTag) { - parameterNodes[0].setTag(methodRef + ":0"); - } + parameterNodes[0] = createParameterNode(methodRef, ValueType.object(methodRef.getClassName()), 0); for (int i = 0; i < arguments.length; ++i) { - parameterNodes[i + 1] = createNode(arguments[i]); - parameterNodes[i + 1].method = methodRef; - if (shouldTag) { - parameterNodes[i + 1].setTag(methodRef + ":" + (i + 1)); - } + parameterNodes[i + 1] = createParameterNode(methodRef, arguments[i], i + 1); } DependencyNode resultNode; if (methodRef.getDescriptor().getResultType() == ValueType.VOID) { resultNode = null; } else { - resultNode = createNode(methodRef.getDescriptor().getResultType()); - resultNode.method = methodRef; - if (shouldTag) { - resultNode.setTag(methodRef + ":RESULT"); - } - } - DependencyNode thrown = createNode(); - thrown.method = methodRef; - if (shouldTag) { - thrown.setTag(methodRef + ":THROWN"); + resultNode = createResultNode(methodRef); } + DependencyNode thrown = createThrownNode(methodRef); MethodDependency dep = new MethodDependency(this, parameterNodes, paramCount, resultNode, thrown, method, methodRef); if (method != null) { - deferredTasks.add(() -> { - CallLocation caller = new CallLocation(dep.getMethod().getReference()); - linkClass(dep.getMethod().getOwnerName(), caller).initClass(caller); - }); + deferredTasks.add(() -> linkClass(dep.getMethod().getOwnerName()) + .initClass(new CallLocation(dep.getMethod().getReference()))); } return dep; } + abstract DependencyNode createParameterNode(MethodReference method, ValueType type, int index); + + abstract DependencyNode createResultNode(MethodReference method); + + abstract DependencyNode createThrownNode(MethodReference method); + + abstract DependencyNode createFieldNode(FieldReference field, ValueType type); + + abstract DependencyNode createArrayItemNode(DependencyNode parent); + + abstract DependencyNode createClassValueNode(int degree, DependencyNode parent); + void scheduleMethodAnalysis(MethodDependency dep) { deferredTasks.add(() -> { - DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyAnalyzer.this); - graphBuilder.buildGraph(dep); + processInvokeDynamic(dep); + processMethod(dep); }); } @Override public Collection getReachableMethods() { - return methodCache.getCachedPreimages(); + return readonlyReachedMethods; } @Override @@ -500,23 +527,14 @@ public class DependencyAnalyzer implements DependencyInfo { private Set fieldsAddedByRoot = new HashSet<>(); - public FieldDependency linkField(FieldReference fieldRef, CallLocation location) { - if (completing) { - throw new IllegalStateException("Can't submit class during completion phase"); - } - boolean added; - if (location != null) { - added = callGraph.getNode(location.getMethod()).addFieldAccess(fieldRef, location.getSourceLocation()); - } else { - added = fieldsAddedByRoot.add(fieldRef); - } + public FieldDependency linkField(FieldReference fieldRef) { FieldDependency dep = fieldCache.map(fieldRef); - if (!dep.isMissing()) { - deferredTasks.add(() -> linkClass(fieldRef.getClassName(), location).initClass(location)); - } - if (!dep.isMissing() && added) { - for (DependencyListener listener : listeners) { - listener.fieldReached(agent, dep, location); + if (!dep.activated) { + dep.activated = true; + if (!dep.isMissing()) { + for (DependencyListener listener : listeners) { + listener.fieldReached(agent, dep); + } } } return dep; @@ -533,21 +551,14 @@ public class DependencyAnalyzer implements DependencyInfo { } private FieldDependency createFieldNode(FieldReference fieldRef, FieldReader field) { - DependencyNode node = createNode(field != null ? field.getType() : null); - if (shouldTag) { - node.setTag(fieldRef.getClassName() + "#" + fieldRef.getFieldName()); - } - FieldDependency dep = new FieldDependency(node, field, fieldRef); - if (!dep.isMissing()) { - deferredTasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null)); - } - return dep; + DependencyNode node = createFieldNode(fieldRef, field != null ? field.getType() : null); + return new FieldDependency(node, field, fieldRef); } - private void activateDependencyPlugin(MethodDependency methodDep, CallLocation location) { + private void activateDependencyPlugin(MethodDependency methodDep) { attachDependencyPlugin(methodDep); if (methodDep.dependencyPlugin != null) { - methodDep.dependencyPlugin.methodReached(agent, methodDep, location); + methodDep.dependencyPlugin.methodReached(agent, methodDep); } } @@ -588,13 +599,48 @@ public class DependencyAnalyzer implements DependencyInfo { @Override public MethodDependency getMethod(MethodReference methodRef) { - return methodCache.getKnown(methodRef); + return getMethod(methodRef.getClassName(), methodRef.getDescriptor()); + } + + public MethodDependency getMethod(String className, MethodDescriptor descriptor) { + Map map = methodCache.get(className); + if (map == null) { + return null; + } + return map.get(descriptor); } @Override public MethodDependency getMethodImplementation(MethodReference methodRef) { - MethodReader method = methodReaderCache.map(methodRef); - return method != null ? methodCache.getKnown(method.getReference()) : null; + MethodReader method = getMethodHolder(methodRef.getClassName(), methodRef.getDescriptor()); + return method != null ? getMethod(method.getReference()) : null; + } + + private MethodHolder getMethodHolder(String className, MethodDescriptor descriptor) { + return methodReaderCache + .computeIfAbsent(className, k -> new HashMap<>(100, 0.5f)) + .computeIfAbsent(descriptor, k -> Optional.ofNullable( + classSource.resolveMutableImplementation(className, k))) + .orElse(null); + } + + private MethodDependency getMethodDependency(String className, MethodDescriptor descriptor) { + Map map = methodCache.computeIfAbsent(className, k -> new HashMap<>()); + MethodDependency result = map.get(descriptor); + if (result == null) { + MethodHolder method = getMethodHolder(className, descriptor); + if (method != null && !(method.getDescriptor().equals(descriptor) + && method.getOwnerName().equals(className))) { + result = getMethodDependency(method.getOwnerName(), method.getDescriptor()); + } else { + MethodReference reference = method != null + ? method.getReference() + : new MethodReference(className, descriptor); + result = createMethodDep(reference, method); + } + map.put(descriptor, result); + } + return result; } private void processQueue() { @@ -763,6 +809,10 @@ public class DependencyAnalyzer implements DependencyInfo { dependencyPlugins.put(method, dependencyPlugin); } + public IncrementalDependencyProvider getIncrementalDependencies() { + return incrementalCache; + } + DependencyTypeFilter getSuperClassFilter(String superClass) { DependencyTypeFilter result = superClassFilters.get(superClass); if (result == null) { @@ -777,10 +827,148 @@ public class DependencyAnalyzer implements DependencyInfo { result = new ExactTypeFilter(superClass); } } else { - result = new SuperClassFilter(classSource, superClass); + if (superClass.equals("java.lang.Object")) { + result = t -> true; + } else { + result = new SuperClassFilter(this, getType(superClass)); + } } superClassFilters.put(superClass, result); } return result; } + + private void processInvokeDynamic(MethodDependency methodDep) { + if (methodDep.method == null) { + return; + } + + Program program = methodDep.method.getProgram(); + if (program == null) { + return; + } + + CallGraphNode caller = callGraph.getNode(methodDep.getReference()); + + ProgramEmitter pe = ProgramEmitter.create(program, classHierarchy); + boolean hasIndy = false; + BasicBlockSplitter splitter = new BasicBlockSplitter(program); + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + for (Instruction insn : block) { + if (!(insn instanceof InvokeDynamicInstruction)) { + continue; + } + block = insn.getBasicBlock(); + + InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn; + MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(), + indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature()); + BootstrapMethodSubstitutor substitutor = bootstrapMethodSubstitutors.get(bootstrapMethod); + if (substitutor == null) { + NullConstantInstruction nullInsn = new NullConstantInstruction(); + nullInsn.setReceiver(indy.getReceiver()); + nullInsn.setLocation(indy.getLocation()); + insn.replace(nullInsn); + CallLocation location = new CallLocation(methodDep.getReference(), insn.getLocation()); + diagnostics.error(location, "Substitutor for bootstrap method {{m0}} was not found", + bootstrapMethod); + continue; + } + + hasIndy = true; + BasicBlock splitBlock = splitter.split(block, insn); + + pe.enter(block); + pe.setCurrentLocation(indy.getLocation()); + insn.delete(); + + List arguments = new ArrayList<>(); + for (int k = 0; k < indy.getArguments().size(); ++k) { + arguments.add(pe.var(indy.getArguments().get(k), indy.getMethod().parameterType(k))); + } + DynamicCallSite callSite = new DynamicCallSite( + methodDep.getReference(), indy.getMethod(), + indy.getInstance() != null ? pe.var(indy.getInstance(), + ValueType.object(methodDep.getMethod().getOwnerName())) : null, + arguments, indy.getBootstrapMethod(), indy.getBootstrapArguments(), + agent); + ValueEmitter result = substitutor.substitute(callSite, pe); + if (result.getVariable() != null && result.getVariable() != indy.getReceiver()) { + AssignInstruction assign = new AssignInstruction(); + assign.setAssignee(result.getVariable()); + assign.setReceiver(indy.getReceiver()); + pe.addInstruction(assign); + } + pe.jump(splitBlock); + block = splitBlock; + } + } + splitter.fixProgram(); + } + + class IncrementalCache implements IncrementalDependencyProvider, IncrementalDependencyRegistration { + private final String[] emptyArray = new String[0]; + private Map classes = new HashMap<>(); + private Map methods = new HashMap<>(); + + @Override + public boolean isNoCache(String className) { + IncrementalItem item = classes.get(className); + return item != null && item.noCache; + } + + @Override + public boolean isNoCache(MethodReference method) { + IncrementalItem item = methods.get(method); + return item != null && item.noCache; + } + + @Override + public String[] getDependencies(String className) { + IncrementalItem item = classes.get(className); + return item != null && item.dependencies != null ? item.dependencies.toArray(new String[0]) : emptyArray; + } + + @Override + public String[] getDependencies(MethodReference method) { + IncrementalItem item = methods.get(method); + return item != null && item.dependencies != null ? item.dependencies.toArray(new String[0]) : emptyArray; + } + + @Override + public void setNoCache(String className) { + classes.computeIfAbsent(className, k -> new IncrementalItem()).noCache = true; + } + + @Override + public void setNoCache(MethodReference method) { + methods.computeIfAbsent(method, k -> new IncrementalItem()).noCache = true; + } + + @Override + public void addDependencies(String className, String... dependencies) { + IncrementalItem item = classes.computeIfAbsent(className, k -> new IncrementalItem()); + if (item.dependencies == null) { + item.dependencies = new LinkedHashSet<>(); + } + item.dependencies.addAll(Arrays.asList(dependencies)); + } + + @Override + public void addDependencies(MethodReference method, String... dependencies) { + IncrementalItem item = this.methods.computeIfAbsent(method, k -> new IncrementalItem()); + if (item.dependencies == null) { + item.dependencies = new LinkedHashSet<>(); + } + item.dependencies.addAll(Arrays.asList(dependencies)); + } + } + + static class IncrementalItem { + boolean noCache; + Set dependencies; + } + + abstract boolean domainOptimizationEnabled(); } \ No newline at end of file diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java new file mode 100644 index 000000000..1136724bf --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.dependency; + +import org.teavm.common.ServiceRepository; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.ClassReaderSource; + +public interface DependencyAnalyzerFactory { + DependencyAnalyzer create(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, + Diagnostics diagnostics); +} diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java index 1c4bca016..1dbd9f22c 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -20,10 +20,14 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodHolder; @@ -32,19 +36,24 @@ import org.teavm.model.util.ModelUtils; class DependencyClassSource implements ClassHolderSource { private ClassReaderSource innerSource; + private ClassHierarchy innerHierarchy; private Diagnostics diagnostics; + private IncrementalDependencyRegistration dependencyRegistration; private Map generatedClasses = new LinkedHashMap<>(); private List transformers = new ArrayList<>(); - private Map cache = new LinkedHashMap<>(); + private Map> cache = new LinkedHashMap<>(1000, 0.5f); - DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics) { + DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics, + IncrementalDependencyRegistration dependencyRegistration) { this.innerSource = innerSource; this.diagnostics = diagnostics; + innerHierarchy = new ClassHierarchy(innerSource); + this.dependencyRegistration = dependencyRegistration; } @Override public ClassHolder get(String name) { - return cache.computeIfAbsent(name, this::findAndTransformClass); + return cache.computeIfAbsent(name, n -> Optional.ofNullable(findAndTransformClass(n))).orElse(null); } public void submit(ClassHolder cls) { @@ -53,7 +62,7 @@ class DependencyClassSource implements ClassHolderSource { } if (!transformers.isEmpty()) { for (ClassHolderTransformer transformer : transformers) { - transformer.transformClass(cls, innerSource, diagnostics); + transformer.transformClass(cls, transformContext); } cls = ModelUtils.copyClass(cls); } @@ -70,7 +79,7 @@ class DependencyClassSource implements ClassHolderSource { ClassHolder cls = findClass(name); if (cls != null && !transformers.isEmpty()) { for (ClassHolderTransformer transformer : transformers) { - transformer.transformClass(cls, innerSource, diagnostics); + transformer.transformClass(cls, transformContext); } } return cls; @@ -88,7 +97,28 @@ class DependencyClassSource implements ClassHolderSource { return generatedClasses.values(); } + public boolean isGeneratedClass(String className) { + return generatedClasses.containsKey(className); + } + public void addTransformer(ClassHolderTransformer transformer) { transformers.add(transformer); } + + final ClassHolderTransformerContext transformContext = new ClassHolderTransformerContext() { + @Override + public ClassHierarchy getHierarchy() { + return innerHierarchy; + } + + @Override + public Diagnostics getDiagnostics() { + return diagnostics; + } + + @Override + public IncrementalDependencyRegistration getIncrementalCache() { + return dependencyRegistration; + } + }; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index fd98f4200..b2487b93c 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -15,46 +15,30 @@ */ package org.teavm.dependency; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_SYNC_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_SYNC_METHOD; import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import org.teavm.cache.NoCache; import org.teavm.callgraph.DefaultCallGraphNode; -import org.teavm.model.AnnotationHolder; -import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlockReader; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; -import org.teavm.model.FieldReference; import org.teavm.model.IncomingReader; -import org.teavm.model.Instruction; -import org.teavm.model.InvokeDynamicInstruction; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodHandle; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReference; import org.teavm.model.PhiReader; import org.teavm.model.Program; -import org.teavm.model.RuntimeConstant; -import org.teavm.model.TextLocation; import org.teavm.model.TryCatchBlockReader; import org.teavm.model.ValueType; import org.teavm.model.VariableReader; -import org.teavm.model.emit.ProgramEmitter; -import org.teavm.model.emit.ValueEmitter; -import org.teavm.model.instructions.AbstractInstructionReader; import org.teavm.model.instructions.ArrayElementType; -import org.teavm.model.instructions.AssignInstruction; -import org.teavm.model.instructions.InstructionReader; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.text.ListingBuilder; -import org.teavm.model.util.BasicBlockSplitter; class DependencyGraphBuilder { private DependencyAnalyzer dependencyAnalyzer; @@ -62,7 +46,6 @@ class DependencyGraphBuilder { private DependencyNode resultNode; private Program program; private DefaultCallGraphNode caller; - private TextLocation currentLocation; private ExceptionConsumer currentExceptionConsumer; DependencyGraphBuilder(DependencyAnalyzer dependencyAnalyzer) { @@ -78,8 +61,6 @@ class DependencyGraphBuilder { program = method.getProgram(); resultNode = dep.getResult(); - processInvokeDynamic(dep); - DataFlowGraphBuilder dfgBuilder = new DataFlowGraphBuilder(); boolean[] significantParams = new boolean[dep.getParameterCount()]; significantParams[0] = true; @@ -122,6 +103,7 @@ class DependencyGraphBuilder { } dep.setVariables(nodes); + reader.setCaller(caller.getMethod()); for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlockReader block = program.basicBlockAt(i); currentExceptionConsumer = createExceptionConsumer(dep, block); @@ -139,7 +121,7 @@ class DependencyGraphBuilder { for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { if (tryCatch.getExceptionType() != null) { - dependencyAnalyzer.linkClass(tryCatch.getExceptionType(), new CallLocation(caller.getMethod())); + dependencyAnalyzer.linkClass(tryCatch.getExceptionType()); } } } @@ -149,26 +131,22 @@ class DependencyGraphBuilder { MethodDependency methodDep; if (dependencyAnalyzer.asyncSupported) { - methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorEnter", Object.class, void.class), null); + methodDep = dependencyAnalyzer.linkMethod(MONITOR_ENTER_METHOD); syncNodes.add(methodDep.getVariable(1)); methodDep.use(); } - methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorEnterSync", Object.class, void.class), null); + methodDep = dependencyAnalyzer.linkMethod(MONITOR_ENTER_SYNC_METHOD); syncNodes.add(methodDep.getVariable(1)); methodDep.use(); if (dependencyAnalyzer.asyncSupported) { - methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorExit", Object.class, void.class), null); + methodDep = dependencyAnalyzer.linkMethod(MONITOR_EXIT_METHOD); syncNodes.add(methodDep.getVariable(1)); methodDep.use(); } - methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorExitSync", Object.class, void.class), null); + methodDep = dependencyAnalyzer.linkMethod(MONITOR_EXIT_SYNC_METHOD); syncNodes.add(methodDep.getVariable(1)); methodDep.use(); @@ -184,72 +162,6 @@ class DependencyGraphBuilder { } } - private void processInvokeDynamic(MethodDependency methodDep) { - if (program == null) { - return; - } - ProgramEmitter pe = ProgramEmitter.create(program, dependencyAnalyzer.getClassSource()); - boolean hasIndy = false; - BasicBlockSplitter splitter = new BasicBlockSplitter(program); - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - for (Instruction insn : block) { - if (!(insn instanceof InvokeDynamicInstruction)) { - continue; - } - block = insn.getBasicBlock(); - - InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn; - MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(), - indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature()); - BootstrapMethodSubstitutor substitutor = dependencyAnalyzer.bootstrapMethodSubstitutors - .get(bootstrapMethod); - if (substitutor == null) { - NullConstantInstruction nullInsn = new NullConstantInstruction(); - nullInsn.setReceiver(indy.getReceiver()); - nullInsn.setLocation(indy.getLocation()); - insn.replace(nullInsn); - CallLocation location = new CallLocation(caller.getMethod(), currentLocation); - dependencyAnalyzer.getDiagnostics().error(location, "Substitutor for bootstrap " - + "method {{m0}} was not found", bootstrapMethod); - continue; - } - - hasIndy = true; - BasicBlock splitBlock = splitter.split(block, insn); - - pe.enter(block); - pe.setCurrentLocation(indy.getLocation()); - insn.delete(); - - List arguments = new ArrayList<>(); - for (int k = 0; k < indy.getArguments().size(); ++k) { - arguments.add(pe.var(indy.getArguments().get(k), indy.getMethod().parameterType(k))); - } - DynamicCallSite callSite = new DynamicCallSite( - methodDep.getReference(), indy.getMethod(), - indy.getInstance() != null ? pe.var(indy.getInstance(), - ValueType.object(methodDep.getMethod().getOwnerName())) : null, - arguments, indy.getBootstrapMethod(), indy.getBootstrapArguments(), - dependencyAnalyzer.getAgent()); - ValueEmitter result = substitutor.substitute(callSite, pe); - if (result.getVariable() != null && result.getVariable() != indy.getReceiver()) { - AssignInstruction assign = new AssignInstruction(); - assign.setAssignee(result.getVariable()); - assign.setReceiver(indy.getReceiver()); - pe.addInstruction(assign); - } - pe.jump(splitBlock); - block = splitBlock; - } - } - splitter.fixProgram(); - - if (hasIndy && methodDep.method.getAnnotations().get(NoCache.class.getName()) == null) { - methodDep.method.getAnnotations().add(new AnnotationHolder(NoCache.class.getName())); - } - } - private ExceptionConsumer createExceptionConsumer(MethodDependency methodDep, BasicBlockReader block) { List tryCatchBlocks = block.readTryCatchBlocks(); ClassReader[] exceptions = new ClassReader[tryCatchBlocks.size()]; @@ -282,10 +194,9 @@ class DependencyGraphBuilder { @Override public void consume(DependencyType type) { - ClassReaderSource classSource = analyzer.getClassSource(); + ClassHierarchy hierarchy = analyzer.getClassHierarchy(); for (int i = 0; i < exceptions.length; ++i) { - if (exceptions[i] == null || classSource.isSuperType(exceptions[i].getName(), type.getName()) - .orElse(false)) { + if (exceptions[i] == null || hierarchy.isSuperType(exceptions[i].getName(), type.getName(), false)) { if (vars[i] != null) { vars[i].propagate(type); } @@ -296,123 +207,7 @@ class DependencyGraphBuilder { } } - static class VirtualCallConsumer implements DependencyConsumer { - private final DependencyNode node; - private final MethodDescriptor methodDesc; - private final DependencyAnalyzer analyzer; - private final DependencyNode[] parameters; - private final DependencyNode result; - private final DefaultCallGraphNode caller; - private final TextLocation location; - private final Set knownMethods = new HashSet<>(); - private final BitSet knownTypes = new BitSet(); - private ExceptionConsumer exceptionConsumer; - private DependencyTypeFilter filter; - - VirtualCallConsumer(DependencyNode node, String filterClass, - MethodDescriptor methodDesc, DependencyAnalyzer analyzer, DependencyNode[] parameters, - DependencyNode result, DefaultCallGraphNode caller, TextLocation location, - ExceptionConsumer exceptionConsumer) { - this.node = node; - this.filter = analyzer.getSuperClassFilter(filterClass); - this.methodDesc = methodDesc; - this.analyzer = analyzer; - this.parameters = parameters; - this.result = result; - this.caller = caller; - this.location = location; - this.exceptionConsumer = exceptionConsumer; - } - - @Override - public void consume(DependencyType type) { - if (knownTypes.get(type.index)) { - return; - } - knownTypes.set(type.index); - - String className = type.getName(); - if (DependencyAnalyzer.shouldLog) { - System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " - + "Target class is " + className); - } - if (className.startsWith("[")) { - className = "java.lang.Object"; - type = analyzer.getType(className); - } - - if (!filter.match(type)) { - return; - } - MethodReference methodRef = new MethodReference(className, methodDesc); - MethodDependency methodDep = analyzer.linkMethod(methodRef, new CallLocation(caller.getMethod(), location)); - if (!methodDep.isMissing() && knownMethods.add(methodRef)) { - methodDep.use(); - DependencyNode[] targetParams = methodDep.getVariables(); - if (parameters[0] != null && targetParams[0] != null) { - parameters[0].connect(targetParams[0], - analyzer.getSuperClassFilter(methodDep.getMethod().getOwnerName())); - } - for (int i = 1; i < parameters.length; ++i) { - if (parameters[i] != null && targetParams[i] != null) { - parameters[i].connect(targetParams[i]); - } - } - if (result != null && methodDep.getResult() != null) { - methodDep.getResult().connect(result); - } - methodDep.getThrown().addConsumer(exceptionConsumer); - } - } - } - - private InstructionReader reader = new AbstractInstructionReader() { - @Override - public void location(TextLocation location) { - currentLocation = location; - } - - @Override - public void classConstant(VariableReader receiver, ValueType cst) { - DependencyNode node = nodes[receiver.getIndex()]; - if (node != null) { - node.propagate(dependencyAnalyzer.getType("java.lang.Class")); - if (!(cst instanceof ValueType.Primitive)) { - StringBuilder sb = new StringBuilder(); - while (cst instanceof ValueType.Array) { - cst = ((ValueType.Array) cst).getItemType(); - sb.append('['); - } - if (cst instanceof ValueType.Object) { - sb.append(((ValueType.Object) cst).getClassName()); - } else { - sb.append(cst.toString()); - } - node.getClassValueNode().propagate(dependencyAnalyzer.getType(sb.toString())); - } else { - node.getClassValueNode().propagate(dependencyAnalyzer.getType("~" + cst.toString())); - } - } - while (cst instanceof ValueType.Array) { - cst = ((ValueType.Array) cst).getItemType(); - } - if (cst instanceof ValueType.Object) { - final String className = ((ValueType.Object) cst).getClassName(); - dependencyAnalyzer.linkClass(className, new CallLocation(caller.getMethod(), currentLocation)); - } - } - - @Override - public void stringConstant(VariableReader receiver, String cst) { - DependencyNode node = nodes[receiver.getIndex()]; - if (node != null) { - node.propagate(dependencyAnalyzer.getType("java.lang.String")); - } - MethodDependency method = dependencyAnalyzer.linkMethod(new MethodReference(String.class, - "", char[].class, void.class), new CallLocation(caller.getMethod(), currentLocation)); - method.use(); - } - + private AbstractInstructionAnalyzer reader = new AbstractInstructionAnalyzer() { @Override public void assign(VariableReader receiver, VariableReader assignee) { DependencyNode valueNode = nodes[assignee.getIndex()]; @@ -472,106 +267,6 @@ class DependencyGraphBuilder { nodes[exception.getIndex()].addConsumer(currentExceptionConsumer); } - @Override - public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { - DependencyNode node = nodes[receiver.getIndex()]; - if (node != null) { - node.propagate(dependencyAnalyzer.getType("[" + itemType)); - } - String className = extractClassName(itemType); - if (className != null) { - dependencyAnalyzer.linkClass(className, new CallLocation(caller.getMethod(), currentLocation)); - } - } - - private String extractClassName(ValueType itemType) { - while (itemType instanceof ValueType.Array) { - itemType = ((ValueType.Array) itemType).getItemType(); - } - return itemType instanceof ValueType.Object ? ((ValueType.Object) itemType).getClassName() : null; - } - - @Override - public void createArray(VariableReader receiver, ValueType itemType, - List dimensions) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < dimensions.size(); ++i) { - sb.append('['); - itemType = ((ValueType.Array) itemType).getItemType(); - } - String itemTypeStr; - if (itemType instanceof ValueType.Object) { - itemTypeStr = ((ValueType.Object) itemType).getClassName(); - } else { - itemTypeStr = itemType.toString(); - } - sb.append(itemTypeStr); - DependencyNode node = nodes[receiver.getIndex()]; - for (int i = 0; i < dimensions.size(); ++i) { - if (node == null) { - break; - } - node.propagate(dependencyAnalyzer.getType(sb.substring(i, sb.length()))); - node = node.getArrayItem(); - } - String className = extractClassName(itemType); - if (className != null) { - dependencyAnalyzer.linkClass(className, new CallLocation(caller.getMethod(), currentLocation)); - } - } - - @Override - public void create(VariableReader receiver, String type) { - dependencyAnalyzer.linkClass(type, new CallLocation(caller.getMethod(), currentLocation)); - DependencyNode node = nodes[receiver.getIndex()]; - if (node != null) { - node.propagate(dependencyAnalyzer.getType(type)); - } - } - - @Override - public void getField(VariableReader receiver, VariableReader instance, FieldReference field, - ValueType fieldType) { - FieldDependency fieldDep = dependencyAnalyzer.linkField(field, - new CallLocation(caller.getMethod(), currentLocation)); - if (!(fieldType instanceof ValueType.Primitive)) { - DependencyNode receiverNode = nodes[receiver.getIndex()]; - if (receiverNode != null) { - fieldDep.getValue().connect(receiverNode); - } - } - initClass(field.getClassName()); - } - - @Override - public void putField(VariableReader instance, FieldReference field, VariableReader value, - ValueType fieldType) { - FieldDependency fieldDep = dependencyAnalyzer.linkField(field, - new CallLocation(caller.getMethod(), currentLocation)); - if (!(fieldType instanceof ValueType.Primitive)) { - DependencyNode valueNode = nodes[value.getIndex()]; - if (valueNode != null) { - valueNode.connect(fieldDep.getValue()); - } - } - initClass(field.getClassName()); - } - - @Override - public void cloneArray(VariableReader receiver, VariableReader array) { - DependencyNode arrayNode = nodes[array.getIndex()]; - final DependencyNode receiverNode = nodes[receiver.getIndex()]; - if (arrayNode != null && receiverNode != null) { - arrayNode.addConsumer(receiverNode::propagate); - arrayNode.getArrayItem().connect(receiverNode.getArrayItem()); - } - MethodDependency cloneDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "clone", Object.class), - new CallLocation(caller.getMethod(), currentLocation)); - arrayNode.connect(cloneDep.getVariable(0)); - cloneDep.use(); - } - @Override public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { DependencyNode arrayNode = nodes[array.getIndex()]; @@ -581,6 +276,20 @@ class DependencyGraphBuilder { } } + @Override + public void cloneArray(VariableReader receiver, VariableReader array) { + DependencyNode arrayNode = getNode(array); + DependencyNode receiverNode = getNode(receiver); + if (arrayNode != null && receiverNode != null) { + arrayNode.addConsumer(receiverNode::propagate); + arrayNode.getArrayItem().connect(receiverNode.getArrayItem()); + } + MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD); + cloneDep.addLocation(getCallLocation()); + arrayNode.connect(cloneDep.getVariable(0)); + cloneDep.use(); + } + @Override public void getElement(VariableReader receiver, VariableReader array, VariableReader index, ArrayElementType type) { @@ -612,31 +321,16 @@ class DependencyGraphBuilder { } @Override - public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, - List arguments, InvocationType type) { - if (instance == null) { - invokeSpecial(receiver, null, method, arguments); - } else { - switch (type) { - case SPECIAL: - invokeSpecial(receiver, instance, method, arguments); - break; - case VIRTUAL: - invokeVirtual(receiver, instance, method, arguments); - break; - } - if (method.getName().equals("getClass") && method.parameterCount() == 0 - && method.getReturnType().isObject(Class.class) && receiver != null) { - nodes[instance.getIndex()].connect(nodes[receiver.getIndex()].getClassValueNode()); - } - } - } - - private void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, + protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, List arguments) { - CallLocation callLocation = new CallLocation(caller.getMethod(), currentLocation); - dependencyAnalyzer.linkClass(method.getClassName(), callLocation).initClass(callLocation); - MethodDependency methodDep = dependencyAnalyzer.linkMethod(method, callLocation); + CallLocation callLocation = getCallLocation(); + if (instance == null) { + dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation); + } else { + dependencyAnalyzer.linkClass(method.getClassName()); + } + MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); + methodDep.addLocation(callLocation); methodDep.use(); if (methodDep.isMissing()) { return; @@ -662,86 +356,39 @@ class DependencyGraphBuilder { initClass(method.getClassName()); } - private void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, + @Override + protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, List arguments) { DependencyNode[] actualArgs = new DependencyNode[arguments.size() + 1]; for (int i = 0; i < arguments.size(); ++i) { actualArgs[i + 1] = nodes[arguments.get(i).getIndex()]; } - actualArgs[0] = nodes[instance.getIndex()]; - DependencyConsumer listener = new VirtualCallConsumer(nodes[instance.getIndex()], + actualArgs[0] = getNode(instance); + DependencyConsumer listener = new VirtualCallConsumer(getNode(instance), method.getClassName(), method.getDescriptor(), dependencyAnalyzer, actualArgs, - receiver != null ? nodes[receiver.getIndex()] : null, caller, currentLocation, + receiver != null ? getNode(receiver) : null, getCallLocation(), currentExceptionConsumer); - nodes[instance.getIndex()].addConsumer(listener); + getNode(instance).addConsumer(listener); dependencyAnalyzer.getClassSource().overriddenMethods(method).forEach(methodImpl -> { - CallLocation callLocation = new CallLocation(caller.getMethod(), currentLocation); - dependencyAnalyzer.linkMethod(methodImpl.getReference(), callLocation); + dependencyAnalyzer.linkMethod(methodImpl.getReference()).addLocation(getCallLocation()); }); } - @Override - public void isInstance(VariableReader receiver, VariableReader value, final ValueType type) { - String className = extractClassName(type); - if (className != null) { - dependencyAnalyzer.linkClass(className, new CallLocation(caller.getMethod(), currentLocation)); - } - } - - @Override - public void invokeDynamic(VariableReader receiver, VariableReader instance, MethodDescriptor method, - List arguments, MethodHandle bootstrapMethod, - List bootstrapArguments) { - // Should be eliminated by processInvokeDynamic method - } - - @Override - public void initClass(final String className) { - CallLocation callLocation = new CallLocation(caller.getMethod(), currentLocation); - dependencyAnalyzer.linkClass(className, callLocation).initClass(callLocation); - } - @Override public void nullCheck(VariableReader receiver, VariableReader value) { - DependencyNode valueNode = nodes[value.getIndex()]; - DependencyNode receiverNode = nodes[receiver.getIndex()]; - if (valueNode != null) { - valueNode.connect(receiverNode); - } - dependencyAnalyzer.linkMethod(new MethodReference(NullPointerException.class, "", void.class), - new CallLocation(caller.getMethod(), currentLocation)).use(); + super.nullCheck(receiver, value); currentExceptionConsumer.consume(dependencyAnalyzer.getType("java.lang.NullPointerException")); } @Override - public void monitorEnter(VariableReader objectRef) { - if (dependencyAnalyzer.asyncSupported) { - MethodDependency methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorEnter", Object.class, void.class), null); - nodes[objectRef.getIndex()].connect(methodDep.getVariable(1)); - methodDep.use(); - } - - MethodDependency methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorEnterSync", Object.class, void.class), null); - nodes[objectRef.getIndex()].connect(methodDep.getVariable(1)); - methodDep.use(); + protected DependencyNode getNode(VariableReader variable) { + return nodes[variable.getIndex()]; } @Override - public void monitorExit(VariableReader objectRef) { - if (dependencyAnalyzer.asyncSupported) { - MethodDependency methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorExit", Object.class, void.class), null); - nodes[objectRef.getIndex()].connect(methodDep.getVariable(1)); - methodDep.use(); - } - - MethodDependency methodDep = dependencyAnalyzer.linkMethod( - new MethodReference(Object.class, "monitorExitSync", Object.class, void.class), null); - nodes[objectRef.getIndex()].connect(methodDep.getVariable(1)); - methodDep.use(); + protected DependencyAnalyzer getAnalyzer() { + return dependencyAnalyzer; } }; } diff --git a/core/src/main/java/org/teavm/dependency/DependencyInfo.java b/core/src/main/java/org/teavm/dependency/DependencyInfo.java index a4f41dfb0..e63c6b88b 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyInfo.java +++ b/core/src/main/java/org/teavm/dependency/DependencyInfo.java @@ -17,6 +17,7 @@ package org.teavm.dependency; import java.util.Collection; import org.teavm.callgraph.CallGraph; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -26,6 +27,8 @@ public interface DependencyInfo { ClassLoader getClassLoader(); + ClassHierarchy getClassHierarchy(); + Collection getReachableMethods(); Collection getReachableFields(); diff --git a/core/src/main/java/org/teavm/dependency/DependencyListener.java b/core/src/main/java/org/teavm/dependency/DependencyListener.java index fd7ecfa9a..99b206eda 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyListener.java +++ b/core/src/main/java/org/teavm/dependency/DependencyListener.java @@ -15,16 +15,14 @@ */ package org.teavm.dependency; -import org.teavm.model.CallLocation; - public interface DependencyListener { void started(DependencyAgent agent); - void classReached(DependencyAgent agent, String className, CallLocation location); + void classReached(DependencyAgent agent, String className); - void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location); + void methodReached(DependencyAgent agent, MethodDependency method); - void fieldReached(DependencyAgent agent, FieldDependency field, CallLocation location); + void fieldReached(DependencyAgent agent, FieldDependency field); void completing(DependencyAgent agent); } diff --git a/core/src/main/java/org/teavm/dependency/DependencyNode.java b/core/src/main/java/org/teavm/dependency/DependencyNode.java index 5ee8c062e..066ffc7db 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyNode.java +++ b/core/src/main/java/org/teavm/dependency/DependencyNode.java @@ -22,6 +22,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; @@ -40,10 +41,10 @@ public class DependencyNode implements ValueDependencyInfo { ObjectArrayList transitionList; String tag; private DependencyNode arrayItemNode; - private DependencyNode classValueNode; + DependencyNode classValueNode; DependencyNode classNodeParent; boolean classNodeComplete; - private int degree; + int degree; boolean locked; MethodReference method; ValueType typeFilter; @@ -78,15 +79,14 @@ public class DependencyNode implements ValueDependencyInfo { } } - ObjectArrayList transitions = new ObjectArrayList<>(typeSet.getTransitions()); + Transition[] transitions = typeSet.getTransitions().toArray(Transition.class); List consumerEntries = typeSet.getConsumers(); if (action != null) { action.run(); } - for (ObjectCursor cursor : transitions) { - Transition transition = cursor.value; + for (Transition transition : transitions) { if (transition.source.filter(type) && transition.filterType(type)) { dependencyAnalyzer.schedulePropagation(transition, type); } @@ -233,17 +233,24 @@ public class DependencyNode implements ValueDependencyInfo { return true; } - if (cachedTypeFilter == null) { - String superClass; - if (typeFilter instanceof ValueType.Object) { - superClass = ((ValueType.Object) typeFilter).getClassName(); - } else { - superClass = "java.lang.Object"; - } - cachedTypeFilter = dependencyAnalyzer.getSuperClassFilter(superClass); - } + return getFilter().match(type); + } - return cachedTypeFilter.match(type); + DependencyTypeFilter getFilter() { + if (cachedTypeFilter == null) { + if (typeFilter == null) { + cachedTypeFilter = t -> true; + } else { + String superClass; + if (typeFilter instanceof ValueType.Object) { + superClass = ((ValueType.Object) typeFilter).getClassName(); + } else { + superClass = typeFilter.toString(); + } + cachedTypeFilter = dependencyAnalyzer.getSuperClassFilter(superClass); + } + } + return cachedTypeFilter; } public void addConsumer(DependencyConsumer consumer) { @@ -262,8 +269,18 @@ public class DependencyNode implements ValueDependencyInfo { } public void connect(DependencyNode node, DependencyTypeFilter filter) { + if (connectWithoutChildNodes(node, filter)) { + connectArrayItemNodes(node); + + if (classValueNode != null && classValueNode != this) { + classValueNode.connect(node.getClassValueNode()); + } + } + } + + boolean connectWithoutChildNodes(DependencyNode node, DependencyTypeFilter filter) { if (this == node) { - return; + return false; } if (node == null) { throw new IllegalArgumentException("Node must not be null"); @@ -273,7 +290,7 @@ public class DependencyNode implements ValueDependencyInfo { transitionList = new ObjectArrayList<>(); } if (transitions.containsKey(node)) { - return; + return false; } Transition transition = new Transition(this, node, filter); @@ -285,7 +302,7 @@ public class DependencyNode implements ValueDependencyInfo { if (typeSet != null) { if (typeSet == node.typeSet) { - return; + return false; } if (typeSet.transitions != null) { typeSet.transitions.add(transition); @@ -303,14 +320,13 @@ public class DependencyNode implements ValueDependencyInfo { } } - connectArrayItemNodes(node); - - if (classValueNode != null && classValueNode != this) { - classValueNode.connect(node.getClassValueNode()); - } + return true; } private void connectArrayItemNodes(DependencyNode node) { + if (degree > DEGREE_THRESHOLD || node.degree > DEGREE_THRESHOLD) { + return; + } if (!isArray(typeFilter) || !isArray(node.typeFilter)) { return; } @@ -382,15 +398,7 @@ public class DependencyNode implements ValueDependencyInfo { @Override public DependencyNode getArrayItem() { if (arrayItemNode == null) { - ValueType itemTypeFilter = typeFilter instanceof ValueType.Array - ? ((ValueType.Array) typeFilter).getItemType() - : null; - arrayItemNode = dependencyAnalyzer.createNode(itemTypeFilter); - arrayItemNode.degree = degree + 1; - arrayItemNode.method = method; - if (DependencyAnalyzer.shouldTag) { - arrayItemNode.tag = tag + "["; - } + arrayItemNode = dependencyAnalyzer.createArrayItemNode(this); } return arrayItemNode; } @@ -398,13 +406,7 @@ public class DependencyNode implements ValueDependencyInfo { @Override public DependencyNode getClassValueNode() { if (classValueNode == null) { - classValueNode = dependencyAnalyzer.createNode(); - classValueNode.degree = degree; - classValueNode.classValueNode = classValueNode; - classValueNode.classNodeParent = this; - if (DependencyAnalyzer.shouldTag) { - classValueNode.tag = tag + "@"; - } + classValueNode = dependencyAnalyzer.createClassValueNode(degree, this); } return classValueNode; } @@ -507,6 +509,10 @@ public class DependencyNode implements ValueDependencyInfo { } Collection findDomain() { + if (!dependencyAnalyzer.domainOptimizationEnabled()) { + return Collections.singleton(this); + } + Set visited = new LinkedHashSet<>(50); Deque stack = new ArrayDeque<>(50); stack.push(this); diff --git a/core/src/main/java/org/teavm/dependency/DependencyPlugin.java b/core/src/main/java/org/teavm/dependency/DependencyPlugin.java index 079bb3189..2511a2fb0 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyPlugin.java +++ b/core/src/main/java/org/teavm/dependency/DependencyPlugin.java @@ -15,8 +15,6 @@ */ package org.teavm.dependency; -import org.teavm.model.CallLocation; - public interface DependencyPlugin { - void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location); + void methodReached(DependencyAgent agent, MethodDependency method); } diff --git a/core/src/main/java/org/teavm/dependency/DependencyType.java b/core/src/main/java/org/teavm/dependency/DependencyType.java index 56281283f..9422f2c09 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyType.java +++ b/core/src/main/java/org/teavm/dependency/DependencyType.java @@ -19,6 +19,7 @@ public class DependencyType { private DependencyAnalyzer dependencyAnalyzer; private String name; int index; + boolean subtypeExists; DependencyType(DependencyAnalyzer dependencyAnalyzer, String name, int index) { this.dependencyAnalyzer = dependencyAnalyzer; diff --git a/core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java b/core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java index 4bc7fc944..b8ecfd2ef 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java +++ b/core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java @@ -15,6 +15,12 @@ */ package org.teavm.dependency; +import java.util.BitSet; + public interface DependencyTypeFilter { boolean match(DependencyType type); + + default int[] tryExtract(BitSet types) { + return null; + } } diff --git a/core/src/main/java/org/teavm/dependency/ExactTypeFilter.java b/core/src/main/java/org/teavm/dependency/ExactTypeFilter.java index 50ba55da2..33f2d664e 100644 --- a/core/src/main/java/org/teavm/dependency/ExactTypeFilter.java +++ b/core/src/main/java/org/teavm/dependency/ExactTypeFilter.java @@ -17,6 +17,7 @@ package org.teavm.dependency; class ExactTypeFilter implements DependencyTypeFilter { String typeName; + int cache = -1; ExactTypeFilter(String typeName) { this.typeName = typeName; @@ -24,6 +25,13 @@ class ExactTypeFilter implements DependencyTypeFilter { @Override public boolean match(DependencyType type) { - return typeName.equals(type.getName()); + if (cache >= 0) { + return type.index == cache; + } + boolean result = typeName.equals(type.getName()); + if (result) { + cache = type.index; + } + return result; } } diff --git a/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java new file mode 100644 index 000000000..33b77e0dd --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java @@ -0,0 +1,191 @@ +/* + * Copyright 2018 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.dependency; + +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_SYNC_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_METHOD; +import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_SYNC_METHOD; +import java.util.HashMap; +import java.util.Map; +import org.teavm.common.ServiceRepository; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; +import org.teavm.model.TryCatchBlockReader; +import org.teavm.model.ValueType; + +public class FastDependencyAnalyzer extends DependencyAnalyzer { + DependencyNode instancesNode; + DependencyNode classesNode; + private Map virtualCallConsumers = new HashMap<>(); + private Map subtypeNodes = new HashMap<>(); + + public FastDependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, + ServiceRepository services, Diagnostics diagnostics) { + super(classSource, classLoader, services, diagnostics); + + instancesNode = new DependencyNode(this, null); + classesNode = new DependencyNode(this, null); + + instancesNode.addConsumer(type -> { + getSubtypeNode(type.getName()).propagate(type); + }); + } + + @Override + protected void processMethod(MethodDependency methodDep) { + MethodReader method = methodDep.getMethod(); + ProgramReader program = method.getProgram(); + + if (program != null) { + FastInstructionAnalyzer instructionAnalyzer = new FastInstructionAnalyzer(this); + instructionAnalyzer.setCaller(method.getReference()); + for (BasicBlockReader block : program.getBasicBlocks()) { + block.readAllInstructions(instructionAnalyzer); + + for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { + if (tryCatch.getExceptionType() != null) { + linkClass(tryCatch.getExceptionType()); + } + } + } + + methodDep.variableNodes = new DependencyNode[program.variableCount()]; + for (int i = 0; i < methodDep.variableNodes.length; ++i) { + methodDep.variableNodes[i] = instancesNode; + } + } + + if (method.hasModifier(ElementModifier.SYNCHRONIZED)) { + processAsyncMethod(methodDep); + } + } + + private void processAsyncMethod(MethodDependency methodDep) { + if (asyncSupported) { + linkMethod(MONITOR_ENTER_METHOD).use(); + } + + linkMethod(MONITOR_ENTER_SYNC_METHOD).use(); + + if (asyncSupported) { + linkMethod(MONITOR_EXIT_METHOD).use(); + } + + linkMethod(MONITOR_EXIT_SYNC_METHOD).use(); + } + + @Override + DependencyNode createParameterNode(MethodReference method, ValueType type, int index) { + return instancesNode; + } + + @Override + DependencyNode createResultNode(MethodReference method) { + return instancesNode; + } + + @Override + DependencyNode createThrownNode(MethodReference method) { + return instancesNode; + } + + @Override + DependencyNode createFieldNode(FieldReference field, ValueType type) { + return instancesNode; + } + + @Override + DependencyNode createArrayItemNode(DependencyNode parent) { + return instancesNode; + } + + @Override + DependencyNode createClassValueNode(int degree, DependencyNode parent) { + return classesNode; + } + + private DependencyNode getSubtypeNode(String type) { + if (type.equals("java.lang.Object")) { + return instancesNode; + } + return subtypeNodes.computeIfAbsent(type, key -> { + DependencyNode node = createNode(); + + defer(() -> { + int degree = 0; + while (degree < key.length() && key.charAt(degree) == '[') { + degree++; + } + + if (degree > 0) { + ValueType fullType = ValueType.parse(key); + if (fullType instanceof ValueType.Object) { + String prefix = key.substring(0, degree) + "L"; + String className = ((ValueType.Object) fullType).getClassName(); + ClassReader cls = getClassSource().get(key); + if (cls != null) { + if (cls.getParent() != null) { + node.connect(getSubtypeNode(prefix + cls.getParent().replace('.', '/') + ";")); + } else { + node.connect(getSubtypeNode("java.lang.Object")); + } + for (String itf : cls.getInterfaces()) { + node.connect(getSubtypeNode(prefix + itf.replace('.', '/') + ";")); + } + } + } else { + node.connect(getSubtypeNode("java.lang.Object")); + } + } else { + ClassReader cls = getClassSource().get(key); + if (cls != null) { + if (cls.getParent() != null) { + node.connect(getSubtypeNode(cls.getParent())); + } + for (String itf : cls.getInterfaces()) { + node.connect(getSubtypeNode(itf)); + } + } + } + }); + + return node; + }); + } + + FastVirtualCallConsumer getVirtualCallConsumer(MethodReference method) { + return virtualCallConsumers.computeIfAbsent(method, key -> { + FastVirtualCallConsumer consumer = new FastVirtualCallConsumer(instancesNode, key.getDescriptor(), this); + defer(() -> { + getSubtypeNode(method.getClassName()).addConsumer(consumer); + }); + return consumer; + }); + } + + @Override + boolean domainOptimizationEnabled() { + return false; + } +} diff --git a/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java new file mode 100644 index 000000000..24ad64673 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/FastInstructionAnalyzer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 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.dependency; + +import java.util.List; +import org.teavm.model.CallLocation; +import org.teavm.model.MethodReference; +import org.teavm.model.VariableReader; + +class FastInstructionAnalyzer extends AbstractInstructionAnalyzer { + private FastDependencyAnalyzer dependencyAnalyzer; + private MethodReference callerMethod; + + FastInstructionAnalyzer(FastDependencyAnalyzer dependencyAnalyzer) { + this.dependencyAnalyzer = dependencyAnalyzer; + } + + @Override + protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments) { + CallLocation callLocation = getCallLocation(); + if (instance == null) { + dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation); + } + MethodDependency methodDep = dependencyAnalyzer.linkMethod(method); + methodDep.addLocation(callLocation); + methodDep.use(); + } + + @Override + protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments) { + dependencyAnalyzer.getVirtualCallConsumer(method).addLocation(getCallLocation()); + + dependencyAnalyzer.getClassSource().overriddenMethods(method).forEach(methodImpl -> { + dependencyAnalyzer.linkMethod(methodImpl.getReference()).addLocation(getCallLocation()); + }); + } + + + @Override + public void cloneArray(VariableReader receiver, VariableReader array) { + DependencyNode arrayNode = getNode(array); + MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD); + cloneDep.addLocation(getCallLocation()); + cloneDep.use(); + } + + @Override + protected DependencyNode getNode(VariableReader variable) { + return dependencyAnalyzer.instancesNode; + } + + @Override + protected DependencyAnalyzer getAnalyzer() { + return dependencyAnalyzer; + } +} diff --git a/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java b/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java new file mode 100644 index 000000000..276ff68b4 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/FastVirtualCallConsumer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018 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.dependency; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.teavm.model.CallLocation; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +class FastVirtualCallConsumer implements DependencyConsumer { + private final DependencyNode node; + private final MethodDescriptor methodDesc; + private final DependencyAnalyzer analyzer; + private final Map callLocations = new LinkedHashMap<>(); + private final Set methods = new LinkedHashSet<>(); + + FastVirtualCallConsumer(DependencyNode node, MethodDescriptor methodDesc, DependencyAnalyzer analyzer) { + this.node = node; + this.methodDesc = methodDesc; + this.analyzer = analyzer; + } + + @Override + public void consume(DependencyType type) { + String className = type.getName(); + if (DependencyAnalyzer.shouldLog) { + System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " + + "Target class is " + className); + } + if (className.startsWith("[")) { + className = "java.lang.Object"; + type = analyzer.getType(className); + } + + MethodDependency methodDep = analyzer.linkMethod(className, methodDesc); + if (!methods.add(methodDep)) { + return; + } + + for (CallLocation location : callLocations.values()) { + methodDep.addLocation(location); + } + + if (!methodDep.isMissing()) { + methodDep.use(); + } + } + + void addLocation(CallLocation location) { + if (callLocations.putIfAbsent(location.getMethod(), location) == null) { + for (MethodDependency method : methods) { + method.addLocation(location); + } + } + } +} diff --git a/core/src/main/java/org/teavm/dependency/FieldDependency.java b/core/src/main/java/org/teavm/dependency/FieldDependency.java index ab72c3b78..253a31415 100644 --- a/core/src/main/java/org/teavm/dependency/FieldDependency.java +++ b/core/src/main/java/org/teavm/dependency/FieldDependency.java @@ -15,6 +15,12 @@ */ package org.teavm.dependency; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.teavm.callgraph.DefaultCallGraphNode; +import org.teavm.model.CallLocation; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; @@ -22,6 +28,9 @@ public class FieldDependency implements FieldDependencyInfo { DependencyNode value; private FieldReader field; private FieldReference reference; + private List locationListeners; + private Set locations; + boolean activated; FieldDependency(DependencyNode value, FieldReader field, FieldReference reference) { this.value = value; @@ -47,4 +56,32 @@ public class FieldDependency implements FieldDependencyInfo { public boolean isMissing() { return field == null; } + + public FieldDependency addLocation(CallLocation location) { + DefaultCallGraphNode node = value.dependencyAnalyzer.callGraph.getNode(location.getMethod()); + if (locations == null) { + locations = new LinkedHashSet<>(); + } + if (locations.add(location)) { + node.addFieldAccess(reference, location.getSourceLocation()); + if (locationListeners != null) { + for (LocationListener listener : locationListeners.toArray(new LocationListener[0])) { + listener.locationAdded(location); + } + } + } + return this; + } + + public void addLocationListener(LocationListener listener) { + if (locationListeners == null) { + locationListeners = new ArrayList<>(); + locationListeners.add(listener); + if (locations != null) { + for (CallLocation location : locations.toArray(new CallLocation[0])) { + listener.locationAdded(location); + } + } + } + } } diff --git a/core/src/main/java/org/teavm/dependency/LocationListener.java b/core/src/main/java/org/teavm/dependency/LocationListener.java new file mode 100644 index 000000000..2391c968f --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/LocationListener.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018 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.dependency; + +import org.teavm.model.CallLocation; + +public interface LocationListener { + void locationAdded(CallLocation location); +} diff --git a/core/src/main/java/org/teavm/dependency/MethodDependency.java b/core/src/main/java/org/teavm/dependency/MethodDependency.java index b340f15f1..4acab4e8e 100644 --- a/core/src/main/java/org/teavm/dependency/MethodDependency.java +++ b/core/src/main/java/org/teavm/dependency/MethodDependency.java @@ -15,7 +15,13 @@ */ package org.teavm.dependency; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.teavm.callgraph.DefaultCallGraphNode; +import org.teavm.model.CallLocation; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -31,6 +37,9 @@ public class MethodDependency implements MethodDependencyInfo { boolean used; DependencyPlugin dependencyPlugin; boolean dependencyPluginAttached; + private List locationListeners; + private Set locations; + boolean activated; MethodDependency(DependencyAnalyzer dependencyAnalyzer, DependencyNode[] variableNodes, int parameterCount, DependencyNode resultNode, DependencyNode thrown, MethodHolder method, MethodReference reference) { @@ -100,6 +109,34 @@ public class MethodDependency implements MethodDependencyInfo { return used; } + public MethodDependency addLocation(CallLocation location) { + DefaultCallGraphNode node = dependencyAnalyzer.callGraph.getNode(location.getMethod()); + if (locations == null) { + locations = new LinkedHashSet<>(); + } + if (locations.add(location)) { + node.addCallSite(reference, location.getSourceLocation()); + if (locationListeners != null) { + for (LocationListener listener : locationListeners.toArray(new LocationListener[0])) { + listener.locationAdded(location); + } + } + } + return this; + } + + public void addLocationListener(LocationListener listener) { + if (locationListeners == null) { + locationListeners = new ArrayList<>(); + locationListeners.add(listener); + if (locations != null) { + for (CallLocation location : locations.toArray(new CallLocation[0])) { + listener.locationAdded(location); + } + } + } + } + public MethodDependency propagate(int parameterIndex, Class type) { return propagate(parameterIndex, dependencyAnalyzer.getType(type.getName())); } diff --git a/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java new file mode 100644 index 000000000..c2f92b81d --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012 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.dependency; + +import org.teavm.common.ServiceRepository; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class PreciseDependencyAnalyzer extends DependencyAnalyzer { + public PreciseDependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, + ServiceRepository services, Diagnostics diagnostics) { + super(classSource, classLoader, services, diagnostics); + } + + @Override + protected void processMethod(MethodDependency methodDep) { + DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(this); + graphBuilder.buildGraph(methodDep); + } + + @Override + DependencyNode createParameterNode(MethodReference method, ValueType type, int index) { + DependencyNode node = createNode(type); + node.method = method; + if (shouldTag) { + node.setTag(method + ":" + index); + } + return node; + } + + @Override + DependencyNode createResultNode(MethodReference method) { + DependencyNode node = createNode(method.getReturnType()); + node.method = method; + if (shouldTag) { + node.setTag(method + ":RESULT"); + } + return node; + } + + @Override + DependencyNode createThrownNode(MethodReference method) { + DependencyNode node = createNode(); + node.method = method; + if (shouldTag) { + node.setTag(method + ":THROWN"); + } + return node; + } + + @Override + DependencyNode createFieldNode(FieldReference field, ValueType type) { + DependencyNode node = createNode(type); + if (shouldTag) { + node.setTag(field.getClassName() + "#" + field.getFieldName()); + } + return node; + } + + @Override + DependencyNode createArrayItemNode(DependencyNode parent) { + ValueType itemTypeFilter = parent.typeFilter instanceof ValueType.Array + ? ((ValueType.Array) parent.typeFilter).getItemType() + : null; + DependencyNode node = createNode(itemTypeFilter); + node.degree = parent.degree + 1; + node.method = parent.method; + if (DependencyAnalyzer.shouldTag) { + node.tag = parent.tag + "["; + } + return node; + } + + @Override + DependencyNode createClassValueNode(int degree, DependencyNode parent) { + DependencyNode node = createNode(); + node.degree = degree; + node.classValueNode = node; + node.classNodeParent = parent; + if (DependencyAnalyzer.shouldTag) { + node.tag = parent.tag + "@"; + } + return node; + } + + @Override + boolean domainOptimizationEnabled() { + return true; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/teavm/dependency/SuperArrayFilter.java b/core/src/main/java/org/teavm/dependency/SuperArrayFilter.java index f532b177b..3e28ee195 100644 --- a/core/src/main/java/org/teavm/dependency/SuperArrayFilter.java +++ b/core/src/main/java/org/teavm/dependency/SuperArrayFilter.java @@ -15,11 +15,14 @@ */ package org.teavm.dependency; +import java.util.BitSet; import org.teavm.model.ValueType; class SuperArrayFilter implements DependencyTypeFilter { private DependencyAnalyzer analyzer; private DependencyTypeFilter itemTypeFilter; + private BitSet knownTypes = new BitSet(); + private BitSet cache = new BitSet(); SuperArrayFilter(DependencyAnalyzer analyzer, DependencyTypeFilter itemTypeFilter) { this.analyzer = analyzer; @@ -28,6 +31,16 @@ class SuperArrayFilter implements DependencyTypeFilter { @Override public boolean match(DependencyType type) { + if (knownTypes.get(type.index)) { + return cache.get(type.index); + } + boolean result = matchCacheMiss(type); + knownTypes.set(type.index); + cache.set(type.index, result); + return result; + } + + private boolean matchCacheMiss(DependencyType type) { if (!type.getName().startsWith("[")) { return false; } @@ -42,4 +55,26 @@ class SuperArrayFilter implements DependencyTypeFilter { } return itemTypeFilter.match(analyzer.getType(typeName)); } + + @Override + public int[] tryExtract(BitSet types) { + int[] result = itemTypeFilter.tryExtract(types); + if (result == null) { + return null; + } + + for (int i = 0; i < result.length; ++i) { + String name = analyzer.types.get(i).getName(); + int mapped; + if (name.startsWith("[")) { + mapped = analyzer.getType("[" + name).index; + } else if (name.startsWith("~")) { + mapped = analyzer.getType("[" + name.substring(1)).index; + } else { + mapped = analyzer.getType(ValueType.arrayOf(ValueType.object(name)).toString()).index; + } + result[i] = mapped; + } + return result; + } } diff --git a/core/src/main/java/org/teavm/dependency/SuperClassFilter.java b/core/src/main/java/org/teavm/dependency/SuperClassFilter.java index 96f8fea0d..0c94af4bf 100644 --- a/core/src/main/java/org/teavm/dependency/SuperClassFilter.java +++ b/core/src/main/java/org/teavm/dependency/SuperClassFilter.java @@ -15,27 +15,40 @@ */ package org.teavm.dependency; -import com.carrotsearch.hppc.IntIntHashMap; -import com.carrotsearch.hppc.IntIntMap; -import org.teavm.model.ClassReaderSource; +import java.util.BitSet; +import org.teavm.common.OptionalPredicate; class SuperClassFilter implements DependencyTypeFilter { - private ClassReaderSource classSource; - private String superType; - private IntIntMap cache = new IntIntHashMap(); + private static final int[] EMPTY_ARRAY = new int[0]; + private DependencyType superType; + private OptionalPredicate predicate; + private BitSet knownTypes = new BitSet(); + private BitSet cache = new BitSet(); - SuperClassFilter(ClassReaderSource classSource, String superType) { - this.classSource = classSource; + SuperClassFilter(DependencyAnalyzer dependencyAnalyzer, DependencyType superType) { this.superType = superType; + predicate = dependencyAnalyzer.getClassHierarchy().getSuperclassPredicate(superType.getName()); } @Override public boolean match(DependencyType type) { - int result = cache.getOrDefault(type.index, -1); - if (result < 0) { - result = classSource.isSuperType(superType, type.getName()).orElse(false) ? 1 : 0; - cache.put(type.index, result); + if (!superType.subtypeExists) { + return superType.index == type.index; } - return result != 0; + if (knownTypes.get(type.index)) { + return cache.get(type.index); + } + boolean result = predicate.test(type.getName(), false); + knownTypes.set(type.index); + cache.set(type.index, result); + return result; + } + + @Override + public int[] tryExtract(BitSet types) { + if (superType.subtypeExists) { + return null; + } + return types.get(superType.index) ? new int[] { superType.index } : EMPTY_ARRAY; } } diff --git a/core/src/main/java/org/teavm/dependency/Transition.java b/core/src/main/java/org/teavm/dependency/Transition.java index c2f526c5b..262a97986 100644 --- a/core/src/main/java/org/teavm/dependency/Transition.java +++ b/core/src/main/java/org/teavm/dependency/Transition.java @@ -17,16 +17,14 @@ package org.teavm.dependency; import com.carrotsearch.hppc.IntHashSet; import java.util.Arrays; -import java.util.BitSet; import java.util.Collection; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ValueType; class Transition { DependencyNode source; DependencyNode destination; DependencyTypeFilter filter; - private BitSet knownFilteredOffTypes; IntHashSet pendingTypes; byte destSubsetOfSrc; @@ -79,7 +77,7 @@ class Transition { } boolean shouldMergeDomains() { - if (filter != null || !isDestSubsetOfSrc()) { + if (!source.dependencyAnalyzer.domainOptimizationEnabled() || filter != null || !isDestSubsetOfSrc()) { return false; } if (destination.typeSet == null) { @@ -163,18 +161,7 @@ class Transition { return true; } - if (knownFilteredOffTypes != null && knownFilteredOffTypes.get(type.index)) { - return false; - } - if (!filter.match(type)) { - if (knownFilteredOffTypes == null) { - knownFilteredOffTypes = new BitSet(64); - } - knownFilteredOffTypes.set(type.index); - return false; - } - - return true; + return filter.match(type); } boolean pointsToDomainOrigin() { @@ -198,7 +185,8 @@ class Transition { ValueType sourceType = source.typeFilter; ValueType destType = destination.typeFilter; - ClassReaderSource classSource = source.dependencyAnalyzer.getClassSource(); - return classSource.isSuperType(sourceType, destType).orElse(false); + ClassHierarchy hierarchy = source.dependencyAnalyzer.getClassHierarchy(); + + return hierarchy.isSuperType(sourceType, destType, false); } } diff --git a/core/src/main/java/org/teavm/dependency/TypeSet.java b/core/src/main/java/org/teavm/dependency/TypeSet.java index e603a0b2d..b75588620 100644 --- a/core/src/main/java/org/teavm/dependency/TypeSet.java +++ b/core/src/main/java/org/teavm/dependency/TypeSet.java @@ -71,7 +71,7 @@ class TypeSet { DependencyType[] getTypes() { if (this.types != null) { - DependencyType[] types = new DependencyType[this.types.cardinality()]; + DependencyType[] types = new DependencyType[typesCount]; int j = 0; for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) { DependencyType type = dependencyAnalyzer.types.get(index); @@ -95,12 +95,33 @@ class TypeSet { int j = 0; DependencyType[] types; if (this.types != null) { - types = new DependencyType[this.types.cardinality()]; - for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) { - DependencyType type = dependencyAnalyzer.types.get(index); - if (sourceNode.filter(type) && !targetNode.hasType(type) && targetNode.filter(type) - && (filter == null || filter.match(type))) { - types[j++] = type; + int[] filteredTypes = null; + if (typesCount > 15) { + filteredTypes = filter != null ? filter.tryExtract(this.types) : null; + if (filteredTypes == null) { + filteredTypes = sourceNode.getFilter().tryExtract(this.types); + } + if (filteredTypes == null) { + filteredTypes = targetNode.getFilter().tryExtract(this.types); + } + } + if (filteredTypes != null) { + types = new DependencyType[filteredTypes.length]; + for (int index : filteredTypes) { + DependencyType type = dependencyAnalyzer.types.get(index); + if (sourceNode.filter(type) && !targetNode.hasType(type) && targetNode.filter(type) + && (filter == null || filter.match(type))) { + types[j++] = type; + } + } + } else { + types = new DependencyType[typesCount]; + for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) { + DependencyType type = dependencyAnalyzer.types.get(index); + if (sourceNode.filter(type) && !targetNode.hasType(type) && targetNode.filter(type) + && (filter == null || filter.match(type))) { + types[j++] = type; + } } } } else if (this.smallTypes != null) { diff --git a/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java b/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java new file mode 100644 index 000000000..07f8f8316 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018 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.dependency; + +import java.util.BitSet; +import org.teavm.model.CallLocation; +import org.teavm.model.MethodDescriptor; + +class VirtualCallConsumer implements DependencyConsumer { + private final DependencyNode node; + private final MethodDescriptor methodDesc; + private final DependencyAnalyzer analyzer; + private final DependencyNode[] parameters; + private final DependencyNode result; + private final CallLocation location; + private final BitSet knownTypes = new BitSet(); + private DependencyGraphBuilder.ExceptionConsumer exceptionConsumer; + private DependencyTypeFilter filter; + + VirtualCallConsumer(DependencyNode node, String filterClass, + MethodDescriptor methodDesc, DependencyAnalyzer analyzer, DependencyNode[] parameters, + DependencyNode result, CallLocation location, + DependencyGraphBuilder.ExceptionConsumer exceptionConsumer) { + this.node = node; + this.filter = analyzer.getSuperClassFilter(filterClass); + this.methodDesc = methodDesc; + this.analyzer = analyzer; + this.parameters = parameters; + this.result = result; + this.location = location; + this.exceptionConsumer = exceptionConsumer; + } + + @Override + public void consume(DependencyType type) { + if (!filter.match(type)) { + return; + } + + if (knownTypes.get(type.index)) { + return; + } + knownTypes.set(type.index); + + String className = type.getName(); + if (DependencyAnalyzer.shouldLog) { + System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " + + "Target class is " + className); + } + if (className.startsWith("[")) { + className = "java.lang.Object"; + type = analyzer.getType(className); + } + + MethodDependency methodDep = analyzer.linkMethod(className, methodDesc); + methodDep.addLocation(location); + if (!methodDep.isMissing()) { + methodDep.use(); + DependencyNode[] targetParams = methodDep.getVariables(); + if (parameters[0] != null && targetParams[0] != null) { + parameters[0].connect(targetParams[0], + analyzer.getSuperClassFilter(methodDep.getMethod().getOwnerName())); + } + for (int i = 1; i < parameters.length; ++i) { + if (parameters[i] != null && targetParams[i] != null) { + parameters[i].connect(targetParams[i]); + } + } + if (result != null && methodDep.getResult() != null) { + methodDep.getResult().connect(result); + } + methodDep.getThrown().addConsumer(exceptionConsumer); + } + } +} diff --git a/core/src/main/java/org/teavm/diagnostics/Problem.java b/core/src/main/java/org/teavm/diagnostics/Problem.java index b310e154d..e6f4134e2 100644 --- a/core/src/main/java/org/teavm/diagnostics/Problem.java +++ b/core/src/main/java/org/teavm/diagnostics/Problem.java @@ -90,7 +90,7 @@ public class Problem implements Serializable { default: return index; } - int digitsEnd = passDigits(next); + int digitsEnd = skipDigits(next); if (digitsEnd == next) { return index; } @@ -139,7 +139,7 @@ public class Problem implements Serializable { return next; } - private int passDigits(int index) { + private int skipDigits(int index) { while (index < text.length() && Character.isDigit(text.charAt(index))) { ++index; } diff --git a/core/src/main/java/org/teavm/model/ClassHierarchy.java b/core/src/main/java/org/teavm/model/ClassHierarchy.java new file mode 100644 index 000000000..506d77559 --- /dev/null +++ b/core/src/main/java/org/teavm/model/ClassHierarchy.java @@ -0,0 +1,129 @@ +/* + * Copyright 2018 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 com.carrotsearch.hppc.ObjectByteHashMap; +import com.carrotsearch.hppc.ObjectByteMap; +import java.util.HashMap; +import java.util.Map; +import org.teavm.common.OptionalPredicate; + +public class ClassHierarchy { + private final ClassReaderSource classSource; + private final Map> superclassPredicateCache = new HashMap<>(); + + public ClassHierarchy(ClassReaderSource classSource) { + this.classSource = classSource; + superclassPredicateCache.put("java.lang.Object", (c, d) -> true); + } + + public ClassReaderSource getClassSource() { + return classSource; + } + + public boolean isSuperType(ValueType superType, ValueType subType, boolean defaultValue) { + if (superType.equals(subType)) { + return true; + } + if (superType instanceof ValueType.Primitive || subType instanceof ValueType.Primitive) { + return false; + } + if (superType.isObject("java.lang.Object")) { + return true; + } + if (superType instanceof ValueType.Object && subType instanceof ValueType.Object) { + return isSuperType(((ValueType.Object) superType).getClassName(), + ((ValueType.Object) subType).getClassName(), defaultValue); + } else if (superType instanceof ValueType.Array & subType instanceof ValueType.Array) { + return isSuperType(((ValueType.Array) superType).getItemType(), ((ValueType.Array) subType).getItemType(), + defaultValue); + } else { + return false; + } + } + + public boolean isSuperType(String superType, String subType, boolean defaultValue) { + if (subType.equals(superType)) { + return true; + } + return getSuperclassPredicate(superType).test(subType, defaultValue); + } + + public OptionalPredicate getSuperclassPredicate(String superclass) { + return superclassPredicateCache.computeIfAbsent(superclass, SuperclassPredicate::new); + } + + class SuperclassPredicate implements OptionalPredicate { + private final String superclass; + private final ObjectByteMap cache = new ObjectByteHashMap<>(100, 0.5); + + SuperclassPredicate(String superclass) { + this.superclass = superclass; + } + + @Override + public boolean test(String value, boolean defaultResult) { + if (value.startsWith("[") || value.startsWith("~")) { + return false; + } + switch (test(value)) { + case 1: + return true; + case 2: + return false; + default: + return defaultResult; + } + } + + byte test(String value) { + if (value.equals(superclass)) { + return 1; + } + byte result = cache.get(value); + if (result == 0) { + result = testCacheMiss(value); + cache.put(value, result); + } + return result; + } + + byte testCacheMiss(String value) { + if (value.equals(superclass)) { + return 1; + } + + ClassReader cls = classSource.get(value); + if (cls == null) { + return 2; + } + + if (cls.getParent() != null) { + if (test(cls.getParent()) == 1) { + return 1; + } + } + + for (String itf : cls.getInterfaces()) { + if (test(itf) == 1) { + return 1; + } + } + + return 2; + } + } +} diff --git a/core/src/main/java/org/teavm/model/ClassHolderSource.java b/core/src/main/java/org/teavm/model/ClassHolderSource.java index 9e60a45d3..2224615b5 100644 --- a/core/src/main/java/org/teavm/model/ClassHolderSource.java +++ b/core/src/main/java/org/teavm/model/ClassHolderSource.java @@ -37,6 +37,10 @@ public interface ClassHolderSource extends ClassReaderSource { return (MethodHolder) resolveImplementation(method); } + default MethodHolder resolveMutableImplementation(String className, MethodDescriptor descriptor) { + return (MethodHolder) resolveImplementation(className, descriptor); + } + default FieldHolder resolveMutable(FieldReference field) { return (FieldHolder) resolve(field); } diff --git a/core/src/main/java/org/teavm/model/ClassHolderTransformer.java b/core/src/main/java/org/teavm/model/ClassHolderTransformer.java index 13190bce2..734a1e525 100644 --- a/core/src/main/java/org/teavm/model/ClassHolderTransformer.java +++ b/core/src/main/java/org/teavm/model/ClassHolderTransformer.java @@ -15,8 +15,6 @@ */ package org.teavm.model; -import org.teavm.diagnostics.Diagnostics; - public interface ClassHolderTransformer { - void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics); + void transformClass(ClassHolder cls, ClassHolderTransformerContext context); } diff --git a/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java b/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java new file mode 100644 index 000000000..6726edb63 --- /dev/null +++ b/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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 org.teavm.cache.IncrementalDependencyRegistration; +import org.teavm.diagnostics.Diagnostics; + +public interface ClassHolderTransformerContext { + ClassHierarchy getHierarchy(); + + Diagnostics getDiagnostics(); + + IncrementalDependencyRegistration getIncrementalCache(); +} diff --git a/core/src/main/java/org/teavm/model/ClassReaderSource.java b/core/src/main/java/org/teavm/model/ClassReaderSource.java index a3ba62010..980f8b832 100644 --- a/core/src/main/java/org/teavm/model/ClassReaderSource.java +++ b/core/src/main/java/org/teavm/model/ClassReaderSource.java @@ -99,6 +99,10 @@ public interface ClassReaderSource { methodReference.getDescriptor(), new HashSet<>()); } + default MethodReader resolveImplementation(String className, MethodDescriptor descriptor) { + return ClassReaderSourceHelper.resolveMethodImplementation(this, className, descriptor, new HashSet<>()); + } + default FieldReader resolve(FieldReference field) { return getAncestors(field.getClassName()) .map(cls -> cls.getField(field.getFieldName())) @@ -115,24 +119,4 @@ public interface ClassReaderSource { default Optional isSuperType(String superType, String subType) { return ClassReaderSourceHelper.isSuperType(this, superType, subType); } - - default Optional isSuperType(ValueType superType, ValueType subType) { - if (superType.equals(subType)) { - return Optional.of(true); - } - if (superType instanceof ValueType.Primitive || subType instanceof ValueType.Primitive) { - return Optional.of(false); - } - if (superType.isObject("java.lang.Object")) { - return Optional.of(true); - } - if (superType instanceof ValueType.Object && subType instanceof ValueType.Object) { - return isSuperType(((ValueType.Object) superType).getClassName(), - ((ValueType.Object) subType).getClassName()); - } else if (superType instanceof ValueType.Array & subType instanceof ValueType.Array) { - return isSuperType(((ValueType.Array) superType).getItemType(), ((ValueType.Array) subType).getItemType()); - } else { - return Optional.of(false); - } - } } diff --git a/core/src/main/java/org/teavm/model/MethodHolder.java b/core/src/main/java/org/teavm/model/MethodHolder.java index 24058813b..ccc624541 100644 --- a/core/src/main/java/org/teavm/model/MethodHolder.java +++ b/core/src/main/java/org/teavm/model/MethodHolder.java @@ -21,6 +21,7 @@ public class MethodHolder extends MemberHolder implements MethodReader { private Program program; private AnnotationValue annotationDefault; private AnnotationContainer[] parameterAnnotations; + private MethodReference reference; public MethodHolder(MethodDescriptor descriptor) { super(descriptor.getName()); @@ -80,6 +81,7 @@ public class MethodHolder extends MemberHolder implements MethodReader { } void setOwner(ClassHolder owner) { + reference = null; this.owner = owner; } @@ -90,7 +92,13 @@ public class MethodHolder extends MemberHolder implements MethodReader { @Override public MethodReference getReference() { - return owner != null ? new MethodReference(owner.getName(), descriptor) : null; + if (owner == null) { + return null; + } + if (reference == null) { + reference = new MethodReference(owner.getName(), descriptor); + } + return reference; } @Override diff --git a/core/src/main/java/org/teavm/model/MethodReference.java b/core/src/main/java/org/teavm/model/MethodReference.java index 9ecb4c1ba..4d4dd13c5 100644 --- a/core/src/main/java/org/teavm/model/MethodReference.java +++ b/core/src/main/java/org/teavm/model/MethodReference.java @@ -140,7 +140,7 @@ public class MethodReference implements Serializable { @JsonValue public String toString() { if (reprCache == null) { - reprCache = className + "." + name + signatureToString(); + reprCache = className + "." + getDescriptor().toString(); } return reprCache; } diff --git a/core/src/main/java/org/teavm/model/ProgramCache.java b/core/src/main/java/org/teavm/model/ProgramCache.java index bb8d82881..3c527e0af 100644 --- a/core/src/main/java/org/teavm/model/ProgramCache.java +++ b/core/src/main/java/org/teavm/model/ProgramCache.java @@ -15,8 +15,11 @@ */ package org.teavm.model; -public interface ProgramCache { - Program get(MethodReference method); +import java.util.function.Supplier; +import org.teavm.cache.CacheStatus; - void store(MethodReference method, Program program); +public interface ProgramCache { + Program get(MethodReference method, CacheStatus status); + + void store(MethodReference method, Program program, Supplier dependencies); } diff --git a/core/src/main/java/org/teavm/model/ReferenceCache.java b/core/src/main/java/org/teavm/model/ReferenceCache.java index c8459188a..5295e2631 100644 --- a/core/src/main/java/org/teavm/model/ReferenceCache.java +++ b/core/src/main/java/org/teavm/model/ReferenceCache.java @@ -19,7 +19,7 @@ import java.util.HashMap; import java.util.Map; public class ReferenceCache { - private Map referenceCache = new HashMap<>(); + private Map> referenceCache = new HashMap<>(); private Map fieldRefenceCache = new HashMap<>(); private Map descriptorCache = new HashMap<>(); private Map valueTypeCache = new HashMap<>(); @@ -29,18 +29,13 @@ public class ReferenceCache { 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; + return getCached(reference.getClassName(), reference.getDescriptor()); + } + + public MethodReference getCached(String className, MethodDescriptor descriptor) { + return referenceCache + .computeIfAbsent(className, key -> new HashMap<>()) + .computeIfAbsent(descriptor, key -> new MethodReference(className, descriptor)); } public MethodDescriptor getCached(MethodDescriptor descriptor) { @@ -117,15 +112,6 @@ public class ReferenceCache { 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) { diff --git a/core/src/main/java/org/teavm/model/analysis/ClassInference.java b/core/src/main/java/org/teavm/model/analysis/ClassInference.java index cb6761cd9..f00e0bc88 100644 --- a/core/src/main/java/org/teavm/model/analysis/ClassInference.java +++ b/core/src/main/java/org/teavm/model/analysis/ClassInference.java @@ -35,6 +35,7 @@ import org.teavm.dependency.FieldDependencyInfo; import org.teavm.dependency.MethodDependencyInfo; import org.teavm.dependency.ValueDependencyInfo; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.Incoming; import org.teavm.model.Instruction; @@ -402,7 +403,7 @@ public class ClassInference { } private void propagateAlongCasts() { - ClassReaderSource classSource = dependencyInfo.getClassSource(); + ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy(); for (ValueCast cast : casts) { int fromNode = nodeMapping[packNodeAndDegree(cast.fromVariable, 0)]; @@ -429,7 +430,7 @@ public class ClassInference { type = ValueType.object(className); } - if (classSource.isSuperType(cast.targetType, type).orElse(false)) { + if (hierarchy.isSuperType(cast.targetType, type, false)) { changed = true; nodeChanged[toNode] = true; targetTypes.add(cursor.value); @@ -499,11 +500,11 @@ public class ClassInference { } private void propagateException(String thrownTypeName, BasicBlock block) { - ClassReaderSource classSource = dependencyInfo.getClassSource(); + ClassHierarchy hierarchy = dependencyInfo.getClassHierarchy(); for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { String expectedType = tryCatch.getExceptionType(); - if (expectedType == null || classSource.isSuperType(expectedType, thrownTypeName).orElse(false)) { + if (expectedType == null || hierarchy.isSuperType(expectedType, thrownTypeName, false)) { if (tryCatch.getHandler().getExceptionVariable() == null) { break; } diff --git a/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java b/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java index 5cfddb6ca..07c46e26b 100644 --- a/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java +++ b/core/src/main/java/org/teavm/model/emit/ProgramEmitter.java @@ -16,6 +16,7 @@ package org.teavm.model.emit; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.FieldReader; @@ -52,12 +53,14 @@ public final class ProgramEmitter { private Program program; private BasicBlock block; ClassReaderSource classSource; + ClassHierarchy hierarchy; private TextLocation currentLocation; - private ProgramEmitter(Program program, BasicBlock block, ClassReaderSource classSource) { + private ProgramEmitter(Program program, BasicBlock block, ClassHierarchy hierarchy) { this.program = program; this.block = block; - this.classSource = classSource; + this.classSource = hierarchy.getClassSource(); + this.hierarchy = hierarchy; } public Program getProgram() { @@ -218,7 +221,7 @@ public final class ProgramEmitter { public ValueEmitter invoke(MethodReference method, ValueEmitter... arguments) { for (int i = 0; i < method.parameterCount(); ++i) { - if (!classSource.isSuperType(method.parameterType(i), arguments[i].getType()).orElse(true)) { + if (!hierarchy.isSuperType(method.parameterType(i), arguments[i].getType(), true)) { throw new EmitException("Argument " + i + " of type " + arguments[i].getType() + " is " + "not compatible with method " + method); } @@ -387,13 +390,13 @@ public final class ProgramEmitter { block.add(insn); } - public static ProgramEmitter create(MethodHolder method, ClassReaderSource classSource) { + public static ProgramEmitter create(MethodHolder method, ClassHierarchy classSource) { ProgramEmitter pe = create(method.getDescriptor(), classSource); method.setProgram(pe.getProgram()); return pe; } - public static ProgramEmitter create(MethodDescriptor method, ClassReaderSource classSource) { + public static ProgramEmitter create(MethodDescriptor method, ClassHierarchy classSource) { Program program = new Program(); BasicBlock zeroBlock = program.createBasicBlock(); BasicBlock block = program.createBasicBlock(); @@ -483,7 +486,7 @@ public final class ProgramEmitter { return new StringBuilderEmitter(this); } - public static ProgramEmitter create(Program program, ClassReaderSource classSource) { + public static ProgramEmitter create(Program program, ClassHierarchy classSource) { return new ProgramEmitter(program, null, classSource); } } diff --git a/core/src/main/java/org/teavm/model/emit/ValueEmitter.java b/core/src/main/java/org/teavm/model/emit/ValueEmitter.java index 617cf873a..5c8549c0a 100644 --- a/core/src/main/java/org/teavm/model/emit/ValueEmitter.java +++ b/core/src/main/java/org/teavm/model/emit/ValueEmitter.java @@ -16,7 +16,7 @@ package org.teavm.model.emit; import org.teavm.model.BasicBlock; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHierarchy; import org.teavm.model.FieldReference; import org.teavm.model.Incoming; import org.teavm.model.MethodReference; @@ -424,16 +424,15 @@ public class ValueEmitter { throw new EmitException("Can't invoke method on non-object type: " + type); } - ClassReaderSource classSource = pe.getClassSource(); + ClassHierarchy hierarchy = pe.hierarchy; for (int i = 0; i < method.parameterCount(); ++i) { - if (!classSource.isSuperType(method.parameterType(i), arguments[i].getType()).orElse(false)) { + if (!hierarchy.isSuperType(method.parameterType(i), arguments[i].getType(), false)) { throw new EmitException("Argument " + i + " of type " + arguments[i].getType() + " is " + "not compatible with method " + method); } } - if (!pe.classSource.isSuperType(method.getClassName(), ((ValueType.Object) type).getClassName()) - .orElse(true)) { + if (!hierarchy.isSuperType(method.getClassName(), ((ValueType.Object) type).getClassName(), true)) { throw new EmitException("Can't call " + method + " on non-compatible class " + type); } @@ -639,7 +638,7 @@ public class ValueEmitter { } public void raise() { - if (!pe.classSource.isSuperType(ValueType.object("java.lang.Throwable"), type).orElse(true)) { + if (!pe.hierarchy.isSuperType(ValueType.object("java.lang.Throwable"), type, true)) { throw new EmitException("Can't throw non-exception value: " + type); } @@ -655,7 +654,7 @@ public class ValueEmitter { public ValueEmitter cast(ValueType type) { if (type.equals(this.type)) { return this; - } else if (pe.classSource.isSuperType(type, this.type).orElse(false)) { + } else if (pe.hierarchy.isSuperType(type, this.type, false)) { return pe.var(variable.getIndex(), type); } @@ -735,7 +734,7 @@ public class ValueEmitter { PrimitiveType primitiveType = ((ValueType.Primitive) this.type).getKind(); String boxClassName = getPrimitiveClassName(primitiveType); ValueEmitter result = invokeValueOf(boxClassName); - if (!pe.getClassSource().isSuperType(targetClass, boxClassName).orElse(false)) { + if (!pe.hierarchy.isSuperType(targetClass, boxClassName, false)) { throw new EmitException("Can't convert " + this.type + " to " + targetClass); } return result; @@ -837,7 +836,7 @@ public class ValueEmitter { } public ValueEmitter assertIs(ValueType type) { - if (!pe.classSource.isSuperType(type, this.type).orElse(true)) { + if (!pe.hierarchy.isSuperType(type, this.type, true)) { throw new EmitException("Value type " + this.type + " is not subtype of " + type); } return this; diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java b/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java index 3cea1652d..c037ea2a2 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java @@ -31,8 +31,8 @@ import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Function; import org.teavm.model.BasicBlockReader; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -65,7 +65,7 @@ public class ExportDependencyListener extends AbstractDependencyListener { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getMethod() == null || method.getMethod().getProgram() == null) { return; } @@ -127,10 +127,10 @@ public class ExportDependencyListener extends AbstractDependencyListener { private void processInvocation(DependencyAgent agent, CallLocation location, String functionClassName, String targetClassName, String methodName) { Diagnostics diagnostics = agent.getDiagnostics(); - ClassReaderSource classSource = agent.getClassSource(); + ClassHierarchy hierarchy = agent.getClassHierarchy(); boolean valid = true; - ClassReader functionClass = classSource.get(functionClassName); + ClassReader functionClass = hierarchy.getClassSource().get(functionClassName); if (functionClass == null) { diagnostics.error(location, "Class '{{c0}}' not found in class path", functionClassName); valid = false; @@ -139,7 +139,7 @@ public class ExportDependencyListener extends AbstractDependencyListener { valid = false; } - ClassReader targetClass = classSource.get(targetClassName); + ClassReader targetClass = hierarchy.getClassSource().get(targetClassName); if (targetClass == null) { diagnostics.error(location, "Class '{{c0}}' not found in class path", functionClassName); valid = false; @@ -168,7 +168,7 @@ public class ExportDependencyListener extends AbstractDependencyListener { } List signatureCandidates = candidates.stream() - .filter(method -> matchSignature(classSource, sam, method)) + .filter(method -> matchSignature(hierarchy, sam, method)) .collect(Collectors.toList()); if (signatureCandidates.isEmpty()) { if (candidates.size() == 1) { @@ -181,12 +181,14 @@ public class ExportDependencyListener extends AbstractDependencyListener { return; } - MethodReader resolvedMethod = findMostSpecific(diagnostics, location, classSource, signatureCandidates); + MethodReader resolvedMethod = findMostSpecific(diagnostics, location, hierarchy, signatureCandidates); if (resolvedMethod != null) { MethodReference reference = resolvedMethod.getReference(); resolvedMethods.put(new ExportedMethodKey(functionClassName, targetClassName, methodName), reference); exportedMethods.add(reference); - agent.linkMethod(reference, location).use(); + MethodDependency dep = agent.linkMethod(reference); + dep.addLocation(location); + dep.use(); } } @@ -216,13 +218,13 @@ public class ExportDependencyListener extends AbstractDependencyListener { } private MethodReader findMostSpecific(Diagnostics diagnostics, CallLocation location, - ClassReaderSource classSource, List methods) { + ClassHierarchy hierarchy, List methods) { MethodReader mostSpecificSoFar = methods.get(0); for (int i = 1; i < methods.size(); ++i) { MethodReader candidate = methods.get(i); - if (matchSignature(classSource, mostSpecificSoFar, candidate)) { + if (matchSignature(hierarchy, mostSpecificSoFar, candidate)) { mostSpecificSoFar = candidate; - } else if (!matchSignature(classSource, candidate, mostSpecificSoFar)) { + } else if (!matchSignature(hierarchy, candidate, mostSpecificSoFar)) { diagnostics.error(location, "Ambiguous methods found for this export, examples are '{{m0}}' " + "and {{m1}}", candidate, mostSpecificSoFar); return null; @@ -232,15 +234,14 @@ public class ExportDependencyListener extends AbstractDependencyListener { return mostSpecificSoFar; } - private boolean matchSignature(ClassReaderSource classSource, MethodReader functionMethod, + private boolean matchSignature(ClassHierarchy hierarchy, MethodReader functionMethod, MethodReader candidateMethod) { if (functionMethod.parameterCount() > candidateMethod.parameterCount()) { return false; } for (int i = 0; i < functionMethod.parameterCount(); ++i) { - if (!classSource.isSuperType(functionMethod.parameterType(i), - candidateMethod.parameterType(i)).orElse(false)) { + if (!hierarchy.isSuperType(functionMethod.parameterType(i), candidateMethod.parameterType(i), false)) { return false; } } diff --git a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java index a7ab58488..c6a915fb5 100644 --- a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java +++ b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java @@ -18,10 +18,12 @@ package org.teavm.model.optimization; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.teavm.common.OptionalPredicate; import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.MethodDependencyInfo; import org.teavm.dependency.ValueDependencyInfo; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.Instruction; @@ -34,12 +36,14 @@ import org.teavm.model.instructions.InvokeInstruction; public class Devirtualization { private DependencyInfo dependency; private ClassReaderSource classSource; + private ClassHierarchy hierarchy; private Set virtualMethods = new HashSet<>(); private Set readonlyVirtualMethods = Collections.unmodifiableSet(virtualMethods); public Devirtualization(DependencyInfo dependency, ClassReaderSource classSource) { this.dependency = dependency; this.classSource = classSource; + hierarchy = new ClassHierarchy(classSource); } public void apply(MethodHolder method) { @@ -72,13 +76,14 @@ public class Devirtualization { } private Set getImplementations(String[] classNames, MethodReference ref) { + OptionalPredicate isSuperclass = hierarchy.getSuperclassPredicate(ref.getClassName()); Set methods = new HashSet<>(); for (String className : classNames) { if (className.startsWith("[")) { className = "java.lang.Object"; } ClassReader cls = classSource.get(className); - if (cls == null || !classSource.isSuperType(ref.getClassName(), cls.getName()).orElse(false)) { + if (cls == null || !isSuperclass.test(cls.getName(), false)) { continue; } MethodDependencyInfo methodDep = dependency.getMethodImplementation(new MethodReference( diff --git a/core/src/main/java/org/teavm/model/transformation/ClassPatch.java b/core/src/main/java/org/teavm/model/transformation/ClassPatch.java index 3f266795e..32aea9e44 100644 --- a/core/src/main/java/org/teavm/model/transformation/ClassPatch.java +++ b/core/src/main/java/org/teavm/model/transformation/ClassPatch.java @@ -15,11 +15,10 @@ */ package org.teavm.model.transformation; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.FieldReference; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; @@ -33,7 +32,7 @@ public class ClassPatch implements ClassHolderTransformer { private FieldReference platformClassField = new FieldReference("java.lang.Class", "platformClass"); @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (!cls.getName().equals("java.lang.Class")) { return; } diff --git a/core/src/main/java/org/teavm/model/util/ModelUtils.java b/core/src/main/java/org/teavm/model/util/ModelUtils.java index a56a7b3aa..d71e08071 100644 --- a/core/src/main/java/org/teavm/model/util/ModelUtils.java +++ b/core/src/main/java/org/teavm/model/util/ModelUtils.java @@ -23,21 +23,24 @@ public final class ModelUtils { private ModelUtils() { } - public static ClassHolder copyClass(ClassReader original) { - ClassHolder copy = new ClassHolder(original.getName()); - copy.setLevel(original.getLevel()); - copy.getModifiers().addAll(original.readModifiers()); - copy.setParent(original.getParent()); - copy.getInterfaces().addAll(original.getInterfaces()); + public static ClassHolder copyClass(ClassReader original, ClassHolder target) { + target.setLevel(original.getLevel()); + target.getModifiers().addAll(original.readModifiers()); + target.setParent(original.getParent()); + target.getInterfaces().addAll(original.getInterfaces()); for (MethodReader method : original.getMethods()) { - copy.addMethod(copyMethod(method)); + target.addMethod(copyMethod(method)); } for (FieldReader field : original.getFields()) { - copy.addField(copyField(field)); + target.addField(copyField(field)); } - copy.setOwnerName(original.getOwnerName()); - copyAnnotations(original.getAnnotations(), copy.getAnnotations()); - return copy; + target.setOwnerName(original.getOwnerName()); + copyAnnotations(original.getAnnotations(), target.getAnnotations()); + return target; + } + + public static ClassHolder copyClass(ClassReader original) { + return copyClass(original, new ClassHolder(original.getName())); } public static MethodHolder copyMethod(MethodReader method) { diff --git a/core/src/main/java/org/teavm/model/util/ProgramUtils.java b/core/src/main/java/org/teavm/model/util/ProgramUtils.java index e4f87bcaf..41906e792 100644 --- a/core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -45,6 +45,9 @@ import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.RaiseInstruction; public final class ProgramUtils { + private static final MethodReference NPE_INIT_METHOD = new MethodReference( + NullPointerException.class, "", void.class); + private ProgramUtils() { } @@ -219,7 +222,7 @@ public final class ProgramUtils { InvokeInstruction initNPE = new InvokeInstruction(); initNPE.setType(InvocationType.SPECIAL); initNPE.setInstance(newNPE.getReceiver()); - initNPE.setMethod(new MethodReference(NullPointerException.class, "", void.class)); + initNPE.setMethod(NPE_INIT_METHOD); initNPE.setLocation(location); RaiseInstruction raise = new RaiseInstruction(); diff --git a/core/src/main/java/org/teavm/parsing/ProgramParser.java b/core/src/main/java/org/teavm/parsing/ProgramParser.java index 1aac6bf6f..c8f73bcad 100644 --- a/core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -43,7 +43,6 @@ import org.teavm.model.Instruction; import org.teavm.model.InvokeDynamicInstruction; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHandle; -import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.ReferenceCache; import org.teavm.model.RuntimeConstant; @@ -654,7 +653,7 @@ public class ProgramParser { if (instance == -1) { InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(referenceCache.getCached(new MethodReference(ownerCls, method))); + insn.setMethod(referenceCache.getCached(ownerCls, method)); if (result >= 0) { insn.setReceiver(getVariable(result)); } @@ -667,7 +666,7 @@ public class ProgramParser { } else { insn.setType(InvocationType.VIRTUAL); } - insn.setMethod(referenceCache.getCached(new MethodReference(ownerCls, method))); + insn.setMethod(referenceCache.getCached(ownerCls, method)); if (result >= 0) { insn.setReceiver(getVariable(result)); } diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index f0a6d3aba..795df7ddb 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -29,8 +29,13 @@ import java.util.Map; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; -import org.teavm.cache.NoCache; +import org.teavm.cache.AlwaysStaleCacheStatus; +import org.teavm.cache.AnnotationAwareCacheStatus; +import org.teavm.cache.CacheStatus; +import org.teavm.cache.EmptyProgramCache; +import org.teavm.cache.ProgramDependencyExtractor; import org.teavm.common.ServiceRepository; import org.teavm.dependency.BootstrapMethodSubstitutor; import org.teavm.dependency.DependencyAnalyzer; @@ -123,8 +128,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private final Set readonlyPreservedClasses = Collections.unmodifiableSet(preservedClasses); private final Map, Object> services = new HashMap<>(); private final Properties properties = new Properties(); - private ProgramCache programCache; - private boolean incremental; + private ProgramCache programCache = EmptyProgramCache.INSTANCE; + private CacheStatus rawCacheStatus = AlwaysStaleCacheStatus.INSTANCE; private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.SIMPLE; private TeaVMProgressListener progressListener; private boolean cancelled; @@ -132,12 +137,16 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private TeaVMTarget target; private Map, TeaVMHostExtension> extensions = new HashMap<>(); private Set virtualMethods; + private AnnotationAwareCacheStatus cacheStatus; + private ProgramDependencyExtractor programDependencyExtractor = new ProgramDependencyExtractor(); + private List> additionalVirtualMethods = new ArrayList<>(); TeaVM(TeaVMBuilder builder) { target = builder.target; classSource = builder.classSource; classLoader = builder.classLoader; - dependencyAnalyzer = new DependencyAnalyzer(this.classSource, classLoader, this, diagnostics); + dependencyAnalyzer = builder.dependencyAnalyzerFactory.create(this.classSource, classLoader, + this, diagnostics); progressListener = new TeaVMProgressListener() { @Override public TeaVMProgressFeedback progressReached(int progress) { return TeaVMProgressFeedback.CONTINUE; @@ -161,6 +170,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } } + public void addVirtualMethods(Predicate virtualMethods) { + additionalVirtualMethods.add(virtualMethods); + } + @Override public void add(DependencyListener listener) { dependencyAnalyzer.addDependencyListener(listener); @@ -213,12 +226,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository { this.programCache = programCache; } - public boolean isIncremental() { - return incremental; - } - - public void setIncremental(boolean incremental) { - this.incremental = incremental; + public void setCacheStatus(CacheStatus cacheStatus) { + rawCacheStatus = cacheStatus; } public TeaVMOptimizationLevel getOptimizationLevel() { @@ -263,16 +272,17 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } if (cls.getMethod(MAIN_METHOD_DESC) == null) { - diagnostics.error(null, "Specified main class '{{c0}}' does not have method '" + MAIN_METHOD_DESC + "'"); + diagnostics.error(null, "Specified main class '{{c0}}' does not have method '" + MAIN_METHOD_DESC + "'", + cls.getName()); return; } MethodDependency mainMethod = dependencyAnalyzer.linkMethod(new MethodReference(className, - "main", ValueType.parse(String[].class), ValueType.VOID), null); + "main", ValueType.parse(String[].class), ValueType.VOID)); TeaVMEntryPoint entryPoint = new TeaVMEntryPoint(name, mainMethod); dependencyAnalyzer.defer(() -> { - dependencyAnalyzer.linkClass(className, null).initClass(null); + dependencyAnalyzer.linkClass(className).initClass(null); mainMethod.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/String;")); mainMethod.getVariable(1).getArrayItem().propagate(dependencyAnalyzer.getType("java.lang.String")); mainMethod.use(); @@ -286,7 +296,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { public void preserveType(String className) { dependencyAnalyzer.defer(() -> { - dependencyAnalyzer.linkClass(className, null).initClass(null); + dependencyAnalyzer.linkClass(className).initClass(null); }); preservedClasses.add(className); } @@ -304,7 +314,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { /** * Gets a {@link ClassReaderSource} which is similar to that of {@link #getClassSource()}, * except that it also contains classes with applied transformations together with - * classes, generated via {@link DependencyAnalyzer#submitClass(ClassHolder)}. + * classes, generated via {@link org.teavm.dependency.DependencyAgent#submitClass(ClassHolder)}. */ public ClassReaderSource getDependencyClassSource() { return dependencyAnalyzer.getClassSource(); @@ -354,6 +364,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } + cacheStatus = new AnnotationAwareCacheStatus(rawCacheStatus, dependencyAnalyzer.getIncrementalDependencies()); + cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass); + // Link reportPhase(TeaVMPhase.LINKING, 1); if (wasCancelled()) { @@ -368,17 +381,17 @@ public class TeaVM implements TeaVMHost, ServiceRepository { // Optimize and allocate registers reportPhase(TeaVMPhase.OPTIMIZATION, 1); - if (!incremental) { + if (optimizationLevel != TeaVMOptimizationLevel.SIMPLE) { devirtualize(classSet, dependencyAnalyzer); if (wasCancelled()) { return; } + } - dependencyAnalyzer.cleanup(); - inline(classSet, dependencyAnalyzer); - if (wasCancelled()) { - return; - } + dependencyAnalyzer.cleanup(); + inline(classSet, dependencyAnalyzer); + if (wasCancelled()) { + return; } optimize(classSet); @@ -498,9 +511,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } - boolean noCache = method.getAnnotations().get(NoCache.class.getName()) != null; - Program optimizedProgram = incremental && !noCache && programCache != null - ? programCache.get(method.getReference()) : null; + Program optimizedProgram = !cacheStatus.isStaleMethod(method.getReference()) + ? programCache.get(method.getReference(), cacheStatus) + : null; MethodOptimizationContextImpl context = new MethodOptimizationContextImpl(method, classSource); if (optimizedProgram == null) { optimizedProgram = ProgramUtils.copy(method.getProgram()); @@ -529,9 +542,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository { allocator.allocateRegisters(method, optimizedProgram); } } - if (incremental && programCache != null) { - programCache.store(method.getReference(), optimizedProgram); - } + + Program finalProgram = optimizedProgram; + programCache.store(method.getReference(), finalProgram, + () -> programDependencyExtractor.extractDependencies(finalProgram)); } method.setProgram(optimizedProgram); } @@ -642,6 +656,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return dependencyAnalyzer.getClassSource(); } + @Override + public CacheStatus getCacheStatus() { + return cacheStatus; + } + @Override public DependencyInfo getDependencyInfo() { return dependencyAnalyzer; @@ -662,11 +681,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return TeaVM.this; } - @Override - public boolean isIncremental() { - return incremental; - } - @Override public Map getEntryPoints() { return readonlyEntryPoints; @@ -684,7 +698,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { @Override public boolean isVirtual(MethodReference method) { - return incremental || virtualMethods == null || virtualMethods.contains(method); + return virtualMethods == null || virtualMethods.contains(method); } }; } diff --git a/core/src/main/java/org/teavm/vm/TeaVMBuilder.java b/core/src/main/java/org/teavm/vm/TeaVMBuilder.java index 576a5bdcf..6b4442449 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMBuilder.java +++ b/core/src/main/java/org/teavm/vm/TeaVMBuilder.java @@ -15,8 +15,9 @@ */ package org.teavm.vm; +import org.teavm.dependency.DependencyAnalyzerFactory; +import org.teavm.dependency.PreciseDependencyAnalyzer; import org.teavm.interop.PlatformMarker; -import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassReaderSource; import org.teavm.parsing.ClasspathClassHolderSource; @@ -24,6 +25,7 @@ public class TeaVMBuilder { TeaVMTarget target; ClassReaderSource classSource; ClassLoader classLoader; + DependencyAnalyzerFactory dependencyAnalyzerFactory = PreciseDependencyAnalyzer::new; public TeaVMBuilder(TeaVMTarget target) { this.target = target; @@ -35,7 +37,7 @@ public class TeaVMBuilder { return classSource; } - public TeaVMBuilder setClassSource(ClassHolderSource classSource) { + public TeaVMBuilder setClassSource(ClassReaderSource classSource) { this.classSource = classSource; return this; } @@ -49,6 +51,15 @@ public class TeaVMBuilder { return this; } + public DependencyAnalyzerFactory getDependencyAnalyzerFactory() { + return dependencyAnalyzerFactory; + } + + public TeaVMBuilder setDependencyAnalyzerFactory(DependencyAnalyzerFactory dependencyAnalyzerFactory) { + this.dependencyAnalyzerFactory = dependencyAnalyzerFactory; + return this; + } + public TeaVM build() { return new TeaVM(this); } diff --git a/core/src/main/java/org/teavm/vm/TeaVMTargetController.java b/core/src/main/java/org/teavm/vm/TeaVMTargetController.java index 88d1186ed..b78e906cf 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTargetController.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTargetController.java @@ -18,6 +18,7 @@ package org.teavm.vm; import java.util.Map; import java.util.Properties; import java.util.Set; +import org.teavm.cache.CacheStatus; import org.teavm.common.ServiceRepository; import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; @@ -31,6 +32,8 @@ public interface TeaVMTargetController { ClassReaderSource getUnprocessedClassSource(); + CacheStatus getCacheStatus(); + DependencyInfo getDependencyInfo(); Diagnostics getDiagnostics(); @@ -39,8 +42,6 @@ public interface TeaVMTargetController { ServiceRepository getServices(); - boolean isIncremental(); - boolean isFriendlyToDebugger(); Map getEntryPoints(); diff --git a/core/src/main/resources/org/teavm/backend/javascript/simpleThread.js b/core/src/main/resources/org/teavm/backend/javascript/simpleThread.js index 12803bade..13059653b 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/simpleThread.js +++ b/core/src/main/resources/org/teavm/backend/javascript/simpleThread.js @@ -24,8 +24,8 @@ function $rt_startThread(runner, callback) { } if (typeof callback !== 'undefined') { callback(result); - } else if (e instanceof Error) { - throw e; + } else if (result instanceof Error) { + throw result; } } function $rt_suspending() { diff --git a/extras-slf4j/src/main/java/org/teavm/extras/slf4j/LoggerFactoryTransformer.java b/extras-slf4j/src/main/java/org/teavm/extras/slf4j/LoggerFactoryTransformer.java index 8c8321bf8..9a6d9bb35 100644 --- a/extras-slf4j/src/main/java/org/teavm/extras/slf4j/LoggerFactoryTransformer.java +++ b/extras-slf4j/src/main/java/org/teavm/extras/slf4j/LoggerFactoryTransformer.java @@ -16,21 +16,28 @@ package org.teavm.extras.slf4j; import org.slf4j.LoggerFactory; -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.*; +import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.ClassReader; +import org.teavm.model.FieldHolder; +import org.teavm.model.FieldReader; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; import org.teavm.model.util.ModelUtils; public class LoggerFactoryTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (!cls.getName().equals(LoggerFactory.class.getName())) { return; } - substitute(cls, innerSource); + substitute(cls, context.getHierarchy()); } - private void substitute(ClassHolder cls, ClassReaderSource classSource) { - ClassReader subst = classSource.get(TeaVMLoggerFactorySubstitution.class.getName()); + private void substitute(ClassHolder cls, ClassHierarchy hierarchy) { + ClassReader subst = hierarchy.getClassSource().get(TeaVMLoggerFactorySubstitution.class.getName()); for (FieldHolder field : cls.getFields().toArray(new FieldHolder[0])) { cls.removeField(field); } diff --git a/html4j/src/main/java/org/teavm/html4j/JCLHacks.java b/html4j/src/main/java/org/teavm/html4j/JCLHacks.java index 65cb3cab2..52d711354 100644 --- a/html4j/src/main/java/org/teavm/html4j/JCLHacks.java +++ b/html4j/src/main/java/org/teavm/html4j/JCLHacks.java @@ -15,13 +15,24 @@ */ package org.teavm.html4j; -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.*; -import org.teavm.model.instructions.*; +import org.teavm.model.AccessLevel; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.RaiseInstruction; public class JCLHacks implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (cls.getName().equals("java.lang.Thread")) { installThreadMethods(cls); } diff --git a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java index b7af14cac..adb3544c7 100644 --- a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java +++ b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java @@ -63,7 +63,7 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { ClassReader cls = agent.getClassSource().get(className); if (cls != null && !cls.hasModifier(ElementModifier.ABSTRACT) && !cls.hasModifier(ElementModifier.INTERFACE)) { @@ -72,21 +72,14 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { - Set methodsToReach = reachedMethods.get(method.getReference()); - if (methodsToReach != null) { - for (MethodReference methodToReach : methodsToReach) { - agent.linkMethod(methodToReach, location); - } - return; - } + public void methodReached(DependencyAgent agent, MethodDependency method) { reachedMethods.put(method.getReference(), new HashSet<>()); if (method.isMissing()) { return; } AnnotationReader annot = method.getMethod().getAnnotations().get(JavaScriptBody.class.getName()); if (annot != null) { - includeDefaultDependencies(agent, location); + includeDefaultDependencies(agent); AnnotationValue javacall = annot.getValue("javacall"); if (method.getResult() != null) { allClassesNode.connect(method.getResult()); @@ -97,10 +90,10 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { for (int i = 0; i < method.getParameterCount(); ++i) { method.getVariable(i).connect(allClassesNode); method.getVariable(i).addConsumer(type -> { - if (agent.getClassSource().isSuperType("java.lang.Enum", type.getName()).orElse(false)) { + if (agent.getClassHierarchy().isSuperType("java.lang.Enum", type.getName(), false)) { MethodReference toStringMethod = new MethodReference(type.getName(), "toString", ValueType.parse(String.class)); - MethodDependency dependency = agent.linkMethod(toStringMethod, location); + MethodDependency dependency = agent.linkMethod(toStringMethod); dependency.getVariable(0).propagate(type); dependency.use(); } @@ -111,34 +104,34 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { } if (javacall != null && javacall.getBoolean()) { String body = annot.getValue("body").getString(); - new GeneratorJsCallback(agent, method, location).parse(body); + new GeneratorJsCallback(agent, method).parse(body); } } } - private void includeDefaultDependencies(DependencyAgent agent, CallLocation location) { - agent.linkMethod(JavaScriptConvGenerator.fromJsMethod, location).use(); - agent.linkMethod(JavaScriptConvGenerator.toJsMethod, location).use(); + private void includeDefaultDependencies(DependencyAgent agent) { + agent.linkMethod(JavaScriptConvGenerator.fromJsMethod).use(); + agent.linkMethod(JavaScriptConvGenerator.toJsMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.intValueMethod, location).propagate(0, Integer.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfIntMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.intValueMethod).propagate(0, Integer.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfIntMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.booleanValueMethod, location).propagate(0, Boolean.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfBooleanMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.booleanValueMethod).propagate(0, Boolean.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfBooleanMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.doubleValueMethod, location).propagate(0, Double.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfDoubleMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.doubleValueMethod).propagate(0, Double.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfDoubleMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.charValueMethod, location).propagate(0, Character.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfCharMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.charValueMethod).propagate(0, Character.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfCharMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.byteValueMethod, location).propagate(0, Byte.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfByteMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.byteValueMethod).propagate(0, Byte.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfByteMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.shortValueMethod, location).propagate(0, Short.class).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfShortMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.shortValueMethod).propagate(0, Short.class).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfShortMethod).use(); - agent.linkMethod(JavaScriptConvGenerator.valueOfLongMethod, location).use(); + agent.linkMethod(JavaScriptConvGenerator.valueOfLongMethod).use(); allClassesNode.propagate(agent.getType("java.lang.Integer")); allClassesNode.propagate(agent.getType("java.lang.Float")); @@ -149,12 +142,10 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { class GeneratorJsCallback extends JsCallback { private DependencyAgent agent; private MethodDependency caller; - private CallLocation location; - GeneratorJsCallback(DependencyAgent agent, MethodDependency caller, CallLocation location) { + GeneratorJsCallback(DependencyAgent agent, MethodDependency caller) { this.agent = agent; this.caller = caller; - this.location = location; } @Override @@ -164,11 +155,12 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { MethodReader reader = findMethod(agent.getClassSource(), fqn, desc); if (reader == null) { - agent.getDiagnostics().error(location, "Can't resolve method {{m0}}", methodRef); + agent.getDiagnostics().error(new CallLocation(caller.getReference()), "Can't resolve method {{m0}}", + methodRef); } methodRef = reader.getReference(); - MethodDependency methodDep = agent.linkMethod(methodRef, location); + MethodDependency methodDep = agent.linkMethod(methodRef); if (ident == null) { reachedMethods.get(caller.getReference()).add(methodRef); methodDep.use(); @@ -176,7 +168,7 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { allClassesNode.connect(methodDep.getVariable(i)); } } else { - allClassesNode.addConsumer(new VirtualCallbackConsumer(agent, methodRef, location)); + allClassesNode.addConsumer(new VirtualCallbackConsumer(agent, methodRef)); } return ""; @@ -186,17 +178,15 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { class VirtualCallbackConsumer implements DependencyConsumer { private DependencyAgent agent; private MethodReference superMethod; - private CallLocation location; - VirtualCallbackConsumer(DependencyAgent agent, MethodReference superMethod, CallLocation location) { + VirtualCallbackConsumer(DependencyAgent agent, MethodReference superMethod) { this.agent = agent; this.superMethod = superMethod; - this.location = location; } @Override public void consume(DependencyType type) { - if (!agent.getClassSource().isSuperType(superMethod.getClassName(), type.getName()).orElse(false)) { + if (!agent.getClassHierarchy().isSuperType(superMethod.getClassName(), type.getName(), false)) { return; } MethodReader method = agent.getClassSource().resolveImplementation(new MethodReference( @@ -205,7 +195,7 @@ public class JavaScriptBodyDependency extends AbstractDependencyListener { return; } virtualMethods.add(method.getReference()); - MethodDependency methodDep = agent.linkMethod(method.getReference(), location); + MethodDependency methodDep = agent.linkMethod(method.getReference()); methodDep.use(); for (int i = 0; i < methodDep.getParameterCount(); ++i) { allClassesNode.connect(methodDep.getVariable(i)); diff --git a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java index 56390240a..28f18e9ab 100644 --- a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java +++ b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java @@ -17,19 +17,18 @@ package org.teavm.html4j; import net.java.html.js.JavaScriptBody; import org.teavm.backend.javascript.spi.GeneratedBy; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationValue; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodHolder; import org.teavm.model.ValueType; public class JavaScriptBodyTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { if (method.getAnnotations().get(JavaScriptBody.class.getName()) != null) { AnnotationHolder genAnnot = new AnnotationHolder(GeneratedBy.class.getName()); diff --git a/html4j/src/main/java/org/teavm/html4j/JavaScriptObjectEnhancer.java b/html4j/src/main/java/org/teavm/html4j/JavaScriptObjectEnhancer.java index 534d66269..defac15b2 100644 --- a/html4j/src/main/java/org/teavm/html4j/JavaScriptObjectEnhancer.java +++ b/html4j/src/main/java/org/teavm/html4j/JavaScriptObjectEnhancer.java @@ -34,7 +34,7 @@ public class JavaScriptObjectEnhancer implements RendererListener { } @Override - public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager context, BuildTarget buildTarget) { classSource = context.getClassSource(); writer = context.getWriter(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index bc259f6eb..0c274f821 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -18,7 +18,6 @@ package org.teavm.jso.impl; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -31,7 +30,7 @@ import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstRoot; import org.mozilla.javascript.ast.FunctionNode; import org.teavm.backend.javascript.rendering.JSParser; -import org.teavm.cache.NoCache; +import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Sync; import org.teavm.jso.JSBody; @@ -81,13 +80,15 @@ class JSClassProcessor { private int methodIndexGenerator; private final Map overriddenMethodCache = new HashMap<>(); private JSValueMarshaller marshaller; + private IncrementalDependencyRegistration incrementalCache; JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository, - Diagnostics diagnostics) { + Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache) { this.classSource = classSource; this.typeHelper = typeHelper; this.repository = repository; this.diagnostics = diagnostics; + this.incrementalCache = incrementalCache; javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics); } @@ -151,7 +152,7 @@ class JSClassProcessor { MethodHolder callerMethod = new MethodHolder(new MethodDescriptor(method.getName() + "$static", staticSignature)); callerMethod.getModifiers().add(ElementModifier.STATIC); - final Program program = ProgramUtils.copy(method.getProgram()); + Program program = ProgramUtils.copy(method.getProgram()); program.createVariable(); InstructionVariableMapper variableMapper = new InstructionVariableMapper(var -> program.variableAt(var.getIndex() + 1)); @@ -264,7 +265,8 @@ class JSClassProcessor { } else if (insn instanceof InvokeInstruction) { InvokeInstruction invoke = (InvokeInstruction) insn; - MethodReader method = getMethod(invoke.getMethod()); + MethodReader method = getMethod(invoke.getMethod().getClassName(), + invoke.getMethod().getDescriptor()); if (method == null) { continue; } @@ -383,9 +385,7 @@ class JSClassProcessor { copyVar(result, invoke.getReceiver(), invoke.getLocation()); } - if (methodToProcess.getAnnotations().get(NoCache.class.getName()) == null) { - methodToProcess.getAnnotations().add(new AnnotationHolder(NoCache.class.getName())); - } + incrementalCache.addDependencies(methodToProcess.getReference(), method.getOwnerName()); return true; } @@ -498,9 +498,7 @@ class JSClassProcessor { Variable result = invoke.getReceiver() != null ? program.createVariable() : null; InvokeInstruction newInvoke = new InvokeInstruction(); - ValueType[] signature = new ValueType[method.parameterCount() + 3]; - Arrays.fill(signature, ValueType.object(JSObject.class.getName())); - newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature)); + newInvoke.setMethod(JSMethods.invoke(method.parameterCount())); newInvoke.setType(InvocationType.SPECIAL); newInvoke.setReceiver(result); newInvoke.getArguments().add(invoke.getInstance()); @@ -691,7 +689,7 @@ class JSClassProcessor { Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location); InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class)); + insn.setMethod(JSMethods.GET); insn.setReceiver(receiver); insn.getArguments().add(instance); insn.getArguments().add(nameVar); @@ -703,8 +701,7 @@ class JSClassProcessor { Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location); InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class, - JSObject.class, void.class)); + insn.setMethod(JSMethods.SET); insn.getArguments().add(instance); insn.getArguments().add(nameVar); insn.getArguments().add(value); @@ -715,7 +712,7 @@ class JSClassProcessor { private void addIndexerGet(Variable array, Variable index, Variable receiver, TextLocation location) { InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class)); + insn.setMethod(JSMethods.GET); insn.setReceiver(receiver); insn.getArguments().add(array); insn.getArguments().add(index); @@ -726,8 +723,7 @@ class JSClassProcessor { private void addIndexerSet(Variable array, Variable index, Variable value, TextLocation location) { InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class, - JSObject.class, void.class)); + insn.setMethod(JSMethods.SET); insn.getArguments().add(array); insn.getArguments().add(index); insn.getArguments().add(value); @@ -743,23 +739,23 @@ class JSClassProcessor { replacement.add(insn); } - private MethodReader getMethod(MethodReference ref) { - ClassReader cls = classSource.get(ref.getClassName()); + private MethodReader getMethod(String className, MethodDescriptor descriptor) { + ClassReader cls = classSource.get(className); if (cls == null) { return null; } - MethodReader method = cls.getMethod(ref.getDescriptor()); + MethodReader method = cls.getMethod(descriptor); if (method != null) { return method; } if (cls.getParent() != null && !cls.getParent().equals("java.lang.Object")) { - method = getMethod(new MethodReference(cls.getParent(), ref.getDescriptor())); + method = getMethod(cls.getParent(), descriptor); if (method != null) { return method; } } for (String iface : cls.getInterfaces()) { - method = getMethod(new MethodReference(iface, ref.getDescriptor())); + method = getMethod(iface, descriptor); if (method != null) { return method; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java index 934cf865c..b8874ccba 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java @@ -15,7 +15,6 @@ */ package org.teavm.jso.impl; -import java.util.HashSet; import java.util.Set; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; @@ -28,37 +27,29 @@ import org.teavm.model.MethodReference; class JSDependencyListener extends AbstractDependencyListener { private JSBodyRepository repository; - private Set reachedClasses = new HashSet<>(); - private Set reachedMethods = new HashSet<>(); JSDependencyListener(JSBodyRepository repository) { this.repository = repository; } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); - if (!reachedMethods.add(ref)) { - return; - } Set callbackMethods = repository.callbackMethods.get(ref); if (callbackMethods != null) { for (MethodReference callbackMethod : callbackMethods) { - agent.linkMethod(callbackMethod, new CallLocation(ref)).use(); + agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use(); } } } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { - if (!reachedClasses.add(className)) { - return; - } + public void classReached(DependencyAgent agent, String className) { ClassReader cls = agent.getClassSource().get(className); for (MethodReader method : cls.getMethods()) { AnnotationReader exposeAnnot = method.getAnnotations().get(JSMethodToExpose.class.getName()); if (exposeAnnot != null) { - MethodDependency methodDep = agent.linkMethod(method.getReference(), null); + MethodDependency methodDep = agent.linkMethod(method.getReference()); methodDep.getVariable(0).propagate(agent.getType(className)); methodDep.use(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSExceptionsDependencyListener.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSExceptionsDependencyListener.java index 60e84b743..8e7dbed41 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSExceptionsDependencyListener.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSExceptionsDependencyListener.java @@ -20,7 +20,6 @@ import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; import org.teavm.jso.JSExceptions; -import org.teavm.model.CallLocation; public class JSExceptionsDependencyListener extends AbstractDependencyListener { private DependencyNode allExceptions; @@ -31,7 +30,7 @@ public class JSExceptionsDependencyListener extends AbstractDependencyListener { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getReference().getClassName().equals(JSExceptions.class.getName())) { if (method.getReference().getName().equals("getJavaException")) { allExceptions.connect(method.getResult()); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java new file mode 100644 index 000000000..2d0f37f2a --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 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.jso.impl; + +import java.util.Arrays; +import java.util.function.Function; +import org.teavm.jso.JSObject; +import org.teavm.jso.core.JSArrayReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public final class JSMethods { + public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class, + JSObject.class, JSObject.class); + public static final MethodReference SET = new MethodReference(JS.class, "set", JSObject.class, JSObject.class, + JSObject.class, void.class); + public static final MethodReference FUNCTION = new MethodReference(JS.class, "function", JSObject.class, + JSObject.class, JSObject.class); + public static final MethodReference ARRAY_DATA = new MethodReference(JS.class, "arrayData", + Object.class, JSObject.class); + public static final MethodReference ARRAY_MAPPER = new MethodReference(JS.class, "arrayMapper", + Function.class, Function.class); + public static final MethodReference BOOLEAN_ARRAY_WRAPPER = new MethodReference(JS.class, "booleanArrayWrapper", + Function.class); + public static final MethodReference BYTE_ARRAY_WRAPPER = new MethodReference(JS.class, "byteArrayWrapper", + Function.class); + public static final MethodReference SHORT_ARRAY_WRAPPER = new MethodReference(JS.class, "shortArrayWrapper", + Function.class); + public static final MethodReference CHAR_ARRAY_WRAPPER = new MethodReference(JS.class, "charArrayWrapper", + Function.class); + public static final MethodReference INT_ARRAY_WRAPPER = new MethodReference(JS.class, "intArrayWrapper", + Function.class); + public static final MethodReference FLOAT_ARRAY_WRAPPER = new MethodReference(JS.class, "floatArrayWrapper", + Function.class); + public static final MethodReference DOUBLE_ARRAY_WRAPPER = new MethodReference(JS.class, "doubleArrayWrapper", + Function.class); + public static final MethodReference STRING_ARRAY_WRAPPER = new MethodReference(JS.class, "stringArrayWrapper", + Function.class); + public static final MethodReference ARRAY_WRAPPER = new MethodReference(JS.class, "arrayWrapper", + Function.class); + public static final MethodReference ARRAY_UNMAPPER = new MethodReference(JS.class, "arrayUnmapper", + Class.class, Function.class, Function.class); + public static final MethodReference UNMAP_ARRAY = new MethodReference(JS.class, "unmapArray", Class.class, + JSArrayReader.class, Function.class, Object[].class); + public static final MethodReference UNWRAP_BOOLEAN_ARRAY = new MethodReference(JS.class, "unwrapBooleanArray", + JSArrayReader.class, boolean[].class); + public static final MethodReference UNWRAP_BYTE_ARRAY = new MethodReference(JS.class, "unwrapByteArray", + JSArrayReader.class, byte[].class); + public static final MethodReference UNWRAP_SHORT_ARRAY = new MethodReference(JS.class, "unwrapShortArray", + JSArrayReader.class, short[].class); + public static final MethodReference UNWRAP_CHAR_ARRAY = new MethodReference(JS.class, "unwrapCharArray", + JSArrayReader.class, char[].class); + public static final MethodReference UNWRAP_INT_ARRAY = new MethodReference(JS.class, "unwrapIntArray", + JSArrayReader.class, int[].class); + public static final MethodReference UNWRAP_FLOAT_ARRAY = new MethodReference(JS.class, "unwrapFloatArray", + JSArrayReader.class, float[].class); + public static final MethodReference UNWRAP_DOUBLE_ARRAY = new MethodReference(JS.class, "unwrapDoubleArray", + JSArrayReader.class, double[].class); + public static final MethodReference UNWRAP_STRING_ARRAY = new MethodReference(JS.class, "unwrapStringArray", + JSArrayReader.class, String[].class); + public static final MethodReference UNWRAP_ARRAY = new MethodReference(JS.class, "unwrapArray", Class.class, + JSArrayReader.class, JSObject[].class); + public static final MethodReference BOOLEAN_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "booleanArrayUnwrapper", Function.class); + public static final MethodReference BYTE_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "byteArrayUnwrapper", Function.class); + public static final MethodReference SHORT_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "shortArrayUnwrapper", Function.class); + public static final MethodReference CHAR_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "charArrayUnwrapper", Function.class); + public static final MethodReference INT_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "intArrayUnwrapper", Function.class); + public static final MethodReference FLOAT_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "floatArrayUnwrapper", Function.class); + public static final MethodReference DOUBLE_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "doubleArrayUnwrapper", Function.class); + public static final MethodReference STRING_ARRAY_UNWRAPPER = new MethodReference(JS.class, + "stringArrayUnwrapper", Function.class); + public static final MethodReference ARRAY_UNWRAPPER = new MethodReference(JS.class, + "arrayUnwrapper", Class.class, Function.class); + + public static final MethodReference FUNCTION_AS_OBJECT = new MethodReference(JS.class, "functionAsObject", + JSObject.class, JSObject.class, JSObject.class); + + private static final ValueType JS_OBJECT = ValueType.object(JSObject.class.getName()); + private static final MethodReference[] INVOKE_METHODS = new MethodReference[13]; + + static { + for (int i = 0; i < INVOKE_METHODS.length; ++i) { + ValueType[] signature = new ValueType[i + 3]; + Arrays.fill(signature, JS_OBJECT); + INVOKE_METHODS[i] = new MethodReference(JS.class.getName(), "invoke", signature); + } + } + + private JSMethods() { + } + + public static MethodReference invoke(int parameterCount) { + return INVOKE_METHODS[parameterCount]; + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java index 63fe9de6d..7430664d2 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java @@ -17,6 +17,8 @@ package org.teavm.jso.impl; import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import org.teavm.ast.ConstantExpr; import org.teavm.ast.Expr; import org.teavm.ast.InvocationExpr; @@ -27,9 +29,9 @@ import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.MethodReader; @@ -37,6 +39,9 @@ import org.teavm.model.MethodReference; import org.teavm.model.ValueType; public class JSNativeGenerator implements Injector, DependencyPlugin, Generator { + private Set reachedFunctorMethods = new HashSet<>(); + private Set functorParamNodes = new HashSet<>(); + @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { @@ -182,14 +187,23 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator } @Override - public void methodReached(final DependencyAgent agent, final MethodDependency method, - final CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "invoke": case "instantiate": case "function": - for (int i = 0; i < method.getReference().parameterCount(); ++i) { - method.getVariable(i).addConsumer(type -> reachFunctorMethods(agent, type.getName(), method)); + if (reachedFunctorMethods.add(method.getReference()) && !method.isMissing()) { + for (int i = 0; i < method.getReference().parameterCount(); ++i) { + DependencyNode node = method.getVariable(i); + if (functorParamNodes.add(node)) { + node.addConsumer(type -> { + if (agent.getClassHierarchy().isSuperType(method.getMethod().getOwnerName(), + type.getName(), false)) { + reachFunctorMethods(agent, type.getName()); + } + }); + } + } } break; case "unwrapString": @@ -198,15 +212,12 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator } } - private void reachFunctorMethods(DependencyAgent agent, String type, MethodDependency caller) { - if (caller.isMissing()) { - return; - } + private void reachFunctorMethods(DependencyAgent agent, String type) { ClassReader cls = agent.getClassSource().get(type); if (cls != null) { for (MethodReader method : cls.getMethods()) { if (!method.hasModifier(ElementModifier.STATIC)) { - agent.linkMethod(method.getReference(), null).use(); + agent.linkMethod(method.getReference()).use(); } } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index 157103b26..b0a3ae458 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -31,10 +31,11 @@ import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.Instruction; @@ -53,7 +54,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { private JSClassProcessor processor; private JSBodyRepository repository; private JSTypeHelper typeHelper; - private ClassReaderSource innerSource; + private ClassHierarchy hierarchy; private Map exposedClasses = new HashMap<>(); JSObjectClassTransformer(JSBodyRepository repository) { @@ -61,11 +62,12 @@ class JSObjectClassTransformer implements ClassHolderTransformer { } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { - this.innerSource = innerSource; - if (processor == null || processor.getClassSource() != innerSource) { - typeHelper = new JSTypeHelper(innerSource); - processor = new JSClassProcessor(innerSource, typeHelper, repository, diagnostics); + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + this.hierarchy = context.getHierarchy(); + if (processor == null || processor.getClassSource() != hierarchy.getClassSource()) { + typeHelper = new JSTypeHelper(hierarchy.getClassSource()); + processor = new JSClassProcessor(hierarchy.getClassSource(), typeHelper, repository, + context.getDiagnostics(), context.getIncrementalCache()); } processor.processClass(cls); if (typeHelper.isJavaScriptClass(cls.getName())) { @@ -89,7 +91,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { } } - ClassReader originalClass = innerSource.get(cls.getName()); + ClassReader originalClass = hierarchy.getClassSource().get(cls.getName()); ExposedClass exposedClass; if (originalClass != null) { exposedClass = getExposedClass(cls.getName()); @@ -98,7 +100,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { createExposedClass(cls, exposedClass); } - exposeMethods(cls, exposedClass, diagnostics, functorMethod); + exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); } private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics, @@ -120,8 +122,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer { BasicBlock basicBlock = program.createBasicBlock(); List marshallInstructions = new ArrayList<>(); - JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, innerSource, program, - marshallInstructions); + JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, hierarchy.getClassSource(), + program, marshallInstructions); List variablesToPass = new ArrayList<>(); for (int i = 0; i < method.parameterCount(); ++i) { @@ -179,7 +181,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { } private ExposedClass createExposedClass(String name) { - ClassReader cls = innerSource.get(name); + ClassReader cls = hierarchy.getClassSource().get(name); ExposedClass exposedCls = new ExposedClass(); if (cls != null) { createExposedClass(cls, exposedCls); @@ -206,7 +208,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { if (exposedCls.implementedInterfaces.contains(ifaceName)) { continue; } - ClassReader iface = innerSource.get(ifaceName); + ClassReader iface = hierarchy.getClassSource().get(ifaceName); if (iface == null) { continue; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java index 6368b51d1..1a8396340 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java @@ -86,7 +86,7 @@ class JSValueMarshaller { Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation()); InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class)); + insn.setMethod(JSMethods.FUNCTION); insn.setReceiver(functor); insn.getArguments().add(var); insn.getArguments().add(nameVar); @@ -98,7 +98,7 @@ class JSValueMarshaller { Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) { if (byRef) { InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class)); + insn.setMethod(JSMethods.ARRAY_DATA); insn.setReceiver(program.createVariable()); insn.setType(InvocationType.SPECIAL); insn.getArguments().add(var); @@ -142,7 +142,7 @@ class JSValueMarshaller { while (--degree > 1) { insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class, "arrayMapper", Function.class, Function.class)); + insn.setMethod(JSMethods.ARRAY_MAPPER); insn.getArguments().add(function); function = program.createVariable(); insn.setReceiver(function); @@ -195,26 +195,26 @@ class JSValueMarshaller { if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { case BOOLEAN: - return new MethodReference(JS.class, "booleanArrayWrapper", Function.class); + return JSMethods.BOOLEAN_ARRAY_WRAPPER; case BYTE: - return new MethodReference(JS.class, "byteArrayWrapper", Function.class); + return JSMethods.BYTE_ARRAY_WRAPPER; case SHORT: - return new MethodReference(JS.class, "shortArrayWrapper", Function.class); + return JSMethods.SHORT_ARRAY_WRAPPER; case CHARACTER: - return new MethodReference(JS.class, "charArrayWrapper", Function.class); + return JSMethods.CHAR_ARRAY_WRAPPER; case INTEGER: - return new MethodReference(JS.class, "intArrayWrapper", Function.class); + return JSMethods.INT_ARRAY_WRAPPER; case FLOAT: - return new MethodReference(JS.class, "floatArrayWrapper", Function.class); + return JSMethods.FLOAT_ARRAY_WRAPPER; case DOUBLE: - return new MethodReference(JS.class, "doubleArrayWrapper", Function.class); + return JSMethods.DOUBLE_ARRAY_WRAPPER; default: break; } } else if (type.isObject(String.class)) { - return new MethodReference(JS.class, "stringArrayWrapper", Function.class); + return JSMethods.STRING_ARRAY_WRAPPER; } - return new MethodReference(JS.class, "arrayWrapper", Function.class); + return JSMethods.ARRAY_WRAPPER; } Variable unwrapReturnValue(CallLocation location, Variable var, ValueType type) { @@ -356,8 +356,7 @@ class JSValueMarshaller { replacement.add(clsInsn); insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class, "arrayUnmapper", Class.class, Function.class, - Function.class)); + insn.setMethod(JSMethods.ARRAY_UNMAPPER); insn.setType(InvocationType.SPECIAL); insn.getArguments().add(cls); insn.getArguments().add(function); @@ -374,8 +373,7 @@ class JSValueMarshaller { replacement.add(clsInsn); insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class, "unmapArray", Class.class, JSArrayReader.class, Function.class, - Object[].class)); + insn.setMethod(JSMethods.UNMAP_ARRAY); insn.getArguments().add(cls); insn.getArguments().add(var); insn.getArguments().add(function); @@ -391,52 +389,52 @@ class JSValueMarshaller { if (itemType instanceof ValueType.Primitive) { switch (((ValueType.Primitive) itemType).getKind()) { case BOOLEAN: - return new MethodReference(JS.class, "unwrapBooleanArray", JSArrayReader.class, boolean[].class); + return JSMethods.UNWRAP_BOOLEAN_ARRAY; case BYTE: - return new MethodReference(JS.class, "unwrapByteArray", JSArrayReader.class, byte[].class); + return JSMethods.UNWRAP_BYTE_ARRAY; case SHORT: - return new MethodReference(JS.class, "unwrapShortArray", JSArrayReader.class, short[].class); + return JSMethods.UNWRAP_SHORT_ARRAY; case CHARACTER: - return new MethodReference(JS.class, "unwrapCharArray", JSArrayReader.class, char[].class); + return JSMethods.UNWRAP_CHAR_ARRAY; case INTEGER: - return new MethodReference(JS.class, "unwrapIntArray", JSArrayReader.class, int[].class); + return JSMethods.UNWRAP_INT_ARRAY; case FLOAT: - return new MethodReference(JS.class, "unwrapFloatArray", JSArrayReader.class, float[].class); + return JSMethods.UNWRAP_FLOAT_ARRAY; case DOUBLE: - return new MethodReference(JS.class, "unwrapDoubleArray", JSArrayReader.class, double[].class); + return JSMethods.UNWRAP_DOUBLE_ARRAY; default: break; } } else if (itemType.isObject(String.class)) { - return new MethodReference(JS.class, "unwrapStringArray", JSArrayReader.class, String[].class); + return JSMethods.UNWRAP_STRING_ARRAY; } - return new MethodReference(JS.class, "unwrapArray", Class.class, JSArrayReader.class, JSObject[].class); + return JSMethods.UNWRAP_ARRAY; } private MethodReference multipleDimensionArrayUnwrapper(ValueType itemType) { if (itemType instanceof ValueType.Primitive) { switch (((ValueType.Primitive) itemType).getKind()) { case BOOLEAN: - return new MethodReference(JS.class, "booleanArrayUnwrapper", Function.class); + return JSMethods.BOOLEAN_ARRAY_UNWRAPPER; case BYTE: - return new MethodReference(JS.class, "byteArrayUnwrapper", Function.class); + return JSMethods.BYTE_ARRAY_UNWRAPPER; case SHORT: - return new MethodReference(JS.class, "shortArrayUnwrapper", Function.class); + return JSMethods.SHORT_ARRAY_UNWRAPPER; case CHARACTER: - return new MethodReference(JS.class, "charArrayUnwrapper", Function.class); + return JSMethods.CHAR_ARRAY_UNWRAPPER; case INTEGER: - return new MethodReference(JS.class, "intArrayUnwrapper", Function.class); + return JSMethods.INT_ARRAY_UNWRAPPER; case FLOAT: - return new MethodReference(JS.class, "floatArrayUnwrapper", Function.class); + return JSMethods.FLOAT_ARRAY_UNWRAPPER; case DOUBLE: - return new MethodReference(JS.class, "doubleArrayUnwrapper", Function.class); + return JSMethods.DOUBLE_ARRAY_UNWRAPPER; default: break; } } else if (itemType.isObject(String.class)) { - return new MethodReference(JS.class, "stringArrayUnwrapper", Function.class); + return JSMethods.STRING_ARRAY_UNWRAPPER; } - return new MethodReference(JS.class, "arrayUnwrapper", Class.class, Function.class); + return JSMethods.ARRAY_UNWRAPPER; } private Variable unwrap(Variable var, String methodName, ValueType argType, ValueType resultType, @@ -474,8 +472,7 @@ class JSValueMarshaller { Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation()); InvokeInstruction insn = new InvokeInstruction(); insn.setType(InvocationType.SPECIAL); - insn.setMethod(new MethodReference(JS.class, "functionAsObject", JSObject.class, JSObject.class, - JSObject.class)); + insn.setMethod(JSMethods.FUNCTION_AS_OBJECT); insn.setReceiver(functor); insn.getArguments().add(var); insn.getArguments().add(nameVar); diff --git a/metaprogramming/api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java b/metaprogramming/api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java index 352e232e1..77dbb4c8e 100644 --- a/metaprogramming/api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java +++ b/metaprogramming/api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java @@ -99,6 +99,10 @@ public final class Metaprogramming { return null; } + public static void unsupportedCase() { + unsupported(); + } + private static void unsupported() { throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " + "environment"); diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java index 359a305d6..d84eb1186 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingDependencyListener.java @@ -26,7 +26,8 @@ import org.teavm.metaprogramming.impl.model.MethodDescriber; import org.teavm.metaprogramming.impl.model.MethodModel; import org.teavm.metaprogramming.impl.optimization.Optimizations; import org.teavm.metaprogramming.impl.reflect.ReflectContext; -import org.teavm.model.CallLocation; +import org.teavm.model.ClassReader; +import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.emit.ProgramEmitter; @@ -45,22 +46,32 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene MetaprogrammingImpl.classLoader = proxyClassLoader; MetaprogrammingImpl.classSource = agent.getClassSource(); + MetaprogrammingImpl.incrementaDependencies = agent.getIncrementalCache(); MetaprogrammingImpl.agent = agent; - MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassSource(), proxyClassLoader); + MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassHierarchy(), proxyClassLoader); } @Override - public void methodReached(DependencyAgent agent, MethodDependency methodDep, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency methodDep) { MethodModel proxy = describer.getMethod(methodDep.getReference()); if (proxy != null && installedProxies.add(proxy)) { - new UsageGenerator(agent, proxy, methodDep, location, proxyClassLoader).installProxyEmitter(); + agent.getIncrementalCache().setNoCache(methodDep.getReference()); + ClassReader cls = agent.getClassSource().get(methodDep.getMethod().getOwnerName()); + int index = 0; + for (MethodReader method : cls.getMethods()) { + if (method.getDescriptor().equals(methodDep.getMethod().getDescriptor())) { + break; + } + ++index; + } + new UsageGenerator(agent, proxy, methodDep, proxyClassLoader, index).installProxyEmitter(); } } @Override public void completing(DependencyAgent agent) { for (MethodModel model : describer.getKnownMethods()) { - ProgramEmitter pe = ProgramEmitter.create(model.getMethod().getDescriptor(), agent.getClassSource()); + ProgramEmitter pe = ProgramEmitter.create(model.getMethod().getDescriptor(), agent.getClassHierarchy()); ValueEmitter[] paramVars = new ValueEmitter[model.getMetaParameterCount()]; int offset = model.isStatic() ? 1 : 0; @@ -69,7 +80,7 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene } if (model.getUsages().size() == 1) { - emitSingleUsage(model, pe, agent, paramVars); + emitSingleUsage(model, pe, paramVars); } else if (model.getUsages().isEmpty()) { if (model.getMethod().getReturnType() == ValueType.VOID) { pe.exit(); @@ -79,11 +90,12 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene } else { emitMultipleUsage(model, pe, agent, paramVars); } + + agent.submitMethod(model.getMethod(), new Optimizations().apply(pe.getProgram(), model.getMethod())); } } - private void emitSingleUsage(MethodModel model, ProgramEmitter pe, DependencyAgent agent, - ValueEmitter[] paramVars) { + private void emitSingleUsage(MethodModel model, ProgramEmitter pe, ValueEmitter[] paramVars) { MethodReference usage = model.getUsages().values().stream().findFirst().orElse(null); ValueEmitter result = pe.invoke(usage, paramVars); if (usage.getReturnType() == ValueType.VOID) { @@ -92,8 +104,6 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene assert result != null : "Expected non-null result at " + model.getMethod(); result.returnValue(); } - - agent.submitMethod(model.getMethod(), new Optimizations().apply(pe.getProgram(), model.getMethod())); } private void emitMultipleUsage(MethodModel model, ProgramEmitter pe, DependencyAgent agent, @@ -131,7 +141,5 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene pe.constantNull(Object.class).returnValue(); } }); - - agent.submitMethod(model.getMethod(), new Optimizations().apply(pe.getProgram(), model.getMethod())); } } diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java index e18c825a9..54c005448 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java @@ -17,6 +17,7 @@ package org.teavm.metaprogramming.impl; import java.util.HashMap; import java.util.Map; +import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.dependency.DependencyAgent; import org.teavm.metaprogramming.Action; import org.teavm.metaprogramming.Computation; @@ -58,20 +59,22 @@ import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.util.TransitionExtractor; public final class MetaprogrammingImpl { + static String suffix; static Map proxySuffixGenerators = new HashMap<>(); static ClassLoader classLoader; static ClassReaderSource classSource; + static IncrementalDependencyRegistration incrementaDependencies; static ReflectContext reflectContext; static DependencyAgent agent; static VariableContext varContext; static MethodReference templateMethod; static CompositeMethodGenerator generator; static ValueType returnType; + static boolean unsupportedCase; private MetaprogrammingImpl() { } - @SuppressWarnings("WeakerAccess") public static Value emit(Computation computation) { if (computation instanceof ValueImpl) { @SuppressWarnings("unchecked") @@ -109,7 +112,7 @@ public final class MetaprogrammingImpl { return new LazyValueImpl<>(varContext, computation, type, generator.forcedLocation); } - @SuppressWarnings({"WeakerAccess", "SameParameterValue"}) + @SuppressWarnings("SameParameterValue") public static void exit(Computation value) { if (value == null) { returnValue(null); @@ -315,14 +318,15 @@ public final class MetaprogrammingImpl { ValueImpl result = new ValueImpl<>(nestedVarContext.createInstance(generator), varContext, innerType); + incrementaDependencies.setNoCache(cls.getName()); agent.submitClass(cls); return result; } private static String createProxyName(String className) { - int suffix = proxySuffixGenerators.getOrDefault(className, 0); - proxySuffixGenerators.put(className, suffix + 1); - return className + "$proxy" + suffix; + int ownSuffix = proxySuffixGenerators.getOrDefault(className, 0); + proxySuffixGenerators.put(className, ownSuffix + 1); + return className + "$proxy$" + suffix + "_" + ownSuffix; } private static void returnValue(Variable var) { @@ -335,10 +339,6 @@ public final class MetaprogrammingImpl { return diagnostics; } - public void submitClass(ClassHolder cls) { - agent.submitClass(cls); - } - public static void close() { TransitionExtractor transitionExtractor = new TransitionExtractor(); BasicBlock block = generator.currentBlock(); @@ -395,9 +395,8 @@ public final class MetaprogrammingImpl { returnValue(var); } - private static void unsupported() { - throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " - + "environment"); + public static void unsupportedCase() { + unsupportedCase = true; } private static Diagnostics diagnostics = new Diagnostics() { diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java index 345fcd82c..2b89bfd5d 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/UsageGenerator.java @@ -20,8 +20,6 @@ import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Map; -import java.util.WeakHashMap; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.MethodDependency; import org.teavm.diagnostics.Diagnostics; @@ -41,7 +39,8 @@ import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; class UsageGenerator { - private static Map suffixGenerator = new WeakHashMap<>(); + private int suffix; + private int ownSuffix; private DependencyAgent agent; private MethodModel model; private MethodDependency methodDep; @@ -52,14 +51,15 @@ class UsageGenerator { private boolean annotationErrorReported; private MethodDependency nameDependency; - UsageGenerator(DependencyAgent agent, MethodModel model, MethodDependency methodDep, CallLocation location, - MetaprogrammingClassLoader classLoader) { + UsageGenerator(DependencyAgent agent, MethodModel model, MethodDependency methodDep, + MetaprogrammingClassLoader classLoader, int suffix) { this.agent = agent; this.diagnostics = agent.getDiagnostics(); this.model = model; this.methodDep = methodDep; - this.location = location; + this.location = new CallLocation(methodDep.getReference()); this.classLoader = classLoader; + this.suffix = suffix; } void installProxyEmitter() { @@ -88,28 +88,29 @@ class UsageGenerator { } private MethodDependency installAdditionalDependencies() { - MethodDependency nameDep = agent.linkMethod(new MethodReference(Class.class, "getName", String.class), - location); + MethodDependency nameDep = agent.linkMethod(new MethodReference(Class.class, "getName", String.class)); + nameDep.addLocation(location); nameDep.getVariable(0).propagate(agent.getType(Class.class.getName())); nameDep.getThrown().connect(methodDep.getThrown()); nameDep.use(); MethodDependency equalsDep = agent.linkMethod(new MethodReference(String.class, "equals", Object.class, - boolean.class), location); + boolean.class)); + equalsDep.addLocation(location); nameDep.getResult().connect(equalsDep.getVariable(0)); equalsDep.getVariable(1).propagate(agent.getType("java.lang.String")); equalsDep.getThrown().connect(methodDep.getThrown()); equalsDep.use(); - MethodDependency hashCodeDep = agent.linkMethod(new MethodReference(String.class, "hashCode", int.class), - location); + MethodDependency hashCodeDep = agent.linkMethod(new MethodReference(String.class, "hashCode", int.class)); + hashCodeDep.addLocation(location); hashCodeDep.getVariable(0).propagate(agent.getType("java.lang.String")); nameDep.getResult().connect(hashCodeDep.getVariable(0)); hashCodeDep.getThrown().connect(methodDep.getThrown()); hashCodeDep.use(); - agent.linkMethod(new MethodReference(Object.class, "hashCode", int.class), null); - agent.linkMethod(new MethodReference(Object.class, "equals", Object.class, boolean.class), null); + agent.linkMethod(new MethodReference(Object.class, "hashCode", int.class)); + agent.linkMethod(new MethodReference(Object.class, "equals", Object.class, boolean.class)); return nameDep; } @@ -127,14 +128,16 @@ class UsageGenerator { return; } - implRef = buildMethodReference(); - model.getUsages().put(type, implRef); + String suffix = getSuffix(); + implRef = buildMethodReference(suffix); MetaprogrammingImpl.templateMethod = model.getMetaMethod(); VariableContext varContext = new TopLevelVariableContext(diagnostics); MetaprogrammingImpl.generator = new CompositeMethodGenerator(varContext); MetaprogrammingImpl.varContext = varContext; MetaprogrammingImpl.returnType = model.getMethod().getReturnType(); MetaprogrammingImpl.generator.location = location != null ? location.getSourceLocation() : null; + MetaprogrammingImpl.proxySuffixGenerators.clear(); + MetaprogrammingImpl.suffix = suffix; for (int i = 0; i <= model.getMetaParameterCount(); ++i) { MetaprogrammingImpl.generator.getProgram().createVariable(); @@ -150,23 +153,10 @@ class UsageGenerator { } } + MetaprogrammingImpl.unsupportedCase = false; + try { proxyMethod.invoke(null, proxyArgs); - MetaprogrammingImpl.close(); - Program program = MetaprogrammingImpl.generator.getProgram(); - //new BoxingEliminator().optimize(program); - - ClassHolder cls = new ClassHolder(implRef.getClassName()); - cls.setLevel(AccessLevel.PUBLIC); - cls.setParent("java.lang.Object"); - - MethodHolder method = new MethodHolder(implRef.getDescriptor()); - method.setLevel(AccessLevel.PUBLIC); - method.getModifiers().add(ElementModifier.STATIC); - method.setProgram(program); - cls.addMethod(method); - - agent.submitClass(cls); } catch (IllegalAccessException | InvocationTargetException e) { StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); @@ -174,7 +164,30 @@ class UsageGenerator { model.getMetaMethod()); } - MethodDependency implMethod = agent.linkMethod(implRef, location); + MetaprogrammingImpl.close(); + if (MetaprogrammingImpl.unsupportedCase) { + return; + } + + model.getUsages().put(type, implRef); + Program program = MetaprogrammingImpl.generator.getProgram(); + //new BoxingEliminator().optimize(program); + + ClassHolder cls = new ClassHolder(implRef.getClassName()); + cls.setLevel(AccessLevel.PUBLIC); + cls.setParent("java.lang.Object"); + + MethodHolder method = new MethodHolder(implRef.getDescriptor()); + method.setLevel(AccessLevel.PUBLIC); + method.getModifiers().add(ElementModifier.STATIC); + method.setProgram(program); + cls.addMethod(method); + + agent.submitClass(cls); + agent.getIncrementalCache().setNoCache(cls.getName()); + + MethodDependency implMethod = agent.linkMethod(implRef); + implMethod.addLocation(location); for (int i = 0; i < implRef.parameterCount(); ++i) { methodDep.getVariable(i + 1).connect(implMethod.getVariable(i + 1)); } @@ -190,7 +203,7 @@ class UsageGenerator { implMethod.getThrown().connect(methodDep.getThrown()); implMethod.use(); - agent.linkClass(implRef.getClassName(), location); + agent.linkClass(implRef.getClassName()); } private ValueType findClass(String name) { @@ -216,9 +229,9 @@ class UsageGenerator { } } - private MethodReference buildMethodReference() { + private MethodReference buildMethodReference(String suffix) { if (model.getClassParameterIndex() < 0) { - return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(), + return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + suffix, model.getMethod().getDescriptor()); } @@ -229,14 +242,12 @@ class UsageGenerator { } signature[i] = model.getMethod().getReturnType(); - return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(), + return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + suffix, model.getMethod().getName(), signature); } - private int getSuffix() { - int suffix = suffixGenerator.getOrDefault(agent, 0); - suffixGenerator.put(agent, suffix + 1); - return suffix; + private String getSuffix() { + return suffix + "_" + ownSuffix++; } private Method getJavaMethod(ClassLoader classLoader, MethodReference ref) throws ReflectiveOperationException { diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java index b8fe1f3c2..651d3470e 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/model/MethodDescriber.java @@ -15,8 +15,11 @@ */ package org.teavm.metaprogramming.impl.model; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import org.teavm.diagnostics.Diagnostics; import org.teavm.metaprogramming.Meta; import org.teavm.metaprogramming.ReflectClass; @@ -32,7 +35,8 @@ import org.teavm.model.ValueType; public class MethodDescriber { private Diagnostics diagnostics; private ClassReaderSource classSource; - private Map cache = new HashMap<>(); + private Map> cache = new HashMap<>(); + private List knownMethods = new ArrayList<>(); public MethodDescriber(Diagnostics diagnostics, ClassReaderSource classSource) { this.diagnostics = diagnostics; @@ -40,19 +44,25 @@ public class MethodDescriber { } public MethodModel getMethod(MethodReference method) { - return cache.computeIfAbsent(method, this::describeMethod); - } - - public MethodModel getKnownMethod(MethodReference method) { - return cache.get(method); + return cache.computeIfAbsent(method, k -> { + MethodModel model = describeMethod(k); + if (model != null) { + knownMethods.add(model); + } + return Optional.ofNullable(model); + }).orElse(null); } public Iterable getKnownMethods() { - return cache.values(); + return knownMethods; } private MethodModel describeMethod(MethodReference methodRef) { - MethodReader method = classSource.resolve(methodRef); + ClassReader cls = classSource.get(methodRef.getClassName()); + if (cls == null) { + return null; + } + MethodReader method = cls.getMethod(methodRef.getDescriptor()); if (method == null) { return null; } diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java index 012dd1896..5169c878b 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/AnnotationProxy.java @@ -25,8 +25,8 @@ import java.util.List; import java.util.Map; import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; @@ -34,25 +34,25 @@ import org.teavm.model.ValueType; class AnnotationProxy implements InvocationHandler { private ClassLoader classLoader; - private ClassReaderSource classSource; + private ClassHierarchy hierarchy; private AnnotationReader reader; private Class annotationType; private Map cache = new HashMap<>(); - AnnotationProxy(ClassLoader classLoader, ClassReaderSource classSource, AnnotationReader reader, + AnnotationProxy(ClassLoader classLoader, ClassHierarchy hierarchy, AnnotationReader reader, Class annotationType) { this.classLoader = classLoader; - this.classSource = classSource; + this.hierarchy = hierarchy; this.reader = reader; this.annotationType = annotationType; } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public Object invoke(Object proxy, Method method, Object[] args) { if (method.getName().equals("annotationType")) { return annotationType; } else { - ClassReader cls = classSource.get(reader.getType()); + ClassReader cls = hierarchy.getClassSource().get(reader.getType()); return cache.computeIfAbsent(method.getName(), name -> { MethodDescriptor desc = new MethodDescriptor(name, ValueType.parse(method.getReturnType())); MethodReader methodReader = cls.getMethod(desc); @@ -102,13 +102,13 @@ class AnnotationProxy implements InvocationHandler { return result; } else if (type.isObject(Class.class)) { return convertClass(value.getJavaClass()); - } else if (classSource.isSuperType(ValueType.parse(Enum.class), type).orElse(false)) { + } else if (hierarchy.isSuperType(ValueType.parse(Enum.class), type, false)) { FieldReference fieldRef = value.getEnumValue(); Class enumClass = Class.forName(fieldRef.getClassName(), true, classLoader); return enumClass.getField(fieldRef.getFieldName()).get(null); - } else if (classSource.isSuperType(ValueType.parse(Annotation.class), type).orElse(false)) { + } else if (hierarchy.isSuperType(ValueType.parse(Annotation.class), type, false)) { Class annotType = convertClass(type); - AnnotationProxy handler = new AnnotationProxy(classLoader, classSource, value.getAnnotation(), annotType); + AnnotationProxy handler = new AnnotationProxy(classLoader, hierarchy, value.getAnnotation(), annotType); return Proxy.newProxyInstance(classLoader, new Class[] { annotType }, handler); } diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java index fa4876920..4a26fae69 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectAnnotatedElementImpl.java @@ -45,7 +45,7 @@ public class ReflectAnnotatedElementImpl implements ReflectAnnotatedElement { return null; } - AnnotationProxy handler = new AnnotationProxy(context.getClassLoader(), context.getClassSource(), + AnnotationProxy handler = new AnnotationProxy(context.getClassLoader(), context.getHierarchy(), annot, t); return (Annotation) Proxy.newProxyInstance(context.getClassLoader(), new Class[] { t }, handler); }); diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java index 3c0229c9e..39e985d28 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectClassImpl.java @@ -265,9 +265,8 @@ public class ReflectClassImpl implements ReflectClass { if (candidate == null) { candidate = method; } else { - boolean moreSpecial = context.getClassSource() - .isSuperType(candidate.getResultType(), method.getResultType()) - .orElse(false); + boolean moreSpecial = context.getHierarchy().isSuperType(candidate.getResultType(), + method.getResultType(), false); if (moreSpecial) { candidate = method; } diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java index 27b6a7e60..99b282c13 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/reflect/ReflectContext.java @@ -19,6 +19,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.ElementReader; @@ -26,11 +27,13 @@ import org.teavm.model.ValueType; public class ReflectContext { private ClassReaderSource classSource; + private ClassHierarchy hierarchy; private Map> classes = new HashMap<>(); private ClassLoader classLoader; - public ReflectContext(ClassReaderSource classSource, ClassLoader classLoader) { - this.classSource = classSource; + public ReflectContext(ClassHierarchy hierarchy, ClassLoader classLoader) { + this.classSource = hierarchy.getClassSource(); + this.hierarchy = hierarchy; this.classLoader = classLoader; } @@ -42,6 +45,10 @@ public class ReflectContext { return classSource; } + public ClassHierarchy getHierarchy() { + return hierarchy; + } + public ReflectClassImpl getClass(ValueType type) { return classes.computeIfAbsent(type, t -> new ReflectClassImpl<>(type, this)); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AnnotationDependencySupport.java b/platform/src/main/java/org/teavm/platform/plugin/AnnotationDependencySupport.java index 035793da7..119a289f2 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AnnotationDependencySupport.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AnnotationDependencySupport.java @@ -20,7 +20,6 @@ import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.platform.Platform; @@ -28,6 +27,7 @@ import org.teavm.platform.PlatformAnnotationProvider; public class AnnotationDependencySupport extends AbstractDependencyListener { private DependencyNode allClasses; + private MethodDependency getAnnotationsDep; @Override public void started(DependencyAgent agent) { @@ -35,29 +35,39 @@ public class AnnotationDependencySupport extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { allClasses.propagate(agent.getType(className)); } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getReference().getClassName().equals(Platform.class.getName()) && method.getReference().getName().equals("getAnnotations")) { method.getResult().propagate(agent.getType("[" + ValueType.parse(Annotation.class).toString())); - agent.linkMethod(new MethodReference(PlatformAnnotationProvider.class, "getAnnotations", - Annotation[].class), location); + if (getAnnotationsDep == null) { + getAnnotationsDep = agent.linkMethod(new MethodReference(PlatformAnnotationProvider.class, + "getAnnotations", Annotation[].class)); + } + method.addLocationListener(getAnnotationsDep::addLocation); + allClasses.addConsumer(type -> { if (type.getName().endsWith("$$__annotations__$$")) { return; } String className = type.getName() + "$$__annotations__$$"; - agent.linkMethod(new MethodReference(className, "", ValueType.VOID), location) - .propagate(0, className) - .use(); + MethodDependency initMethod = agent.linkMethod(new MethodReference(className, "", + ValueType.VOID)); + initMethod.propagate(0, className); + initMethod.use(); MethodDependency readMethod = agent.linkMethod(new MethodReference(className, - "getAnnotations", ValueType.parse(Annotation[].class)), location); + "getAnnotations", ValueType.parse(Annotation[].class))); readMethod.getResult().getArrayItem().connect(method.getResult().getArrayItem()); readMethod.use(); + + method.addLocationListener(location -> { + initMethod.addLocation(location); + readMethod.addLocation(location); + }); }); } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncDependencyListener.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncDependencyListener.java index 87d80eab3..7017448d2 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncDependencyListener.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncDependencyListener.java @@ -19,13 +19,12 @@ import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.MethodDependency; import org.teavm.interop.Async; -import org.teavm.model.CallLocation; public class AsyncDependencyListener extends AbstractDependencyListener { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getMethod() != null && method.getMethod().getAnnotations().get(Async.class.getName()) != null) { - new AsyncMethodGenerator().methodReached(agent, method, location); + new AsyncMethodGenerator().methodReached(agent, method); } } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java index 1e1ad40a9..9a653740f 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java @@ -24,7 +24,6 @@ import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.MethodReader; @@ -96,10 +95,11 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); MethodReference asyncRef = getAsyncReference(ref); - MethodDependency asyncMethod = agent.linkMethod(asyncRef, location); + MethodDependency asyncMethod = agent.linkMethod(asyncRef); + method.addLocationListener(asyncMethod::addLocation); int paramCount = ref.parameterCount(); for (int i = 0; i <= paramCount; ++i) { method.getVariable(i).connect(asyncMethod.getVariable(i)); @@ -107,20 +107,20 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua asyncMethod.getVariable(paramCount + 1).propagate(agent.getType(AsyncCallbackWrapper.class.getName())); MethodDependency completeMethod = agent.linkMethod( - new MethodReference(AsyncCallbackWrapper.class, "complete", Object.class, void.class), null); + new MethodReference(AsyncCallbackWrapper.class, "complete", Object.class, void.class)); if (method.getResult() != null) { - completeMethod.getVariable(1).connect(method.getResult(), type -> agent.getClassSource() - .isSuperType(ref.getReturnType(), ValueType.object(type.getName())).orElse(false)); + completeMethod.getVariable(1).connect(method.getResult(), type -> agent.getClassHierarchy() + .isSuperType(ref.getReturnType(), ValueType.object(type.getName()), false)); } completeMethod.use(); MethodDependency errorMethod = agent.linkMethod(new MethodReference(AsyncCallbackWrapper.class, "error", - Throwable.class, void.class), null); + Throwable.class, void.class)); errorMethod.getVariable(1).connect(method.getThrown()); errorMethod.use(); agent.linkMethod(new MethodReference(AsyncCallbackWrapper.class, "create", - AsyncCallback.class, AsyncCallbackWrapper.class), null).use(); + AsyncCallback.class, AsyncCallbackWrapper.class)).use(); asyncMethod.use(); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java index 095c72221..31d65b88d 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java @@ -16,12 +16,11 @@ package org.teavm.platform.plugin; import org.teavm.backend.javascript.spi.GeneratedBy; -import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Async; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; @@ -30,7 +29,7 @@ import org.teavm.platform.async.AsyncCallback; public class AsyncMethodProcessor implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { if (method.hasModifier(ElementModifier.NATIVE) && method.getAnnotations().get(Async.class.getName()) != null @@ -46,8 +45,8 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { if (asyncMethod != null) { if (asyncMethod.hasModifier(ElementModifier.STATIC) != method.hasModifier(ElementModifier.STATIC)) { - diagnostics.error(new CallLocation(method.getReference()), "Methods {{m0}} and {{m1}} must " - + "both be either static or non-static", + context.getDiagnostics().error(new CallLocation(method.getReference()), + "Methods {{m0}} and {{m1}} must both be either static or non-static", method.getReference(), asyncMethod.getReference()); } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ClassLookupDependencySupport.java b/platform/src/main/java/org/teavm/platform/plugin/ClassLookupDependencySupport.java index cc2a1642b..ef76d4358 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ClassLookupDependencySupport.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ClassLookupDependencySupport.java @@ -28,12 +28,12 @@ public class ClassLookupDependencySupport extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { allClasses.propagate(agent.getType(className)); } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); if (ref.getClassName().equals(Platform.class.getName()) && ref.getName().equals("lookupClass")) { allClasses.addConsumer(type -> { @@ -44,7 +44,9 @@ public class ClassLookupDependencySupport extends AbstractDependencyListener { MethodReader initMethod = cls.getMethod(new MethodDescriptor("", void.class)); if (initMethod != null) { - agent.linkMethod(initMethod.getReference(), location).use(); + MethodDependency initDep = agent.linkMethod(initMethod.getReference()); + method.addLocationListener(initDep::addLocation); + initDep.use(); } }); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/EnumDependencySupport.java b/platform/src/main/java/org/teavm/platform/plugin/EnumDependencySupport.java index 55b497632..11ef489d7 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/EnumDependencySupport.java +++ b/platform/src/main/java/org/teavm/platform/plugin/EnumDependencySupport.java @@ -36,7 +36,7 @@ public class EnumDependencySupport extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { ClassReader cls = agent.getClassSource().get(className); if (cls == null || cls.getParent() == null || !cls.getParent().equals("java.lang.Enum")) { return; @@ -45,7 +45,7 @@ public class EnumDependencySupport extends AbstractDependencyListener { } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getReference().getClassName().equals(Platform.class.getName()) && method.getReference().getName().equals("getEnumConstants")) { allEnums.connect(method.getResult().getArrayItem()); @@ -55,12 +55,14 @@ public class EnumDependencySupport extends AbstractDependencyListener { MethodReader valuesMethod = cls.getMethod(new MethodDescriptor("values", ValueType.arrayOf(ValueType.object(cls.getName())))); if (valuesMethod != null) { - agent.linkMethod(valuesMethod.getReference(), new CallLocation(ref)).use(); + MethodDependency valuesDep = agent.linkMethod(valuesMethod.getReference()); + valuesDep.addLocation(new CallLocation(ref)); + valuesDep.use(); } }); - method.getResult().propagate(agent.getType("[java.lang.Enum")); + method.getResult().propagate(agent.getType("[Ljava/lang/Enum;")); for (String cls : agent.getReachableClasses()) { - classReached(agent, cls, location); + classReached(agent, cls); } } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java index 8b48535e9..b7fa3ce28 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java @@ -16,16 +16,16 @@ package org.teavm.platform.plugin; import org.teavm.backend.javascript.spi.GeneratedBy; -import org.teavm.cache.NoCache; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AccessLevel; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.MethodHolder; @@ -37,21 +37,20 @@ import org.teavm.platform.metadata.ClassScopedMetadataProvider; import org.teavm.platform.metadata.MetadataProvider; class MetadataProviderTransformer implements ClassHolderTransformer { - static int fieldIdGen; - @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + int index = 0; for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName()); if (providerAnnot != null) { - transformMetadataMethod(cls, method, diagnostics, innerSource); + transformMetadataMethod(cls, method, context.getDiagnostics(), context.getHierarchy(), index++); } providerAnnot = method.getAnnotations().get(ClassScopedMetadataProvider.class.getName()); if (providerAnnot != null) { ValueType[] params = method.getParameterTypes(); if (params.length != 1 && params[0].isObject(PlatformClass.class.getName())) { - diagnostics.error(new CallLocation(method.getReference()), "Method {{m0}} marked with {{c1}} " - + "must take exactly one parameter of type {{c2}}", + context.getDiagnostics().error(new CallLocation(method.getReference()), + "Method {{m0}} marked with {{c1}} must take exactly one parameter of type {{c2}}", method.getReference(), ClassScopedMetadataProvider.class.getName(), PlatformClass.class.getName()); } @@ -60,20 +59,17 @@ class MetadataProviderTransformer implements ClassHolderTransformer { genAnnot.getValues().put("value", new AnnotationValue(ValueType.object( ClassScopedMetadataProviderNativeGenerator.class.getName()))); method.getAnnotations().add(genAnnot); - - AnnotationHolder noCacheAnnot = new AnnotationHolder(NoCache.class.getName()); - method.getAnnotations().add(noCacheAnnot); } } } private void transformMetadataMethod(ClassHolder cls, MethodHolder method, Diagnostics diagnostics, - ClassReaderSource classSource) { + ClassHierarchy hierarchy, int suffix) { if (!validate(method, diagnostics)) { return; } - FieldHolder field = new FieldHolder("$$metadata$$" + fieldIdGen++); + FieldHolder field = new FieldHolder("$$metadata$$" + suffix); field.setType(method.getResultType()); field.setLevel(AccessLevel.PRIVATE); field.getModifiers().add(ElementModifier.STATIC); @@ -95,15 +91,12 @@ class MetadataProviderTransformer implements ClassHolderTransformer { createMethod.getAnnotations().add(refAnnot); method.getModifiers().remove(ElementModifier.NATIVE); - ProgramEmitter pe = ProgramEmitter.create(method, classSource); + ProgramEmitter pe = ProgramEmitter.create(method, hierarchy); pe.when(pe.getField(field.getReference(), field.getType()).isNull()) .thenDo(() -> pe.setField(field.getReference(), pe.invoke(createMethod.getReference().getClassName(), createMethod.getReference().getName(), createMethod.getResultType()))); pe.getField(field.getReference(), field.getType()) .returnValue(); - - AnnotationHolder noCacheAnnot = new AnnotationHolder(NoCache.class.getName()); - method.getAnnotations().add(noCacheAnnot); } private boolean validate(MethodHolder method, Diagnostics diagnostics) { diff --git a/platform/src/main/java/org/teavm/platform/plugin/NewInstanceDependencySupport.java b/platform/src/main/java/org/teavm/platform/plugin/NewInstanceDependencySupport.java index 32137f0e4..8e43b1594 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/NewInstanceDependencySupport.java +++ b/platform/src/main/java/org/teavm/platform/plugin/NewInstanceDependencySupport.java @@ -15,11 +15,21 @@ */ package org.teavm.platform.plugin; -import org.teavm.dependency.*; -import org.teavm.model.*; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; import org.teavm.platform.Platform; public class NewInstanceDependencySupport extends AbstractDependencyListener { + private static final MethodDescriptor INIT_METHOD = new MethodDescriptor("", void.class); private DependencyNode allClassesNode; @Override @@ -28,7 +38,7 @@ public class NewInstanceDependencySupport extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { ClassReader cls = agent.getClassSource().get(className); if (cls == null) { return; @@ -36,14 +46,14 @@ public class NewInstanceDependencySupport extends AbstractDependencyListener { if (cls.hasModifier(ElementModifier.ABSTRACT) || cls.hasModifier(ElementModifier.INTERFACE)) { return; } - MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); + MethodReader method = cls.getMethod(INIT_METHOD); if (method != null) { allClassesNode.propagate(agent.getType(className)); } } @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReader reader = method.getMethod(); if (reader.getOwnerName().equals(Platform.class.getName()) && reader.getName().equals("newInstanceImpl")) { allClassesNode.connect(method.getResult()); @@ -55,7 +65,8 @@ public class NewInstanceDependencySupport extends AbstractDependencyListener { private void attachConstructor(DependencyAgent agent, String type, CallLocation location) { MethodReference ref = new MethodReference(type, "", ValueType.VOID); - MethodDependency methodDep = agent.linkMethod(ref, location); + MethodDependency methodDep = agent.linkMethod(ref); + methodDep.addLocation(location); methodDep.getVariable(0).propagate(agent.getType(type)); methodDep.use(); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformDependencyListener.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformDependencyListener.java index 23828dbbe..2bbb133c8 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformDependencyListener.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformDependencyListener.java @@ -19,7 +19,6 @@ import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.platform.Platform; public class PlatformDependencyListener extends AbstractDependencyListener { @@ -31,12 +30,12 @@ public class PlatformDependencyListener extends AbstractDependencyListener { } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { allClasses.propagate(agent.getType(className)); } @Override - public void methodReached(final DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(final DependencyAgent agent, MethodDependency method) { if (!method.getReference().getClassName().equals(Platform.class.getName())) { return; } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index f1f78f0a5..9f79925f3 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -25,7 +25,6 @@ import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; @@ -37,7 +36,7 @@ import org.teavm.platform.PlatformRunnable; public class PlatformGenerator implements Generator, Injector, DependencyPlugin { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "asJavaClass": method.getResult().propagate(agent.getType("java.lang.Class")); @@ -48,7 +47,7 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin case "startThread": case "schedule": { MethodDependency launchMethod = agent.linkMethod(new MethodReference(Platform.class, - "launchThread", PlatformRunnable.class, void.class), null); + "launchThread", PlatformRunnable.class, void.class)); method.getVariable(1).connect(launchMethod.getVariable(1)); launchMethod.use(); break; diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java index 5edb9e2f5..8541c36ae 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java @@ -21,16 +21,15 @@ import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; import org.teavm.platform.PlatformObject; import org.teavm.platform.PlatformQueue; public class PlatformQueueGenerator implements Injector, DependencyPlugin { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { MethodDependency addMethod = agent.linkMethod(new MethodReference(PlatformQueue.class, "wrap", - Object.class, PlatformObject.class), null); + Object.class, PlatformObject.class)); addMethod.getVariable(1).connect(method.getResult()); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java index b97398ae7..ad944b096 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java @@ -18,11 +18,10 @@ package org.teavm.platform.plugin; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; class ResourceAccessorDependencyListener extends AbstractDependencyListener { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { if (!method.getReference().getClassName().equals(ResourceAccessor.class.getName())) { return; } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java index fa0cefb44..6df18638a 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java @@ -16,10 +16,9 @@ package org.teavm.platform.plugin; import org.teavm.backend.javascript.TeaVMJavaScriptHost; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodHolder; import org.teavm.vm.spi.TeaVMHost; @@ -32,7 +31,7 @@ class ResourceAccessorTransformer implements ClassHolderTransformer { } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (cls.getName().equals(ResourceAccessor.class.getName())) { ResourceAccessorInjector injector = new ResourceAccessorInjector(); for (MethodHolder method : cls.getMethods()) { diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java index a89e778ac..67ef9cf93 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java @@ -23,11 +23,11 @@ import org.teavm.platform.metadata.ResourceArray; import org.teavm.platform.metadata.ResourceMap; class ResourceProgramTransformer { - private ClassReaderSource innerSource; + private ClassHierarchy hierarchy; private Program program; - public ResourceProgramTransformer(ClassReaderSource innerSource, Program program) { - this.innerSource = innerSource; + public ResourceProgramTransformer(ClassHierarchy hierarchy, Program program) { + this.hierarchy = hierarchy; this.program = program; } @@ -72,8 +72,8 @@ class ResourceProgramTransformer { accessInsn.setReceiver(insn.getReceiver()); return Arrays.asList(accessInsn); } - ClassReader iface = innerSource.get(method.getClassName()); - if (iface == null || !innerSource.isSuperType(Resource.class.getName(), iface.getName()).orElse(false)) { + ClassReader iface = hierarchy.getClassSource().get(method.getClassName()); + if (iface == null || !hierarchy.isSuperType(Resource.class.getName(), iface.getName(), false)) { return null; } if (method.getName().startsWith("get")) { diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java index bcddb82b0..4042637e8 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java @@ -15,16 +15,19 @@ */ package org.teavm.platform.plugin; -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.*; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.MethodHolder; +import org.teavm.model.Program; class ResourceTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { Program program = method.getProgram(); if (program != null) { - new ResourceProgramTransformer(innerSource, program).transformProgram(); + new ResourceProgramTransformer(context.getHierarchy(), program).transformProgram(); } } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierDependencyPlugin.java b/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierDependencyPlugin.java index 949b6df23..3ebb90942 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierDependencyPlugin.java +++ b/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierDependencyPlugin.java @@ -18,11 +18,10 @@ package org.teavm.platform.plugin; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; public class StringAmplifierDependencyPlugin implements DependencyPlugin { @Override - public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodReached(DependencyAgent agent, MethodDependency method) { method.getResult().propagate(agent.getType("java.lang.String")); } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierTransformer.java index 202d6d4e4..67385a341 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/StringAmplifierTransformer.java @@ -15,11 +15,11 @@ */ package org.teavm.platform.plugin; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReference; @@ -31,15 +31,15 @@ import org.teavm.platform.metadata.Resource; public class StringAmplifierTransformer implements ClassHolderTransformer { @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { - transformProgram(innerSource, method.getProgram()); + transformProgram(context.getHierarchy(), method.getProgram()); } } } - private void transformProgram(ClassReaderSource classSource, Program program) { + private void transformProgram(ClassHierarchy hierarchy, Program program) { for (BasicBlock block : program.getBasicBlocks()) { for (Instruction instruction : block) { if (!(instruction instanceof InvokeInstruction)) { @@ -53,7 +53,7 @@ public class StringAmplifierTransformer implements ClassHolderTransformer { MethodReference method = invoke.getMethod(); String owningClass = method.getClassName(); - if (classSource.isSuperType(Resource.class.getName(), owningClass).orElse(false)) { + if (hierarchy.isSuperType(Resource.class.getName(), owningClass, false)) { if (method.getReturnType().isObject(String.class)) { Variable var = program.createVariable(); InvokeInstruction amplifyInstruction = new InvokeInstruction(); diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java b/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java index 1c34c728a..ad29409ff 100644 --- a/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java +++ b/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java @@ -15,12 +15,11 @@ */ package org.teavm.dependency; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AccessLevel; import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassReaderSource; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReference; @@ -40,7 +39,7 @@ public class DependencyTestPatcher implements ClassHolderTransformer { } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (cls.getName().equals(className)) { MethodHolder method = new MethodHolder("main", ValueType.parse(String[].class), ValueType.VOID); method.setLevel(AccessLevel.PUBLIC); diff --git a/tests/src/test/java/org/teavm/incremental/EntryPoint.java b/tests/src/test/java/org/teavm/incremental/EntryPoint.java new file mode 100644 index 000000000..908171988 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/EntryPoint.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.incremental; + +import org.teavm.backend.javascript.spi.InjectedBy; + +public class EntryPoint { + private EntryPoint() { + } + + public static void main(String[] args) { + saveResult(run()); + } + + @InjectedBy(EntryPointGenerator.class) + private static native void saveResult(String result); + + private static native String run(); +} diff --git a/tests/src/test/java/org/teavm/incremental/EntryPointGenerator.java b/tests/src/test/java/org/teavm/incremental/EntryPointGenerator.java new file mode 100644 index 000000000..baa74b362 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/EntryPointGenerator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.incremental; + +import java.io.IOException; +import org.teavm.backend.javascript.spi.Injector; +import org.teavm.backend.javascript.spi.InjectorContext; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReference; + +public class EntryPointGenerator implements Injector { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + context.getWriter().append("main.result = ("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(").").appendField(new FieldReference("java.lang.String", "characters")); + context.getWriter().append(".data"); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/EntryPointTransformer.java b/tests/src/test/java/org/teavm/incremental/EntryPointTransformer.java new file mode 100644 index 000000000..90e7346fd --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/EntryPointTransformer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 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.incremental; + +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; + +public class EntryPointTransformer implements ClassHolderTransformer { + private String entryPoint; + + public EntryPointTransformer(String entryPoint) { + this.entryPoint = entryPoint; + } + + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (!cls.getName().equals(EntryPoint.class.getName())) { + return; + } + + MethodHolder method = cls.getMethod(new MethodDescriptor("run", String.class)); + method.getModifiers().remove(ElementModifier.NATIVE); + + Program program = new Program(); + program.createVariable(); + BasicBlock block = program.createBasicBlock(); + + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setMethod(new MethodReference(entryPoint, "run", ValueType.object("java.lang.String"))); + invoke.setReceiver(program.createVariable()); + block.add(invoke); + + ExitInstruction exit = new ExitInstruction(); + exit.setValueToReturn(invoke.getReceiver()); + block.add(exit); + + method.setProgram(program); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/IncrementalTest.java b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java new file mode 100644 index 000000000..dfebb8a0a --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java @@ -0,0 +1,327 @@ +/* + * Copyright 2018 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.incremental; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; +import org.mozilla.javascript.typedarrays.NativeUint16Array; +import org.teavm.ast.AsyncMethodNode; +import org.teavm.ast.RegularMethodNode; +import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.cache.AlwaysStaleCacheStatus; +import org.teavm.cache.CacheStatus; +import org.teavm.cache.InMemoryMethodNodeCache; +import org.teavm.cache.InMemoryProgramCache; +import org.teavm.callgraph.CallGraph; +import org.teavm.dependency.FastDependencyAnalyzer; +import org.teavm.diagnostics.DefaultProblemTextConsumer; +import org.teavm.diagnostics.Problem; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.ClassReader; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.util.ModelUtils; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.tooling.TeaVMProblemRenderer; +import org.teavm.vm.BuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; +import org.teavm.vm.TeaVMOptimizationLevel; + +public class IncrementalTest { + private static final String OLD_FILE = "classes-old.js"; + private static final String NEW_FILE = "classes-new.js"; + private static final String REFRESHED_FILE = "classes-refreshed.js"; + private static ClassHolderSource oldClassSource = new ClasspathClassHolderSource( + IncrementalTest.class.getClassLoader()); + private static Context rhinoContext; + private static ScriptableObject rhinoRootScope; + private String[] updatedMethods; + private String oldResult; + private String newResult; + + @Rule + public TestName name = new TestName(); + + @BeforeClass + public static void initClass() { + rhinoContext = Context.enter(); + rhinoContext.setOptimizationLevel(-1); + rhinoContext.setLanguageVersion(Context.VERSION_ES6); + rhinoRootScope = rhinoContext.initStandardObjects(); + } + + @AfterClass + public static void closeClass() { + Context.exit(); + rhinoRootScope = null; + rhinoContext = null; + } + + @Test + public void simple() { + run(); + checkUpdatedMethods("Foo.get", "Main.callFoo"); + assertEquals("old", oldResult); + assertEquals("new", newResult); + } + + @Test + public void lambda() { + run(); + checkUpdatedMethods("Bar.call", "Bar$call$lambda$_1_0.get", "Bar$call$lambda$_1_0.", + "BarNew.lambda$call$0", "Main.run"); + assertEquals("Foo: bar-old", oldResult); + assertEquals("Foo: bar-new", newResult); + } + + @Test + public void lambdaUnchanged() { + run(); + checkUpdatedMethods(); + assertEquals("Foo: main", oldResult); + assertEquals("Foo: main", newResult); + } + + @Test + public void meta() { + run(); + checkUpdatedMethods("Main$PROXY$2_0.meta", "Main$StringSupplier$proxy$2_0_0.", "Main.meta", + "Main$StringSupplier$proxy$2_0_0.get"); + assertEquals("meta: ok", oldResult); + assertEquals("meta: ok", newResult); + } + + private void checkUpdatedMethods(String... methods) { + assertEquals("Unexpected set of updated methods", new HashSet<>(Arrays.asList(methods)), + new HashSet<>(Arrays.asList(updatedMethods))); + } + + private void run() { + String entryPoint = "org.teavm.incremental.data." + name.getMethodName().toLowerCase() + ".Main"; + Builder builder = new Builder(entryPoint); + + ClassHolderSourceImpl newClassSource = new ClassHolderSourceImpl(oldClassSource, true); + ClassHolderSourceImpl refreshedClassSource = new ClassHolderSourceImpl(oldClassSource, false); + + builder.build(oldClassSource, AlwaysStaleCacheStatus.INSTANCE, OLD_FILE); + builder.build(refreshedClassSource, refreshedClassSource, REFRESHED_FILE); + builder.enableCapturing(); + builder.build(newClassSource, newClassSource, NEW_FILE); + + assertEquals("Script must be the same after refreshing", builder.buildTarget.get(OLD_FILE), + builder.buildTarget.get(REFRESHED_FILE)); + + updatedMethods = builder.programCache.updatedMethods + .stream() + .map(m -> getSimpleName(m.getClassName()) + "." + m.getName()) + .sorted() + .toArray(String[]::new); + + oldResult = runScript(builder.buildTarget.get(OLD_FILE), OLD_FILE); + newResult = runScript(builder.buildTarget.get(NEW_FILE), NEW_FILE); + } + + private String runScript(String script, String fileName) { + Scriptable scope = new NativeObject(); + scope.setParentScope(rhinoRootScope); + rhinoContext.evaluateString(scope, script, fileName, 1, null); + Function main = (Function) scope.get("main", scope); + ScriptRuntime.doTopCall(main, rhinoContext, scope, scope, + new Object[] { new NativeArray(0), Undefined.instance }); + NativeUint16Array jsChars = (NativeUint16Array) main.get("result", main); + char[] chars = new char[jsChars.getArrayLength()]; + for (int i = 0; i < chars.length; ++i) { + chars[i] = (char) jsChars.get(i).intValue(); + } + return new String(chars); + } + + private static String getSimpleName(String name) { + return name.substring(name.lastIndexOf('.') + 1); + } + + static class Builder { + String entryPoint; + CapturingMethodNodeCache astCache = new CapturingMethodNodeCache(); + CapturingProgramCache programCache = new CapturingProgramCache(); + BuildTargetImpl buildTarget = new BuildTargetImpl(); + + Builder(String entryPoint) { + this.entryPoint = entryPoint; + } + + void enableCapturing() { + programCache.capturing = true; + astCache.capturing = true; + } + + void build(ClassHolderSource classSource, CacheStatus cacheStatus, String name) { + JavaScriptTarget target = new JavaScriptTarget(); + TeaVM vm = new TeaVMBuilder(target) + .setClassLoader(IncrementalTest.class.getClassLoader()) + .setClassSource(classSource) + .setDependencyAnalyzerFactory(FastDependencyAnalyzer::new) + .build(); + vm.setCacheStatus(cacheStatus); + vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); + vm.setProgramCache(programCache); + target.setAstCache(astCache); + target.setMinifying(false); + vm.add(new EntryPointTransformer(entryPoint)); + vm.entryPoint(EntryPoint.class.getName()); + vm.installPlugins(); + vm.build(buildTarget, name); + List problems = vm.getProblemProvider().getSevereProblems(); + if (!problems.isEmpty()) { + fail("Compiler error generating file '" + name + "'\n" + buildErrorMessage(vm)); + } + } + + private String buildErrorMessage(TeaVM vm) { + CallGraph cg = vm.getDependencyInfo().getCallGraph(); + DefaultProblemTextConsumer consumer = new DefaultProblemTextConsumer(); + StringBuilder sb = new StringBuilder(); + for (Problem problem : vm.getProblemProvider().getProblems()) { + consumer.clear(); + problem.render(consumer); + sb.append(consumer.getText()); + TeaVMProblemRenderer.renderCallStack(cg, problem.getLocation(), sb); + sb.append("\n"); + } + return sb.toString(); + } + } + + static class CapturingMethodNodeCache extends InMemoryMethodNodeCache { + final Set updatedMethods = new HashSet<>(); + boolean capturing; + + @Override + public void store(MethodReference methodReference, RegularMethodNode node, Supplier dependencies) { + super.store(methodReference, node, dependencies); + if (capturing) { + updatedMethods.add(methodReference); + } + } + + @Override + public void storeAsync(MethodReference methodReference, AsyncMethodNode node, Supplier dependencies) { + super.storeAsync(methodReference, node, dependencies); + if (capturing) { + updatedMethods.add(methodReference); + } + } + } + + static class CapturingProgramCache extends InMemoryProgramCache { + final Set updatedMethods = new HashSet<>(); + boolean capturing; + + @Override + public void store(MethodReference method, Program program, Supplier dependencies) { + super.store(method, program, dependencies); + if (capturing) { + updatedMethods.add(method); + } + } + } + + static class ClassHolderSourceImpl implements ClassHolderSource, CacheStatus { + private ClassHolderSource underlying; + private Map cache = new HashMap<>(); + private boolean replace; + + ClassHolderSourceImpl(ClassHolderSource underlying, boolean replace) { + this.underlying = underlying; + this.replace = replace; + } + + @Override + public boolean isStaleClass(String className) { + ClassReader cls = underlying.get(className); + if (cls == null) { + return true; + } + + return cls.getAnnotations().get(Update.class.getName()) != null; + } + + @Override + public boolean isStaleMethod(MethodReference method) { + return isStaleClass(method.getClassName()); + } + + @Override + public ClassHolder get(String name) { + if (!replace) { + return underlying.get(name); + } + return cache.computeIfAbsent(name, key -> { + ClassHolder cls = underlying.get(key); + if (cls == null) { + return cls; + } + if (cls.getAnnotations().get(Update.class.getName()) != null) { + ClassHolder newClass = underlying.get(key + "New"); + if (newClass != null) { + cls = ModelUtils.copyClass(newClass, new ClassHolder(key)); + } + } + return cls; + }); + } + } + + static class BuildTargetImpl implements BuildTarget { + private Map fs = new HashMap<>(); + + public String get(String name) { + return new String(fs.get(name).toByteArray(), StandardCharsets.UTF_8); + } + + @Override + public OutputStream createResource(String fileName) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + fs.put(fileName, out); + return out; + } + } +} diff --git a/core/src/main/java/org/teavm/cache/NoCache.java b/tests/src/test/java/org/teavm/incremental/Update.java similarity index 86% rename from core/src/main/java/org/teavm/cache/NoCache.java rename to tests/src/test/java/org/teavm/incremental/Update.java index 45953182b..7bdb168b2 100644 --- a/core/src/main/java/org/teavm/cache/NoCache.java +++ b/tests/src/test/java/org/teavm/incremental/Update.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.cache; +package org.teavm.incremental; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -21,6 +21,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface NoCache { +@Target(ElementType.TYPE) +public @interface Update { } diff --git a/tests/src/test/java/org/teavm/incremental/data/lambda/Bar.java b/tests/src/test/java/org/teavm/incremental/data/lambda/Bar.java new file mode 100644 index 000000000..95bb8f9a3 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambda/Bar.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.incremental.data.lambda; + +import org.teavm.incremental.Update; + +@Update +public final class Bar { + private Bar() { + } + + public static String call() { + return Foo.callLambda(() -> "bar-old"); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/lambda/BarNew.java b/tests/src/test/java/org/teavm/incremental/data/lambda/BarNew.java new file mode 100644 index 000000000..090fda39e --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambda/BarNew.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.incremental.data.lambda; + +public final class BarNew { + private BarNew() { + } + + public static String call() { + return Foo.callLambda(() -> "bar-new"); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/lambda/Foo.java b/tests/src/test/java/org/teavm/incremental/data/lambda/Foo.java new file mode 100644 index 000000000..49726ad4f --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambda/Foo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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.incremental.data.lambda; + +import java.util.function.Supplier; + +public final class Foo { + private Foo() { + } + + public static String callLambda(Supplier s) { + return "Foo: " + s.get(); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/lambda/Main.java b/tests/src/test/java/org/teavm/incremental/data/lambda/Main.java new file mode 100644 index 000000000..42cff2d28 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambda/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.incremental.data.lambda; + +public class Main { + private Main() { + } + + public static String run() { + return Bar.call(); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Foo.java b/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Foo.java new file mode 100644 index 000000000..c677ddc0c --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Foo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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.incremental.data.lambdaunchanged; + +import java.util.function.Supplier; + +public final class Foo { + private Foo() { + } + + public static String callLambda(Supplier s) { + return "Foo: " + s.get(); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Main.java b/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Main.java new file mode 100644 index 000000000..6b55828eb --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/lambdaunchanged/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.incremental.data.lambdaunchanged; + +public class Main { + private Main() { + } + + public static String run() { + return Foo.callLambda(() -> "main"); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/meta/Main.java b/tests/src/test/java/org/teavm/incremental/data/meta/Main.java new file mode 100644 index 000000000..c7148e6f3 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/meta/Main.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.incremental.data.meta; + +import static org.teavm.metaprogramming.Metaprogramming.exit; +import static org.teavm.metaprogramming.Metaprogramming.proxy; +import org.teavm.metaprogramming.CompileTime; +import org.teavm.metaprogramming.Meta; +import org.teavm.metaprogramming.Value; + +@CompileTime +public class Main { + private Main() { + } + + public static String run() { + return meta("ok").get(); + } + + @Meta + private static native StringSupplier meta(String s); + private static void meta(Value s) { + Value result = proxy(StringSupplier.class, (instance, method, args) -> { + exit(() -> "meta: " + s.get()); + }); + exit(() -> result.get()); + } + + interface StringSupplier { + String get(); + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/simple/Foo.java b/tests/src/test/java/org/teavm/incremental/data/simple/Foo.java new file mode 100644 index 000000000..b5c8a34d8 --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/simple/Foo.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.incremental.data.simple; + +import org.teavm.incremental.Update; + +@Update +public final class Foo { + private Foo() { + } + + public static String get() { + return "old"; + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/simple/FooNew.java b/tests/src/test/java/org/teavm/incremental/data/simple/FooNew.java new file mode 100644 index 000000000..245ab86bd --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/simple/FooNew.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.incremental.data.simple; + +public final class FooNew { + private FooNew() { + } + + public static String get() { + return "new"; + } +} diff --git a/tests/src/test/java/org/teavm/incremental/data/simple/Main.java b/tests/src/test/java/org/teavm/incremental/data/simple/Main.java new file mode 100644 index 000000000..d87acb0ae --- /dev/null +++ b/tests/src/test/java/org/teavm/incremental/data/simple/Main.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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.incremental.data.simple; + +public final class Main { + private Main() { + } + + public static String run() { + return callFoo(); + } + + private static String callFoo() { + return Foo.get(); + } +} diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingClass.java b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingClass.java new file mode 100644 index 000000000..e9b554333 --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingClass.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 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.metaprogramming.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface MetaprogrammingClass { +} diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java index 0b53e23f1..886e0a3d2 100644 --- a/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java +++ b/tests/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java @@ -24,6 +24,7 @@ import static org.teavm.metaprogramming.Metaprogramming.emit; import static org.teavm.metaprogramming.Metaprogramming.exit; import static org.teavm.metaprogramming.Metaprogramming.findClass; import static org.teavm.metaprogramming.Metaprogramming.lazy; +import static org.teavm.metaprogramming.Metaprogramming.unsupportedCase; import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,7 +50,11 @@ public class MetaprogrammingTest { @Meta static native int classNameLength(Class cls, int add); - static void classNameLength(ReflectClass cls, Value add) { + static void classNameLength(ReflectClass cls, Value add) { + if (cls != findClass(Object.class) && cls != findClass(Integer.class)) { + unsupportedCase(); + return; + } int length = cls.getName().length(); exit(() -> length + add.get()); } @@ -66,6 +71,10 @@ public class MetaprogrammingTest { @Meta private static native Object getField(Class cls, Object obj); private static void getField(ReflectClass cls, Value obj) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectField field = cls.getField("a"); exit(() -> field.get(obj)); } @@ -80,6 +89,10 @@ public class MetaprogrammingTest { @Meta private static native void setField(Class cls, Object obj, Object value); private static void setField(ReflectClass cls, Value obj, Value value) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectField field = cls.getField("a"); emit(() -> field.set(obj, value)); } @@ -95,6 +108,10 @@ public class MetaprogrammingTest { @Meta private static native String callDebug(Class cls, Object obj); private static void callDebug(ReflectClass cls, Value obj) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectMethod method = cls.getMethod("debug"); if (method == null) { exit(() -> "missing"); @@ -106,6 +123,10 @@ public class MetaprogrammingTest { @Meta private static native String callDebug(Class cls, Object obj, String a, int b); private static void callDebug(ReflectClass cls, Value obj, Value a, Value b) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectClass stringClass = findClass(String.class); ReflectClass intClass = findClass(int.class); ReflectMethod method = cls.getMethod("debug", stringClass, intClass); @@ -132,6 +153,10 @@ public class MetaprogrammingTest { @Meta private static native Object callConstructor(Class type); private static void callConstructor(ReflectClass type) { + if (type.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectMethod ctor = type.getMethod(""); if (ctor != null) { exit(() -> ctor.construct()); @@ -143,6 +168,10 @@ public class MetaprogrammingTest { @Meta private static native Object callConstructor(Class type, String a, int b); private static void callConstructor(ReflectClass type, Value a, Value b) { + if (type.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } ReflectClass stringClass = findClass(String.class); ReflectClass intClass = findClass(int.class); ReflectMethod ctor = type.getMethod("", stringClass, intClass); @@ -167,13 +196,17 @@ public class MetaprogrammingTest { @Test public void isInstanceWorks() { - assertTrue(isInstance("foo", String.class)); - assertFalse(isInstance(23, String.class)); + assertTrue(isInstance(new Context(), Context.class)); + assertFalse(isInstance(23, Context.class)); } @Meta private static native boolean isInstance(Object obj, Class type); private static void isInstance(Value obj, ReflectClass type) { + if (type.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } exit(() -> type.isInstance(obj.get())); } @@ -200,6 +233,11 @@ public class MetaprogrammingTest { @Meta private static native String readAnnotations(Class cls, Object obj); private static void readAnnotations(ReflectClass cls, Value obj) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } + StringBuilder sb = new StringBuilder(); sb.append(describeAnnotation(cls.getAnnotation(TestAnnotation.class))).append('\n'); for (ReflectMethod method : cls.getDeclaredMethods()) { @@ -273,6 +311,10 @@ public class MetaprogrammingTest { @Meta private static native String emitClassLiteral(Class cls); private static void emitClassLiteral(ReflectClass cls) { + if (!cls.isAssignableFrom(String.class)) { + unsupportedCase(); + return; + } ReflectClass arrayClass = arrayClass(cls); exit(() -> arrayClass.asJavaClass().getName()); } @@ -287,6 +329,10 @@ public class MetaprogrammingTest { @Meta private static native Object createArrayOfType(Class cls, int size); private static void createArrayOfType(ReflectClass cls, Value size) { + if (!cls.isAssignableFrom(String.class)) { + unsupportedCase(); + return; + } exit(() -> cls.createArray(size.get())); } @@ -298,6 +344,10 @@ public class MetaprogrammingTest { @Meta private static native Object getArrayElement(Class type, Object array, int index); private static void getArrayElement(ReflectClass type, Value array, Value index) { + if (!type.isAssignableFrom(String[].class)) { + unsupportedCase(); + return; + } exit(() -> type.getArrayElement(array.get(), index.get())); } @@ -340,6 +390,11 @@ public class MetaprogrammingTest { @Meta private static native String fieldType(Class cls, String name); private static void fieldType(ReflectClass cls, Value name) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } + Value result = lazy(() -> null); for (ReflectField field : cls.getDeclaredFields()) { String type = field.getType().getName(); @@ -374,6 +429,11 @@ public class MetaprogrammingTest { @Meta private static native void fieldType(Class cls, String name, Consumer typeConsumer); private static void fieldType(ReflectClass cls, Value name, Value> typeConsumer) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } + Value result = lazy(() -> { typeConsumer.get().accept(null); return null; @@ -397,13 +457,18 @@ public class MetaprogrammingTest { @Test public void unassignedLazyEvaluated() { - withUnassignedLazy(Object.class); + withUnassignedLazy(Context.class); assertEquals(23, counter); } @Meta - private static native void withUnassignedLazy(Class cls); + private static native void withUnassignedLazy(Class cls); private static void withUnassignedLazy(ReflectClass cls) { + if (cls.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } + emit(() -> counter = 42); Value value = lazy(() -> counter = 23); emit(() -> { @@ -422,30 +487,40 @@ public class MetaprogrammingTest { @Meta private static native Object createInstance(Class cls, int size); private static void createInstance(ReflectClass cls, Value size) { + if (!cls.isAssignableFrom(String.class) && !cls.isAssignableFrom(String[].class)) { + unsupportedCase(); + return; + } exit(() -> cls.createArray(size.get())); } + @MetaprogrammingClass static class Context { public int a; public int b; } + @MetaprogrammingClass class A { public String debug() { return "debug!"; } } + @MetaprogrammingClass class B { public String debug(String a, int b) { return "debug!" + a + ":" + b; } } + + @MetaprogrammingClass static class C { public C() { } } + @MetaprogrammingClass static class D { String a; int b; @@ -457,6 +532,7 @@ public class MetaprogrammingTest { } @TestAnnotation(a = "foo", c = Object.class) + @MetaprogrammingClass static class WithAnnotations { @TestAnnotation(c = {}) int f; diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java b/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java index 715f8e1dd..cfc725ced 100644 --- a/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java +++ b/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertNull; import static org.teavm.metaprogramming.Metaprogramming.emit; import static org.teavm.metaprogramming.Metaprogramming.exit; import static org.teavm.metaprogramming.Metaprogramming.proxy; +import static org.teavm.metaprogramming.Metaprogramming.unsupportedCase; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipJVM; @@ -43,6 +44,10 @@ public class ProxyTest { @Meta private static native T createProxy(Class proxyType, String add); private static void createProxy(ReflectClass proxyType, Value add) { + if (proxyType.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } Value proxy = proxy(proxyType, (instance, method, args) -> { String name = method.getName(); exit(() -> name + add.get()); @@ -64,6 +69,10 @@ public class ProxyTest { @Meta private static native T createProxyWithDefaultReturnValue(Class proxyType, StringBuilder sb); private static void createProxyWithDefaultReturnValue(ReflectClass proxyType, Value sb) { + if (proxyType.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } Value proxy = proxy(proxyType, (instance, method, args) -> { String name = method.getName(); emit(() -> sb.get().append(name + ";")); @@ -80,6 +89,11 @@ public class ProxyTest { @Meta private static native T createProxyWithBoxedParameters(Class proxyType); private static void createProxyWithBoxedParameters(ReflectClass proxyType) { + if (proxyType.getAnnotation(MetaprogrammingClass.class) == null) { + unsupportedCase(); + return; + } + Value proxy = proxy(proxyType, (instance, method, args) -> { Value result = emit(() -> new StringBuilder()); @@ -99,12 +113,14 @@ public class ProxyTest { exit(() -> proxy.get()); } + @MetaprogrammingClass interface A { String foo(); String bar(); } + @MetaprogrammingClass interface B { String foo(); @@ -113,6 +129,7 @@ public class ProxyTest { void baz(); } + @MetaprogrammingClass interface C { String foo(boolean a, byte b, char c, short d, int e, long f, String g); } diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 49ca0b8f1..29b33c7bb 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -36,13 +36,18 @@ import org.teavm.backend.c.CTarget; import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.wasm.WasmTarget; import org.teavm.backend.wasm.render.WasmBinaryVersion; +import org.teavm.cache.AlwaysStaleCacheStatus; +import org.teavm.cache.CacheStatus; import org.teavm.cache.DiskCachedClassHolderSource; +import org.teavm.cache.DiskMethodNodeCache; import org.teavm.cache.DiskProgramCache; -import org.teavm.cache.DiskRegularMethodNodeCache; +import org.teavm.cache.EmptyProgramCache; import org.teavm.cache.FileSymbolTable; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.DebugInformationBuilder; import org.teavm.dependency.DependencyInfo; +import org.teavm.dependency.FastDependencyAnalyzer; +import org.teavm.dependency.PreciseDependencyAnalyzer; import org.teavm.diagnostics.ProblemProvider; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; @@ -81,12 +86,13 @@ public class TeaVMTool { private ClassLoader classLoader = TeaVMTool.class.getClassLoader(); private DiskCachedClassHolderSource cachedClassSource; private DiskProgramCache programCache; - private DiskRegularMethodNodeCache astCache; + private DiskMethodNodeCache astCache; private FileSymbolTable symbolTable; private FileSymbolTable fileTable; private boolean cancelled; private TeaVMProgressListener progressListener; private TeaVM vm; + private boolean fastDependencyAnalysis; private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.SIMPLE; private List sourceFileProviders = new ArrayList<>(); private DebugInformationBuilder debugEmitter; @@ -205,6 +211,14 @@ public class TeaVMTool { this.optimizationLevel = optimizationLevel; } + public boolean isFastDependencyAnalysis() { + return fastDependencyAnalysis; + } + + public void setFastDependencyAnalysis(boolean fastDependencyAnalysis) { + this.fastDependencyAnalysis = fastDependencyAnalysis; + } + public void setMinHeapSize(int minHeapSize) { this.minHeapSize = minHeapSize; } @@ -305,10 +319,6 @@ public class TeaVMTool { ? new DebugInformationBuilder() : null; javaScriptTarget.setDebugEmitter(debugEmitter); - if (incremental) { - javaScriptTarget.setAstCache(astCache); - } - return javaScriptTarget; } @@ -333,6 +343,7 @@ public class TeaVMTool { cancelled = false; log.info("Running TeaVM"); TeaVMBuilder vmBuilder = new TeaVMBuilder(prepareTarget()); + CacheStatus cacheStatus; if (incremental) { cacheDirectory.mkdirs(); symbolTable = new FileSymbolTable(new File(cacheDirectory, "symbols")); @@ -341,10 +352,10 @@ public class TeaVMTool { ClassHolderSource classSource = new PreOptimizingClassHolderSource(innerClassSource); cachedClassSource = new DiskCachedClassHolderSource(cacheDirectory, symbolTable, fileTable, classSource, innerClassSource); - programCache = new DiskProgramCache(cacheDirectory, symbolTable, fileTable, innerClassSource); - - if (targetType == TeaVMTargetType.JAVASCRIPT) { - astCache = new DiskRegularMethodNodeCache(cacheDirectory, symbolTable, fileTable, innerClassSource); + programCache = new DiskProgramCache(cacheDirectory, symbolTable, fileTable); + if (incremental && targetType == TeaVMTargetType.JAVASCRIPT) { + astCache = new DiskMethodNodeCache(cacheDirectory, symbolTable, fileTable); + javaScriptTarget.setAstCache(astCache); } try { symbolTable.update(); @@ -353,19 +364,31 @@ public class TeaVMTool { log.info("Cache is missing"); } vmBuilder.setClassLoader(classLoader).setClassSource(cachedClassSource); + cacheStatus = cachedClassSource; } else { vmBuilder.setClassLoader(classLoader).setClassSource(new PreOptimizingClassHolderSource( new ClasspathClassHolderSource(classLoader))); + cacheStatus = AlwaysStaleCacheStatus.INSTANCE; } + + vmBuilder.setDependencyAnalyzerFactory(fastDependencyAnalysis + ? FastDependencyAnalyzer::new + : PreciseDependencyAnalyzer::new); + vm = vmBuilder.build(); if (progressListener != null) { vm.setProgressListener(progressListener); } vm.setProperties(properties); - vm.setProgramCache(programCache); - vm.setIncremental(incremental); - vm.setOptimizationLevel(optimizationLevel); + vm.setProgramCache(incremental ? programCache : EmptyProgramCache.INSTANCE); + vm.setCacheStatus(cacheStatus); + vm.setOptimizationLevel(!fastDependencyAnalysis && !incremental + ? optimizationLevel + : TeaVMOptimizationLevel.SIMPLE); + if (incremental) { + vm.addVirtualMethods(m -> true); + } vm.installPlugins(); for (ClassHolderTransformer transformer : resolveTransformers(classLoader)) { @@ -409,7 +432,7 @@ public class TeaVMTool { } if (incremental) { - programCache.flush(); + programCache.flush(vm.getDependencyClassSource()); if (astCache != null) { astCache.flush(); } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 93717a5c0..d46b7bc9f 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -58,6 +58,8 @@ public interface BuildStrategy { void setOptimizationLevel(TeaVMOptimizationLevel level); + void setFastDependencyAnalysis(boolean value); + void setTargetFileName(String targetFileName); void setClassesToPreserve(String[] classesToPreserve); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index d681621f0..3a136c304 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -49,6 +49,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private boolean incremental; private String cacheDirectory; private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.ADVANCED; + private boolean fastDependencyAnalysis; private boolean minifying; private boolean sourceMapsFileGenerated; private boolean debugInformationGenerated; @@ -153,6 +154,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.optimizationLevel = level; } + @Override + public void setFastDependencyAnalysis(boolean fastDependencyAnalysis) { + this.fastDependencyAnalysis = fastDependencyAnalysis; + } + @Override public void setTargetFileName(String targetFileName) { this.targetFileName = targetFileName; @@ -189,6 +195,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setTargetFileName(targetFileName); tool.setClassLoader(buildClassLoader()); tool.setOptimizationLevel(optimizationLevel); + tool.setFastDependencyAnalysis(fastDependencyAnalysis); tool.setSourceMapsFileGenerated(sourceMapsFileGenerated); tool.setDebugInformationGenerated(debugInformationGenerated); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index 6be5447cc..efc1ff6a2 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -134,6 +134,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.optimizationLevel = level; } + @Override + public void setFastDependencyAnalysis(boolean value) { + request.fastDependencyAnalysis = value; + } + @Override public void setTargetFileName(String targetFileName) { request.tagetFileName = targetFileName; diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java index 8552cc3ee..9a4ee8058 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java @@ -124,17 +124,8 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi public RemoteBuildResponse build(RemoteBuildRequest request, RemoteBuildCallback callback) { System.out.println("Build started"); - if (!request.incremental && incremental) { - try { - System.out.println("Dropping incremental cache"); - FileUtils.cleanDirectory(incrementalCache); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - TeaVMTool tool = new TeaVMTool(); - tool.setIncremental(incremental && request.incremental); + tool.setIncremental(incremental || request.incremental); if (tool.isIncremental()) { tool.setCacheDirectory(request.cacheDirectory != null ? new File(request.cacheDirectory) @@ -162,6 +153,7 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi } tool.setOptimizationLevel(request.optimizationLevel); + tool.setFastDependencyAnalysis(request.fastDependencyAnalysis); tool.setMinifying(request.minifying); tool.setWasmVersion(request.wasmVersion); tool.setMinHeapSize(request.heapSize); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 0f8eb32bb..3c50ac850 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -41,6 +41,7 @@ public class RemoteBuildRequest implements Serializable { public boolean minifying; public Properties properties; public TeaVMOptimizationLevel optimizationLevel; + public boolean fastDependencyAnalysis; public WasmBinaryVersion wasmVersion; public int heapSize; } diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 7379cca33..4cb70907a 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -63,6 +63,9 @@ import org.teavm.backend.wasm.WasmTarget; import org.teavm.callgraph.CallGraph; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.DebugInformationBuilder; +import org.teavm.dependency.DependencyAnalyzerFactory; +import org.teavm.dependency.FastDependencyAnalyzer; +import org.teavm.dependency.PreciseDependencyAnalyzer; import org.teavm.diagnostics.DefaultProblemTextConsumer; import org.teavm.diagnostics.Problem; import org.teavm.model.AnnotationHolder; @@ -79,6 +82,7 @@ import org.teavm.tooling.TeaVMProblemRenderer; import org.teavm.vm.DirectoryBuildTarget; import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVMBuilder; +import org.teavm.vm.TeaVMOptimizationLevel; import org.teavm.vm.TeaVMTarget; public class TeaVMTestRunner extends Runner implements Filterable { @@ -98,6 +102,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { private static final String C_COMPILER = "teavm.junit.c.compiler"; private static final String MINIFIED = "teavm.junit.minified"; private static final String OPTIMIZED = "teavm.junit.optimized"; + private static final String FAST_ANALYSIS = "teavm.junit.fastAnalysis"; private static final int stopTimeout = 15000; private Class testClass; @@ -625,16 +630,22 @@ public class TeaVMTestRunner extends Runner implements Filterable { T target = targetSupplier.get(); configuration.apply(target); + DependencyAnalyzerFactory dependencyAnalyzerFactory = PreciseDependencyAnalyzer::new; + boolean fastAnalysis = Boolean.parseBoolean(System.getProperty(FAST_ANALYSIS)); + if (fastAnalysis) { + dependencyAnalyzerFactory = FastDependencyAnalyzer::new; + } + TeaVM vm = new TeaVMBuilder(target) .setClassLoader(classLoader) .setClassSource(classSource) + .setDependencyAnalyzerFactory(dependencyAnalyzerFactory) .build(); Properties properties = new Properties(); applyProperties(method.getDeclaringClass(), properties); vm.setProperties(properties); - vm.setIncremental(false); configuration.apply(vm); vm.installPlugins(); @@ -642,6 +653,11 @@ public class TeaVMTestRunner extends Runner implements Filterable { new TestEntryPointTransformer(methodHolder.getReference(), testClass.getName()).install(vm); vm.entryPoint(entryPoint); + + if (fastAnalysis) { + vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); + vm.addVirtualMethods(m -> true); + } vm.build(new DirectoryBuildTarget(outputFile.getParentFile()), outputFile.getName()); if (!vm.getProblemProvider().getProblems().isEmpty()) { result.success = false; diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java index 30850f521..81d45bc9f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java @@ -24,12 +24,13 @@ import static org.teavm.junit.TeaVMTestRunner.JUNIT4_TEST; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; @@ -59,20 +60,20 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { } @Override - public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { if (cls.getName().equals(TestEntryPoint.class.getName())) { for (MethodHolder method : cls.getMethods()) { switch (method.getName()) { case "launchTest": - method.setProgram(generateLaunchProgram(method, innerSource)); + method.setProgram(generateLaunchProgram(method, context.getHierarchy())); method.getModifiers().remove(ElementModifier.NATIVE); break; case "before": - method.setProgram(generateBeforeProgram(method, innerSource)); + method.setProgram(generateBeforeProgram(method, context.getHierarchy())); method.getModifiers().remove(ElementModifier.NATIVE); break; case "after": - method.setProgram(generateAfterProgram(method, innerSource)); + method.setProgram(generateAfterProgram(method, context.getHierarchy())); method.getModifiers().remove(ElementModifier.NATIVE); break; } @@ -80,8 +81,8 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { } } - private Program generateBeforeProgram(MethodHolder method, ClassReaderSource innerSource) { - ProgramEmitter pe = ProgramEmitter.create(method, innerSource); + private Program generateBeforeProgram(MethodHolder method, ClassHierarchy hierarchy) { + ProgramEmitter pe = ProgramEmitter.create(method, hierarchy); ValueEmitter testCaseInitVar = pe.getField(TestEntryPoint.class, "testCase", Object.class); pe.when(testCaseInitVar.isNull()) .thenDo(() -> { @@ -90,7 +91,7 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { }); ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class); - if (innerSource.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName()).orElse(false)) { + if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName(), false)) { testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_BEFORE); } @@ -105,8 +106,8 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { return pe.getProgram(); } - private Program generateAfterProgram(MethodHolder method, ClassReaderSource innerSource) { - ProgramEmitter pe = ProgramEmitter.create(method, innerSource); + private Program generateAfterProgram(MethodHolder method, ClassHierarchy hierarchy) { + ProgramEmitter pe = ProgramEmitter.create(method, hierarchy); ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class); List classes = collectSuperClasses(pe.getClassSource(), testMethod.getClassName()); @@ -115,7 +116,7 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { .filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null) .forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference())); - if (innerSource.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName()).orElse(false)) { + if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName(), false)) { testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_AFTER); } @@ -136,13 +137,13 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { return result; } - private Program generateLaunchProgram(MethodHolder method, ClassReaderSource innerSource) { - ProgramEmitter pe = ProgramEmitter.create(method, innerSource); + private Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) { + ProgramEmitter pe = ProgramEmitter.create(method, hierarchy); pe.getField(TestEntryPoint.class, "testCase", Object.class) .cast(ValueType.object(testMethod.getClassName())) .invokeSpecial(testMethod); - MethodReader testMethodReader = innerSource.resolve(testMethod); + MethodReader testMethodReader = hierarchy.getClassSource().resolve(testMethod); AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(JUNIT4_TEST); AnnotationValue throwsValue = testAnnotation != null ? testAnnotation.getValue("expected") : null; if (throwsValue != null) { diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java index 7cb3d0a05..dbcd91892 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java @@ -20,7 +20,6 @@ import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; class TestExceptionDependencyListener extends AbstractDependencyListener { @@ -31,17 +30,17 @@ class TestExceptionDependencyListener extends AbstractDependencyListener { allClasses = agent.createNode(); allClasses.addConsumer(c -> { if (agent.getClassSource().isSuperType("java.lang.Throwable", c.getName()).orElse(false)) { - MethodDependency methodDep = agent.linkMethod(new MethodReference(c.getName(), GET_MESSAGE), null); + MethodDependency methodDep = agent.linkMethod(new MethodReference(c.getName(), GET_MESSAGE)); methodDep.getVariable(0).propagate(c); methodDep.use(); } }); - agent.linkClass("java.lang.Throwable", null); + agent.linkClass("java.lang.Throwable"); } @Override - public void classReached(DependencyAgent agent, String className, CallLocation location) { + public void classReached(DependencyAgent agent, String className) { allClasses.propagate(agent.getType(className)); } } diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index cba4835da..aa6a6a237 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -119,6 +119,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.optimizationLevel", defaultValue = "SIMPLE") private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.SIMPLE; + @Parameter(property = "teavm.fastGlobalAnalysis", defaultValue = "false") + private boolean fastGlobalAnalysis; + @Parameter(property = "teavm.targetType", defaultValue = "JAVASCRIPT") private TeaVMTargetType targetType = TeaVMTargetType.JAVASCRIPT; @@ -259,6 +262,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setTargetFileName(targetFileName); } builder.setOptimizationLevel(optimizationLevel); + builder.setFastDependencyAnalysis(fastGlobalAnalysis); if (classesToPreserve != null) { builder.setClassesToPreserve(classesToPreserve); }