From 054db3e8d18f5cbba7fb1755cbe132456cef6557 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 14 May 2019 14:01:39 +0300 Subject: [PATCH] C: incremental code generator --- .../java/org/teavm/backend/c/CTarget.java | 90 ++-- .../backend/c/generate/CallSiteGenerator.java | 29 +- .../backend/c/generate/ClassGenerator.java | 87 ++- .../c/generate/CodeGenerationVisitor.java | 6 + .../backend/c/generate/GenerationContext.java | 8 +- .../backend/c/generate/NameProvider.java | 3 +- .../backend/c/generate/SimpleStringPool.java | 105 ++++ .../teavm/backend/c/generate/StringPool.java | 26 +- .../c/generate/StringPoolGenerator.java | 20 +- .../intrinsic/ExceptionHandlingIntrinsic.java | 4 +- .../backend/c/intrinsic/IntrinsicContext.java | 2 + .../c/intrinsic/ShadowStackIntrinsic.java | 11 +- .../ExceptionHandlingDependencyListener.java | 2 +- .../org/teavm/backend/wasm/WasmTarget.java | 3 +- .../generate/CallSiteBinaryGenerator.java | 2 +- .../ExceptionHandlingIntrinsic.java | 2 +- .../java/org/teavm/cache/AnnotationIO.java | 177 ++++++ .../main/java/org/teavm/cache/ClassIO.java | 164 +----- .../cache/MemoryCachedClassReaderSource.java | 2 +- .../main/java/org/teavm/cache/ProgramIO.java | 5 + .../java/org/teavm/common/GraphIndexer.java | 14 +- .../dependency/ClassClosureAnalyzer.java | 137 +++++ .../teavm/dependency/DependencyAnalyzer.java | 3 +- .../main/java/org/teavm/model/Program.java | 6 + .../java/org/teavm/model/ProgramReader.java | 2 + .../model/lowlevel/CallSiteDescriptor.java | 94 ++++ .../lowlevel/CallSiteDescriptorAnnot.java | 24 + .../lowlevel/CallSiteDescriptorsAnnot.java | 20 + .../model/lowlevel/CallSiteLocation.java | 20 + .../model/lowlevel/CallSiteLocationAnnot.java | 26 + .../lowlevel/ExceptionHandlerDescriptor.java | 17 + .../ExceptionHandlerDescriptorAnnot.java | 22 + .../lowlevel/ShadowStackTransformer.java | 19 +- .../org/teavm/model/util/ProgramUtils.java | 1 + .../org/teavm/parsing/ClassRefsRenamer.java | 2 + .../org/teavm/runtime/ExceptionHandling.java | 8 +- .../java/org/teavm/runtime/RuntimeClass.java | 2 + .../teavm/runtime/RuntimeClassPointer.java | 24 + .../vm/IncrementalDirectoryBuildTarget.java | 122 +++++ .../java/org/teavm/vm/MemoryBuildTarget.java | 3 +- .../resources/org/teavm/backend/c/runtime.h | 42 +- pom.xml | 1 + tools/c-incremental/pom.xml | 92 ++++ .../c/incremental/BuilderListener.java | 28 + .../c/incremental/IncrementalCBuilder.java | 510 ++++++++++++++++++ .../c/incremental/ProgressHandler.java | 22 + tools/cli/pom.xml | 5 + .../org/teavm/cli/TeaVMCBuilderRunner.java | 139 +++++ .../tooling/builder/SimpleBuildResult.java} | 9 +- .../java/org/teavm/devserver/CodeServlet.java | 3 +- 50 files changed, 1890 insertions(+), 275 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/c/generate/SimpleStringPool.java create mode 100644 core/src/main/java/org/teavm/cache/AnnotationIO.java create mode 100644 core/src/main/java/org/teavm/dependency/ClassClosureAnalyzer.java create mode 100644 core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorAnnot.java create mode 100644 core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorsAnnot.java create mode 100644 core/src/main/java/org/teavm/model/lowlevel/CallSiteLocationAnnot.java create mode 100644 core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptorAnnot.java create mode 100644 core/src/main/java/org/teavm/runtime/RuntimeClassPointer.java create mode 100644 core/src/main/java/org/teavm/vm/IncrementalDirectoryBuildTarget.java create mode 100644 tools/c-incremental/pom.xml create mode 100644 tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/BuilderListener.java create mode 100644 tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java create mode 100644 tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/ProgressHandler.java create mode 100644 tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java rename tools/{devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java => core/src/main/java/org/teavm/tooling/builder/SimpleBuildResult.java} (87%) 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 4dc20494b..657ec713a 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -18,14 +18,12 @@ package org.teavm.backend.c; import com.carrotsearch.hppc.ObjectByteHashMap; import com.carrotsearch.hppc.ObjectByteMap; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; 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.HashSet; @@ -48,6 +46,7 @@ import org.teavm.backend.c.generate.IncludeManager; import org.teavm.backend.c.generate.NameProvider; import org.teavm.backend.c.generate.OutputFileUtil; import org.teavm.backend.c.generate.SimpleIncludeManager; +import org.teavm.backend.c.generate.SimpleStringPool; import org.teavm.backend.c.generate.StringPool; import org.teavm.backend.c.generate.StringPoolGenerator; import org.teavm.backend.c.generators.ArrayGenerator; @@ -102,6 +101,7 @@ import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.instructions.CloneArrayInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.lowlevel.CallSiteDescriptor; import org.teavm.model.lowlevel.Characteristics; import org.teavm.model.lowlevel.ClassInitializerEliminator; import org.teavm.model.lowlevel.ClassInitializerTransformer; @@ -141,12 +141,27 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private int minHeapSize = 32 * 1024 * 1024; private List intrinsicFactories = new ArrayList<>(); private List generatorFactories = new ArrayList<>(); + private Characteristics characteristics; private Set asyncMethods; + private boolean incremental; + private StringPool stringPool; + + public CTarget() { + this(new SimpleStringPool()); + } + + public CTarget(StringPool stringPool) { + this.stringPool = stringPool; + } public void setMinHeapSize(int minHeapSize) { this.minHeapSize = minHeapSize; } + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + @Override public List getTransformers() { List transformers = new ArrayList<>(); @@ -163,7 +178,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { @Override public void setController(TeaVMTargetController controller) { this.controller = controller; - Characteristics characteristics = new Characteristics(controller.getUnprocessedClassSource()); + characteristics = new Characteristics(controller.getUnprocessedClassSource()); classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource()); classInitializerTransformer = new ClassInitializerTransformer(); shadowStackTransformer = new ShadowStackTransformer(characteristics); @@ -273,6 +288,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { nullCheckTransformation.apply(program, method.getResultType()); new CoroutineTransformation(controller.getUnprocessedClassSource(), asyncMethods) .apply(program, method.getReference()); + ShadowStackTransformer shadowStackTransformer = !incremental + ? this.shadowStackTransformer + : new ShadowStackTransformer(characteristics); shadowStackTransformer.apply(program, method); } @@ -281,7 +299,6 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { VirtualTableProvider vtableProvider = createVirtualTableProvider(classes); ClassHierarchy hierarchy = new ClassHierarchy(classes); TagRegistry tagRegistry = new TagRegistry(classes, hierarchy); - StringPool stringPool = new StringPool(); Decompiler decompiler = new Decompiler(classes, new HashSet<>(), false, true); Characteristics characteristics = new Characteristics(controller.getUnprocessedClassSource()); @@ -313,12 +330,18 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { GenerationContext context = new GenerationContext(vtableProvider, characteristics, controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, - intrinsics, generators, asyncMethods::contains, buildTarget); + intrinsics, generators, asyncMethods::contains, buildTarget, incremental); BufferedCodeWriter runtimeWriter = new BufferedCodeWriter(); - copyResource("runtime.h", "runtime.h", buildTarget); + BufferedCodeWriter runtimeHeaderWriter = new BufferedCodeWriter(); emitResource(runtimeWriter, "runtime.c"); + runtimeHeaderWriter.println("#pragma once"); + if (incremental) { + runtimeHeaderWriter.println("#define TEAVM_INCREMENTAL true"); + } + emitResource(runtimeHeaderWriter, "runtime.h"); + ClassGenerator classGenerator = new ClassGenerator(context, controller.getUnprocessedClassSource(), tagRegistry, decompiler); IntrinsicFactoryContextImpl intrinsicFactoryContext = new IntrinsicFactoryContextImpl( @@ -334,8 +357,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { generateClasses(classes, classGenerator, buildTarget); generateSpecialFunctions(context, runtimeWriter); OutputFileUtil.write(runtimeWriter, "runtime.c", buildTarget); + OutputFileUtil.write(runtimeHeaderWriter, "runtime.h", buildTarget); - generateCallSites(buildTarget, context); + generateCallSites(buildTarget, context, classes.getClassNames()); generateStrings(buildTarget, stringPool); List types = classGenerator.getTypes().stream() @@ -362,25 +386,6 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { } } - private void copyResource(String resourceName, String targetName, BuildTarget buildTarget) { - ClassLoader classLoader = CTarget.class.getClassLoader(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader( - classLoader.getResourceAsStream("org/teavm/backend/c/" + resourceName))); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - buildTarget.createResource(targetName), StandardCharsets.UTF_8))) { - while (true) { - String line = reader.readLine(); - if (line == null) { - break; - } - writer.write(line); - writer.write('\n'); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - private void generateClasses(ListableClassHolderSource classes, ClassGenerator classGenerator, BuildTarget buildTarget) throws IOException { List classNames = sortClassNames(classes); @@ -444,7 +449,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { return classNames; } - private void generateCallSites(BuildTarget buildTarget, GenerationContext context) throws IOException { + private void generateCallSites(BuildTarget buildTarget, GenerationContext context, + Collection classNames) throws IOException { BufferedCodeWriter writer = new BufferedCodeWriter(); BufferedCodeWriter headerWriter = new BufferedCodeWriter(); @@ -458,15 +464,32 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { headerIncludes.includePath("runtime.h"); headerIncludes.includeClass(CallSiteGenerator.CALL_SITE); - String callSiteName = context.getNames().forClass(CallSiteGenerator.CALL_SITE); - headerWriter.println("extern " + callSiteName + " teavm_callSites[];"); - - new CallSiteGenerator(context, writer, includes).generate(shadowStackTransformer.getCallSites()); + if (incremental) { + generateIncrementalCallSites(context, headerWriter); + } else { + generateFastCallSites(context, writer, includes, headerWriter, classNames); + } OutputFileUtil.write(writer, "callsites.c", buildTarget); OutputFileUtil.write(headerWriter, "callsites.h", buildTarget); } + private void generateFastCallSites(GenerationContext context, CodeWriter writer, IncludeManager includes, + CodeWriter headerWriter, Collection classNames) { + String callSiteName = context.getNames().forClass(CallSiteGenerator.CALL_SITE); + headerWriter.println("extern " + callSiteName + " teavm_callSites[];"); + headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) (teavm_callSites + id)"); + + new CallSiteGenerator(context, writer, includes, "teavm_callSites") + .generate(CallSiteDescriptor.extract(context.getClassSource(), classNames)); + } + + private void generateIncrementalCallSites(GenerationContext context, CodeWriter headerWriter) { + String callSiteName = context.getNames().forClass(CallSiteGenerator.CALL_SITE); + headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) (((" + callSiteName + + "*) ((void**) frame)[3]) + id)"); + } + private void generateStrings(BuildTarget buildTarget, StringPool stringPool) throws IOException { BufferedCodeWriter writer = new BufferedCodeWriter(); BufferedCodeWriter headerWriter = new BufferedCodeWriter(); @@ -685,7 +708,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { int size = context.getStringPool().getStrings().size(); writer.println("for (int i = 0; i < " + size + "; ++i) {").indent(); - writer.println("((TeaVM_Object*) (teavm_stringPool + i))->header = stringHeader;"); + writer.println("TeaVM_Object *s = (TeaVM_Object*) (teavm_stringPool + i);"); + writer.println("if (s != NULL) s->header = stringHeader;"); writer.outdent().println("}"); } diff --git a/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java index d7708aa24..5c5331054 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CallSiteGenerator.java @@ -38,16 +38,24 @@ public class CallSiteGenerator { private List exceptionHandlers = new ArrayList<>(); private String callSiteLocationName; private String exceptionHandlerName; + private String callSitesName; + private boolean isStatic; - public CallSiteGenerator(GenerationContext context, CodeWriter writer, IncludeManager includes) { + public CallSiteGenerator(GenerationContext context, CodeWriter writer, IncludeManager includes, + String callSitesName) { this.context = context; this.writer = writer; this.includes = includes; callSiteLocationName = context.getNames().forClass(CALL_SITE_LOCATION); exceptionHandlerName = context.getNames().forClass(EXCEPTION_HANDLER); + this.callSitesName = callSitesName; } - public void generate(List callSites) { + public void setStatic(boolean isStatic) { + this.isStatic = isStatic; + } + + public void generate(List callSites) { CodeWriter writerForLocations = writer.fragment(); generateCallSites(callSites); @@ -58,12 +66,15 @@ public class CallSiteGenerator { writer = oldWriter; } - private void generateCallSites(List callSites) { + private void generateCallSites(List callSites) { String callSiteName = context.getNames().forClass(CALL_SITE); includes.includeClass(CALL_SITE); includes.includePath("strings.h"); - writer.print(callSiteName).print(" teavm_callSites[" + callSites.size() + "] = {").indent(); + if (isStatic) { + writer.print("static "); + } + writer.print(callSiteName).print(" " + callSitesName + "[" + callSites.size() + "] = {").indent(); String handlerCountName = fieldName(CALL_SITE, "handlerCount"); String firstHandlerName = fieldName(CALL_SITE, "firstHandler"); String locationName = fieldName(CALL_SITE, "location"); @@ -86,14 +97,14 @@ public class CallSiteGenerator { } String firstHandlerExpr = !callSite.getHandlers().isEmpty() - ? "teavm_exceptionHandlers + " + exceptionHandlers.size() + ? "exceptionHandlers_" + callSitesName + " + " + exceptionHandlers.size() : "NULL"; writer.println().print("{ "); writer.print(".").print(handlerCountName).print(" = ") .print(String.valueOf(callSite.getHandlers().size())).print(", "); writer.print(".").print(firstHandlerName).print(" = ").print(firstHandlerExpr).print(", "); writer.print(".").print(locationName).print(" = ") - .print(locationIndex >= 0 ? "teavm_callSiteLocations + " + locationIndex : "NULL"); + .print(locationIndex >= 0 ? "callSiteLocations_" + callSitesName + " + " + locationIndex : "NULL"); writer.print(" }"); exceptionHandlers.addAll(callSite.getHandlers()); @@ -104,8 +115,8 @@ public class CallSiteGenerator { private void generateLocations() { includes.includeClass(CALL_SITE_LOCATION); - writer.print("static ").print(callSiteLocationName).print(" teavm_callSiteLocations[" + locations.size() - + "] = {").indent(); + writer.print("static ").print(callSiteLocationName).print(" callSiteLocations_" + callSitesName + + "[" + locations.size() + "] = {").indent(); String fileNameName = fieldName(CALL_SITE_LOCATION, "fileName"); String classNameName = fieldName(CALL_SITE_LOCATION, "className"); @@ -137,7 +148,7 @@ public class CallSiteGenerator { private void generateHandlers() { includes.includeClass(EXCEPTION_HANDLER); - writer.print("static ").print(exceptionHandlerName).print(" teavm_exceptionHandlers[" + writer.print("static ").print(exceptionHandlerName).print(" exceptionHandlers_" + callSitesName + "[" + exceptionHandlers.size() + "] = {").indent(); String idName = fieldName(EXCEPTION_HANDLER, "id"); diff --git a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java index e15cc384c..60680532a 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java @@ -18,6 +18,7 @@ package org.teavm.backend.c.generate; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -57,7 +58,9 @@ import org.teavm.model.instructions.ConstructInstruction; import org.teavm.model.instructions.ConstructMultiArrayInstruction; import org.teavm.model.instructions.InstructionVisitor; import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.lowlevel.CallSiteDescriptor; import org.teavm.model.lowlevel.Characteristics; +import org.teavm.runtime.CallSite; import org.teavm.runtime.RuntimeArray; import org.teavm.runtime.RuntimeClass; import org.teavm.runtime.RuntimeObject; @@ -81,6 +84,7 @@ public class ClassGenerator { private CodeWriter headerWriter; private IncludeManager includes; private IncludeManager headerIncludes; + private boolean stackDefined; public ClassGenerator(GenerationContext context, ClassReaderSource unprocessedClassSource, TagRegistry tagRegistry, Decompiler decompiler) { @@ -161,7 +165,9 @@ public class ClassGenerator { }; public void generateClass(CodeWriter writer, CodeWriter headerWriter, ClassHolder cls) { + stackDefined = false; init(writer, headerWriter, fileName(cls.getName())); + codeGenerator = new CodeGenerator(context, codeWriter, includes); String sysInitializerName = context.getNames().forClassSystemInitializer(cls.getName()); @@ -180,6 +186,12 @@ public class ClassGenerator { generateLayoutArray(cls.getName()); } + private void generateCallSites(List callSites, String callSitesName) { + CallSiteGenerator generator = new CallSiteGenerator(context, codeWriter, includes, callSitesName); + generator.setStatic(true); + generator.generate(callSites); + } + public void generateType(CodeWriter writer, CodeWriter headerWriter, ValueType type) { init(writer, headerWriter, fileName(type)); includes.includeType(type); @@ -220,6 +232,24 @@ public class ClassGenerator { continue; } + if (context.isIncremental()) { + String callSitesName; + List callSites = CallSiteDescriptor.extract(method.getProgram()); + if (!callSites.isEmpty()) { + callSitesName = "callsites_" + context.getNames().forMethod(method.getReference()); + includes.includeClass(CallSite.class.getName()); + generateCallSites(callSites, callSitesName); + } else { + callSitesName = "NULL"; + } + if (stackDefined) { + codeWriter.println("#undef TEAVM_ALLOC_STACK"); + } + codeWriter.println("#define TEAVM_ALLOC_STACK(size) TEAVM_ALLOC_STACK_DEF(size, " + + callSitesName + ")"); + stackDefined = true; + } + generateMethodForwardDeclaration(method); RegularMethodNode methodNode = decompiler.decompileRegular(method); codeGenerator.generateMethod(methodNode); @@ -454,6 +484,8 @@ public class ClassGenerator { int flags = 0; String layout = "NULL"; String initFunction = "NULL"; + String superinterfaceCount = "0"; + String superinterfaces = "NULL"; if (type instanceof ValueType.Object) { String className = ((ValueType.Object) type).getClassName(); @@ -473,7 +505,7 @@ public class ClassGenerator { flags |= RuntimeClass.ENUM; } List ranges = tagRegistry.getRanges(className); - tag = ranges != null && !ranges.isEmpty() ? ranges.get(0).lower : 0; + tag = !context.isIncremental() && ranges != null && !ranges.isEmpty() ? ranges.get(0).lower : 0; if (cls != null && cls.getParent() != null && types.contains(ValueType.object(cls.getParent()))) { includes.includeClass(cls.getParent()); @@ -487,10 +519,31 @@ public class ClassGenerator { if (cls != null && needsInitializer(cls)) { initFunction = context.getNames().forClassInitializer(className); } + + Set interfaces = cls != null + ? cls.getInterfaces().stream() + .filter(c -> types.contains(ValueType.object(c))) + .collect(Collectors.toSet()) + : Collections.emptySet(); + if (!interfaces.isEmpty()) { + superinterfaceCount = Integer.toString(cls.getInterfaces().size()); + StringBuilder sb = new StringBuilder("(TeaVM_Class*[]) { "); + boolean first = true; + for (String itf : interfaces) { + if (!first) { + sb.append(", "); + } + first = false; + includes.includeClass(itf); + sb.append("(TeaVM_Class*) &").append(context.getNames().forClassInstance(ValueType.object(itf))); + } + superinterfaces = sb.append(" }").toString(); + } + } else if (type instanceof ValueType.Array) { includes.includeClass("java.lang.Object"); parent = "(TeaVM_Class*) &" + context.getNames().forClassInstance(ValueType.object("java.lang.Object")); - tag = tagRegistry.getRanges("java.lang.Object").get(0).lower; + tag = !context.isIncremental() ? tagRegistry.getRanges("java.lang.Object").get(0).lower : 0; ValueType itemType = ((ValueType.Array) type).getItemType(); sizeExpr = "sizeof(" + CodeWriter.strictTypeAsString(itemType) + ")"; includes.includeType(itemType); @@ -532,6 +585,8 @@ public class ClassGenerator { codeWriter.println(".itemType = " + itemTypeExpr + ","); codeWriter.println(".isSupertypeOf = &" + superTypeFunction + ","); codeWriter.println(".superclass = " + parent + ","); + codeWriter.println(".superinterfaceCount = " + superinterfaceCount + ","); + codeWriter.println(".superinterfaces = " + superinterfaces + ","); codeWriter.println(".enumValues = NULL,"); codeWriter.println(".layout = " + layout + ","); codeWriter.println(".enumValues = " + enumConstants + ","); @@ -566,6 +621,7 @@ public class ClassGenerator { if (type instanceof ValueType.Object) { String className = ((ValueType.Object) type).getClassName(); return !context.getCharacteristics().isStructure(className) + && !context.getCharacteristics().isFunction(className) && !className.equals(Address.class.getName()); } else { return type instanceof ValueType.Array; @@ -766,6 +822,14 @@ public class ClassGenerator { } private void generateIsSuperclassFunction(String className) { + if (context.isIncremental()) { + generateIncrementalSuperclassFunction(className); + } else { + generateFastIsSuperclassFunction(className); + } + } + + private void generateFastIsSuperclassFunction(String className) { List ranges = tagRegistry.getRanges(className); if (ranges.isEmpty()) { codeWriter.println("return INT32_C(0);"); @@ -789,6 +853,25 @@ public class ClassGenerator { codeWriter.println("return INT32_C(1);"); } + private void generateIncrementalSuperclassFunction(String className) { + String functionName = context.getNames().forSupertypeFunction(ValueType.object(className)); + ClassReader cls = context.getClassSource().get(className); + if (cls != null && types.contains(ValueType.object(className))) { + includes.includeClass(className); + String name = context.getNames().forClassInstance(ValueType.object(className)); + codeWriter.println("if (cls == (TeaVM_Class*) &" + name + ") return INT32_C(1);"); + + codeWriter.println("if (cls->superclass != NULL && " + functionName + "(cls->superclass)) " + + "return INT32_C(1);"); + codeWriter.println("for (int32_t i = 0; i < cls->superinterfaceCount; ++i) {").indent(); + codeWriter.println("if (" + functionName + "(cls->superinterfaces[i])) " + + "return INT32_C(1);"); + codeWriter.outdent().println("}"); + } + + codeWriter.println("return INT32_C(0);"); + } + private void generateIsSuperArrayFunction(ValueType itemType) { String itemTypeName = context.getNames().forMemberField(new FieldReference( RuntimeClass.class.getName(), "itemType")); diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java index 52279cedc..e854d7967 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java @@ -517,6 +517,7 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { writer.print("TEAVM_ARRAY_DATA(TEAVM_FIELD("); String typeName = ((ValueType.Object) type).getClassName(); expr.getArguments().get(i).acceptVisitor(this); + includes.includeClass(typeName); writer.print(", ").print(names.forClass(typeName)).print(", ") .print(names.forMemberField(new FieldReference(typeName, "array"))).print(")"); writer.print(", ").print(BUFFER_TYPES.get(typeName)).print(")"); @@ -1016,6 +1017,11 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { ClassGenerator.escape(name, sb); return sb.toString(); } + + @Override + public boolean isIncremental() { + return context.isIncremental(); + } }; private static CVariableType typeToCType(ValueType type) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java index babf5b41b..d572e424c 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java +++ b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java @@ -43,11 +43,12 @@ public class GenerationContext { private Map intrinsicCache = new HashMap<>(); private Predicate asyncMethods; private BuildTarget buildTarget; + private boolean incremental; public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, ClassReaderSource classSource, List intrinsics, List generators, - Predicate asyncMethods, BuildTarget buildTarget) { + Predicate asyncMethods, BuildTarget buildTarget, boolean incremental) { this.virtualTableProvider = virtualTableProvider; this.characteristics = characteristics; this.dependencies = dependencies; @@ -59,6 +60,7 @@ public class GenerationContext { this.generators = new ArrayList<>(generators); this.asyncMethods = asyncMethods; this.buildTarget = buildTarget; + this.incremental = incremental; } public void addIntrinsic(Intrinsic intrinsic) { @@ -118,4 +120,8 @@ public class GenerationContext { public BuildTarget getBuildTarget() { return buildTarget; } + + public boolean isIncremental() { + return incremental; + } } diff --git a/core/src/main/java/org/teavm/backend/c/generate/NameProvider.java b/core/src/main/java/org/teavm/backend/c/generate/NameProvider.java index 28b8db0c5..94c7a961d 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/NameProvider.java +++ b/core/src/main/java/org/teavm/backend/c/generate/NameProvider.java @@ -55,7 +55,8 @@ public class NameProvider extends LowLevelNameProvider { memberFieldNames.put(new FieldReference(String.class.getName(), "hashCode"), "hashCode"); for (String name : new String[] { "size", "flags", "tag", "canary", "name", "itemType", "arrayType", - "isSupertypeOf", "init", "enumValues", "layout", "simpleName" }) { + "isSupertypeOf", "init", "enumValues", "layout", "simpleName", "superinterfaceCount", + "superinterfaces" }) { memberFieldNames.put(new FieldReference(RuntimeClass.class.getName(), name), name); } memberFieldNames.put(new FieldReference(RuntimeClass.class.getName(), "parent"), "superclass"); diff --git a/core/src/main/java/org/teavm/backend/c/generate/SimpleStringPool.java b/core/src/main/java/org/teavm/backend/c/generate/SimpleStringPool.java new file mode 100644 index 000000000..12ef09175 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/generate/SimpleStringPool.java @@ -0,0 +1,105 @@ +/* + * 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.backend.c.generate; + +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SimpleStringPool implements StringPool { + private ObjectIntMap stringIndexes = new ObjectIntHashMap<>(); + private final List strings = new ArrayList<>(); + private final List readonlyStrings = Collections.unmodifiableList(strings); + private final List freeIndexes = new ArrayList<>(); + private FreeIndex firstFreeIndex; + private FreeIndex lastFreeIndex; + private ObjectIntMap lastStringIndexes = new ObjectIntHashMap<>(); + + @Override + public int getStringIndex(String string) { + int index = stringIndexes.getOrDefault(string, -1); + if (index < 0) { + index = lastStringIndexes.getOrDefault(string, -1); + if (index >= 0) { + removeFreeIndex(index); + strings.set(index, string); + } else if (firstFreeIndex != null) { + index = firstFreeIndex.value; + freeIndexes.set(index, null); + firstFreeIndex = firstFreeIndex.next; + if (firstFreeIndex == null) { + lastFreeIndex = null; + } + strings.set(index, string); + } else { + index = strings.size(); + strings.add(string); + } + stringIndexes.put(string, index); + } + return index; + } + + private void removeFreeIndex(int index) { + FreeIndex freeIndex = freeIndexes.get(index); + if (freeIndex == null) { + return; + } + freeIndexes.set(index, null); + + if (freeIndex.previous != null) { + freeIndex.previous.next = freeIndex.next; + } else { + firstFreeIndex = freeIndex.next; + } + if (freeIndex.next != null) { + freeIndex.next.previous = freeIndex.previous; + } else { + lastFreeIndex = freeIndex.previous; + } + } + + @Override + public List getStrings() { + return readonlyStrings; + } + + public void reset() { + for (int i = freeIndexes.size(); i < strings.size(); ++i) { + FreeIndex freeIndex = new FreeIndex(); + freeIndexes.add(freeIndex); + if (lastFreeIndex != null) { + freeIndex.previous = lastFreeIndex; + lastFreeIndex.next = freeIndex; + } + lastFreeIndex = freeIndex; + } + lastStringIndexes.clear(); + lastStringIndexes.putAll(stringIndexes); + stringIndexes.clear(); + for (int i = 0; i < strings.size(); ++i) { + strings.set(i, null); + } + } + + static class FreeIndex { + int value; + FreeIndex previous; + FreeIndex next; + } +} diff --git a/core/src/main/java/org/teavm/backend/c/generate/StringPool.java b/core/src/main/java/org/teavm/backend/c/generate/StringPool.java index 057be6673..52b17f27b 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/StringPool.java +++ b/core/src/main/java/org/teavm/backend/c/generate/StringPool.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Alexey Andreev. + * Copyright 2019 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,10 @@ */ package org.teavm.backend.c.generate; -import com.carrotsearch.hppc.ObjectIntHashMap; -import com.carrotsearch.hppc.ObjectIntMap; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -public class StringPool { - private final ObjectIntMap stringIndexes = new ObjectIntHashMap<>(); - private final List strings = new ArrayList<>(); - private final List readonlyStrings = Collections.unmodifiableList(strings); +public interface StringPool { + int getStringIndex(String string); - public int getStringIndex(String string) { - int index = stringIndexes.getOrDefault(string, -1); - if (index < 0) { - index = strings.size(); - stringIndexes.put(string, index); - strings.add(string); - } - return index; - } - - public List getStrings() { - return readonlyStrings; - } + List getStrings(); } diff --git a/core/src/main/java/org/teavm/backend/c/generate/StringPoolGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/StringPoolGenerator.java index 6031e13ba..f4c2d31d4 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/StringPoolGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/StringPoolGenerator.java @@ -28,16 +28,20 @@ public class StringPoolGenerator { writer.println("TeaVM_String teavm_stringPool[" + strings.size() + "] = {").indent(); for (int i = 0; i < strings.size(); ++i) { String s = strings.get(i); - boolean codes = hasBadCharacters(s); - String macroName = codes ? "TEAVM_STRING_FROM_CODES" : "TEAVM_STRING"; - writer.print(macroName + "(" + s.length() + ", " + s.hashCode() + ","); - if (codes) { - generateNumericStringLiteral(s); + if (s == null) { + writer.println("TEAVM_NULL_STRING"); } else { - writer.print("u"); - generateSimpleStringLiteral(writer, s); + boolean codes = hasBadCharacters(s); + String macroName = codes ? "TEAVM_STRING_FROM_CODES" : "TEAVM_STRING"; + writer.print(macroName + "(" + s.length() + ", " + s.hashCode() + ","); + if (codes) { + generateNumericStringLiteral(s); + } else { + writer.print("u"); + generateSimpleStringLiteral(writer, s); + } + writer.print(")"); } - writer.print(")"); writer.print(i < strings.size() - 1 ? "," : " "); writer.print(" // string #" + i); diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java index de5059ab9..9070aca60 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ExceptionHandlingIntrinsic.java @@ -39,8 +39,10 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { switch (invocation.getMethod().getName()) { case "findCallSiteById": context.includes().includePath("callsites.h"); - context.writer().print("(teavm_callSites + "); + context.writer().print("TEAVM_FIND_CALLSITE("); context.emit(invocation.getArguments().get(0)); + context.writer().print(", "); + context.emit(invocation.getArguments().get(1)); context.writer().print(")"); break; } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java b/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java index 22977406d..f5a843911 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java @@ -39,4 +39,6 @@ public interface IntrinsicContext { IncludeManager includes(); String escapeFileName(String name); + + boolean isIncremental(); } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java index b3ba602dc..b319ce003 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/ShadowStackIntrinsic.java @@ -78,15 +78,16 @@ public class ShadowStackIntrinsic implements Intrinsic { context.writer().print(")[0]"); return; case "getStackRootCount": - context.writer().print("((int32_t) (intptr_t) ((void**) "); + context.writer().print("TEAVM_GC_ROOTS_COUNT("); context.emit(invocation.getArguments().get(0)); - context.writer().print(")[2])"); + context.writer().print(")"); return; - case "getStackRootPointer": - context.writer().print("&((void**) "); + case "getStackRootPointer": { + context.writer().print("TEAVM_GET_GC_ROOTS("); context.emit(invocation.getArguments().get(0)); - context.writer().print(")[3]"); + context.writer().print(")"); return; + } case "getCallSiteId": context.writer().print("((int32_t) (intptr_t) ((void**) "); context.emit(invocation.getArguments().get(0)); diff --git a/core/src/main/java/org/teavm/backend/lowlevel/dependency/ExceptionHandlingDependencyListener.java b/core/src/main/java/org/teavm/backend/lowlevel/dependency/ExceptionHandlingDependencyListener.java index d4b5a97c4..bfc52b4e8 100644 --- a/core/src/main/java/org/teavm/backend/lowlevel/dependency/ExceptionHandlingDependencyListener.java +++ b/core/src/main/java/org/teavm/backend/lowlevel/dependency/ExceptionHandlingDependencyListener.java @@ -34,7 +34,7 @@ public class ExceptionHandlingDependencyListener extends AbstractDependencyListe public void methodReached(DependencyAgent agent, MethodDependency method) { if (method.getReference().equals(FILL_IN_STACK_TRACE)) { DependencyNode node = agent.linkField(STACK_TRACE).getValue(); - node.propagate(agent.getType("[java/lang.StackTraceElement;")); + node.propagate(agent.getType("[Ljava/lang/StackTraceElement;")); node.getArrayItem().propagate(agent.getType("java.lang.StackTraceElement")); MethodDependency initElem = agent.linkMethod(STACK_TRACE_ELEMENT_INIT); 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 b9d89a4ae..d804b1ade 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -127,6 +127,7 @@ import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.instructions.CloneArrayInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.lowlevel.CallSiteDescriptor; import org.teavm.model.lowlevel.Characteristics; import org.teavm.model.lowlevel.ClassInitializerEliminator; import org.teavm.model.lowlevel.ClassInitializerTransformer; @@ -374,7 +375,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { int pages = (minHeapSize + pageSize - 1) / pageSize; module.setMemorySize(pages); generateMethods(classes, context, generator, classGenerator, binaryWriter, module); - exceptionHandlingIntrinsic.postProcess(shadowStackTransformer.getCallSites()); + exceptionHandlingIntrinsic.postProcess(CallSiteDescriptor.extract(classes, classes.getClassNames())); generateIsSupertypeFunctions(tagRegistry, module, classGenerator); classGenerator.postProcess(); mutatorIntrinsic.setStaticGcRootsAddress(classGenerator.getStaticGcRootsAddress()); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/CallSiteBinaryGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/CallSiteBinaryGenerator.java index 3a91c7246..b256bf22f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/CallSiteBinaryGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/CallSiteBinaryGenerator.java @@ -62,7 +62,7 @@ public class CallSiteBinaryGenerator { this.stringPool = stringPool; } - public int writeCallSites(List callSites) { + public int writeCallSites(List callSites) { if (callSites.isEmpty()) { return 0; } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java index 408e1b3f3..0c72887c5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java @@ -55,7 +55,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { return false; } - public void postProcess(List callSites) { + public void postProcess(List callSites) { int address = callSiteBinaryGenerator.writeCallSites(callSites); for (WasmInt32Constant constant : constants) { constant.setValue(address); diff --git a/core/src/main/java/org/teavm/cache/AnnotationIO.java b/core/src/main/java/org/teavm/cache/AnnotationIO.java new file mode 100644 index 000000000..0ebdef589 --- /dev/null +++ b/core/src/main/java/org/teavm/cache/AnnotationIO.java @@ -0,0 +1,177 @@ +/* + * Copyright 2019 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.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.AnnotationContainerReader; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.FieldReference; +import org.teavm.model.ReferenceCache; +import org.teavm.model.ValueType; + +public class AnnotationIO { + private ReferenceCache referenceCache; + private SymbolTable symbolTable; + + public AnnotationIO(ReferenceCache referenceCache, SymbolTable symbolTable) { + this.referenceCache = referenceCache; + this.symbolTable = symbolTable; + } + + public void writeAnnotations(VarDataOutput output, AnnotationContainerReader annotations) throws IOException { + List annotationList = new ArrayList<>(); + for (AnnotationReader annot : annotations.all()) { + annotationList.add(annot); + } + output.writeUnsigned(annotationList.size()); + for (AnnotationReader annot : annotationList) { + writeAnnotation(output, annot); + } + } + + public CachedAnnotations readAnnotations(VarDataInput input) throws IOException { + Map annotations = new HashMap<>(); + int annotCount = input.readUnsigned(); + for (int i = 0; i < annotCount; ++i) { + CachedAnnotation annot = readAnnotation(input); + annotations.put(annot.type, annot); + } + return new CachedAnnotations(annotations); + } + + private void writeAnnotation(VarDataOutput output, AnnotationReader annotation) throws IOException { + output.writeUnsigned(symbolTable.lookup(annotation.getType())); + int fieldCount = 0; + for (@SuppressWarnings("unused") String field : annotation.getAvailableFields()) { + ++fieldCount; + } + output.writeUnsigned(fieldCount); + for (String field : annotation.getAvailableFields()) { + output.writeUnsigned(symbolTable.lookup(field)); + writeAnnotationValue(output, annotation.getValue(field)); + } + } + + private CachedAnnotation readAnnotation(VarDataInput input) throws IOException { + CachedAnnotation annotation = new CachedAnnotation(); + annotation.type = referenceCache.getCached(symbolTable.at(input.readUnsigned())); + int valueCount = input.readUnsigned(); + Map fields = new HashMap<>(); + for (int i = 0; i < valueCount; ++i) { + String name = referenceCache.getCached(symbolTable.at(input.readUnsigned())); + AnnotationValue value = readAnnotationValue(input); + fields.put(name, value); + } + annotation.fields = fields; + return annotation; + } + + public void writeAnnotationValue(VarDataOutput output, AnnotationValue value) throws IOException { + output.writeUnsigned(value.getType()); + switch (value.getType()) { + case AnnotationValue.ANNOTATION: + writeAnnotation(output, value.getAnnotation()); + break; + case AnnotationValue.BOOLEAN: + output.writeUnsigned(value.getBoolean() ? 1 : 0); + break; + case AnnotationValue.BYTE: + output.writeSigned(value.getByte()); + break; + case AnnotationValue.CLASS: + output.writeUnsigned(symbolTable.lookup(value.getJavaClass().toString())); + break; + case AnnotationValue.DOUBLE: + output.writeDouble(value.getDouble()); + break; + case AnnotationValue.ENUM: + output.writeUnsigned(symbolTable.lookup(value.getEnumValue().getClassName())); + output.writeUnsigned(symbolTable.lookup(value.getEnumValue().getFieldName())); + break; + case AnnotationValue.FLOAT: + output.writeFloat(value.getFloat()); + break; + case AnnotationValue.INT: + output.writeSigned(value.getInt()); + break; + case AnnotationValue.LIST: { + List list = value.getList(); + output.writeUnsigned(list.size()); + for (AnnotationValue item : list) { + writeAnnotationValue(output, item); + } + break; + } + case AnnotationValue.LONG: + output.writeSigned(value.getLong()); + break; + case AnnotationValue.SHORT: + output.writeSigned(value.getShort()); + break; + case AnnotationValue.STRING: + output.write(value.getString()); + break; + } + } + + public AnnotationValue readAnnotationValue(VarDataInput input) throws IOException { + int type = input.readUnsigned(); + switch (type) { + case AnnotationValue.ANNOTATION: + return new AnnotationValue(readAnnotation(input)); + case AnnotationValue.BOOLEAN: + return new AnnotationValue(input.readUnsigned() != 0); + case AnnotationValue.BYTE: + return new AnnotationValue((byte) input.readSigned()); + case AnnotationValue.CLASS: + return new AnnotationValue(referenceCache.getCached(ValueType.parse( + symbolTable.at(input.readUnsigned())))); + case AnnotationValue.DOUBLE: + return new AnnotationValue(input.readDouble()); + case AnnotationValue.ENUM: { + String className = referenceCache.getCached(symbolTable.at(input.readUnsigned())); + String fieldName = referenceCache.getCached(symbolTable.at(input.readUnsigned())); + return new AnnotationValue(referenceCache.getCached(new FieldReference(className, fieldName))); + } + case AnnotationValue.FLOAT: + return new AnnotationValue(input.readFloat()); + case AnnotationValue.INT: + return new AnnotationValue(input.readSigned()); + case AnnotationValue.LIST: { + List list = new ArrayList<>(); + int sz = input.readUnsigned(); + for (int i = 0; i < sz; ++i) { + list.add(readAnnotationValue(input)); + } + return new AnnotationValue(list); + } + case AnnotationValue.LONG: + return new AnnotationValue(input.readSignedLong()); + case AnnotationValue.SHORT: + return new AnnotationValue((short) input.readSigned()); + case AnnotationValue.STRING: + return new AnnotationValue(input.read()); + default: + throw new RuntimeException("Unexpected annotation value type: " + type); + } + } + +} diff --git a/core/src/main/java/org/teavm/cache/ClassIO.java b/core/src/main/java/org/teavm/cache/ClassIO.java index 4c618bcbc..4498a47b5 100644 --- a/core/src/main/java/org/teavm/cache/ClassIO.java +++ b/core/src/main/java/org/teavm/cache/ClassIO.java @@ -20,19 +20,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; import org.teavm.model.AccessLevel; import org.teavm.model.AnnotationContainerReader; -import org.teavm.model.AnnotationReader; -import org.teavm.model.AnnotationValue; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReader; @@ -48,12 +43,14 @@ public class ClassIO { private ReferenceCache referenceCache; private SymbolTable symbolTable; private ProgramIO programIO; + private AnnotationIO annotationIO; public ClassIO(ReferenceCache referenceCache, SymbolTable symbolTable, SymbolTable fileTable, SymbolTable varTable) { this.referenceCache = referenceCache; this.symbolTable = symbolTable; programIO = new ProgramIO(referenceCache, symbolTable, fileTable, varTable); + annotationIO = new AnnotationIO(referenceCache, symbolTable); } public void writeClass(OutputStream stream, ClassReader cls) throws IOException { @@ -66,7 +63,7 @@ public class ClassIO { for (String iface : cls.getInterfaces()) { output.writeUnsigned(symbolTable.lookup(iface)); } - writeAnnotations(output, cls.getAnnotations()); + annotationIO.writeAnnotations(output, cls.getAnnotations()); output.writeUnsigned(cls.getFields().size()); for (FieldReader field : cls.getFields()) { writeField(output, field); @@ -93,7 +90,7 @@ public class ClassIO { interfaces.add(referenceCache.getCached(symbolTable.at(input.readUnsigned()))); } cls.interfaces = Collections.unmodifiableSet(interfaces); - cls.annotations = readAnnotations(input); + cls.annotations = annotationIO.readAnnotations(input); Map fields = new LinkedHashMap<>(); int fieldCount = input.readUnsigned(); @@ -120,7 +117,7 @@ public class ClassIO { output.writeUnsigned(field.getLevel().ordinal()); output.writeUnsigned(packModifiers(field.readModifiers())); writeFieldValue(output, field.getInitialValue()); - writeAnnotations(output, field.getAnnotations()); + annotationIO.writeAnnotations(output, field.getAnnotations()); } private CachedField readField(String className, VarDataInput input) throws IOException { @@ -130,7 +127,7 @@ public class ClassIO { field.level = accessLevels[input.readUnsigned()]; field.modifiers = unpackModifiers(input.readUnsigned()); field.initialValue = readFieldValue(input); - field.annotations = readAnnotations(input); + field.annotations = annotationIO.readAnnotations(input); field.ownerName = className; field.reference = referenceCache.getCached(new FieldReference(className, field.name)); return field; @@ -181,15 +178,15 @@ public class ClassIO { output.writeUnsigned(symbolTable.lookup(method.getDescriptor().toString())); output.writeUnsigned(method.getLevel().ordinal()); output.writeUnsigned(packModifiers(method.readModifiers())); - writeAnnotations(output, method.getAnnotations()); + annotationIO.writeAnnotations(output, method.getAnnotations()); for (AnnotationContainerReader parameterAnnotation : method.getParameterAnnotations()) { - writeAnnotations(output, parameterAnnotation); + annotationIO.writeAnnotations(output, parameterAnnotation); } if (method.getAnnotationDefault() != null) { output.writeUnsigned(1); - writeAnnotationValue(output, method.getAnnotationDefault()); + annotationIO.writeAnnotationValue(output, method.getAnnotationDefault()); } else { output.writeUnsigned(0); } @@ -212,17 +209,17 @@ public class ClassIO { method.reference = referenceCache.getCached(className, descriptor); method.level = accessLevels[input.readUnsigned()]; method.modifiers = unpackModifiers(input.readUnsigned()); - method.annotations = readAnnotations(input); + method.annotations = annotationIO.readAnnotations(input); method.ownerName = className; method.name = descriptor.getName(); method.parameterAnnotations = new CachedAnnotations[descriptor.parameterCount()]; for (int i = 0; i < method.parameterCount(); ++i) { - method.parameterAnnotations[i] = readAnnotations(input); + method.parameterAnnotations[i] = annotationIO.readAnnotations(input); } if (input.readUnsigned() != 0) { - method.annotationDefault = readAnnotationValue(input); + method.annotationDefault = annotationIO.readAnnotationValue(input); } if (input.readUnsigned() != 0) { @@ -239,143 +236,6 @@ public class ClassIO { return method; } - private void writeAnnotations(VarDataOutput output, AnnotationContainerReader annotations) throws IOException { - List annotationList = new ArrayList<>(); - for (AnnotationReader annot : annotations.all()) { - annotationList.add(annot); - } - output.writeUnsigned(annotationList.size()); - for (AnnotationReader annot : annotationList) { - writeAnnotation(output, annot); - } - } - - private CachedAnnotations readAnnotations(VarDataInput input) throws IOException { - Map annotations = new HashMap<>(); - int annotCount = input.readUnsigned(); - for (int i = 0; i < annotCount; ++i) { - CachedAnnotation annot = readAnnotation(input); - annotations.put(annot.type, annot); - } - return new CachedAnnotations(annotations); - } - - private void writeAnnotation(VarDataOutput output, AnnotationReader annotation) throws IOException { - output.writeUnsigned(symbolTable.lookup(annotation.getType())); - int fieldCount = 0; - for (@SuppressWarnings("unused") String field : annotation.getAvailableFields()) { - ++fieldCount; - } - output.writeUnsigned(fieldCount); - for (String field : annotation.getAvailableFields()) { - output.writeUnsigned(symbolTable.lookup(field)); - writeAnnotationValue(output, annotation.getValue(field)); - } - } - - private CachedAnnotation readAnnotation(VarDataInput input) throws IOException { - CachedAnnotation annotation = new CachedAnnotation(); - annotation.type = referenceCache.getCached(symbolTable.at(input.readUnsigned())); - int valueCount = input.readUnsigned(); - Map fields = new HashMap<>(); - for (int i = 0; i < valueCount; ++i) { - String name = referenceCache.getCached(symbolTable.at(input.readUnsigned())); - AnnotationValue value = readAnnotationValue(input); - fields.put(name, value); - } - annotation.fields = fields; - return annotation; - } - - private void writeAnnotationValue(VarDataOutput output, AnnotationValue value) throws IOException { - output.writeUnsigned(value.getType()); - switch (value.getType()) { - case AnnotationValue.ANNOTATION: - writeAnnotation(output, value.getAnnotation()); - break; - case AnnotationValue.BOOLEAN: - output.writeUnsigned(value.getBoolean() ? 1 : 0); - break; - case AnnotationValue.BYTE: - output.writeSigned(value.getByte()); - break; - case AnnotationValue.CLASS: - output.writeUnsigned(symbolTable.lookup(value.getJavaClass().toString())); - break; - case AnnotationValue.DOUBLE: - output.writeDouble(value.getDouble()); - break; - case AnnotationValue.ENUM: - output.writeUnsigned(symbolTable.lookup(value.getEnumValue().getClassName())); - output.writeUnsigned(symbolTable.lookup(value.getEnumValue().getFieldName())); - break; - case AnnotationValue.FLOAT: - output.writeFloat(value.getFloat()); - break; - case AnnotationValue.INT: - output.writeSigned(value.getInt()); - break; - case AnnotationValue.LIST: { - List list = value.getList(); - output.writeUnsigned(list.size()); - for (AnnotationValue item : list) { - writeAnnotationValue(output, item); - } - break; - } - case AnnotationValue.LONG: - output.writeSigned(value.getLong()); - break; - case AnnotationValue.SHORT: - output.writeSigned(value.getShort()); - break; - case AnnotationValue.STRING: - output.write(value.getString()); - break; - } - } - - private AnnotationValue readAnnotationValue(VarDataInput input) throws IOException { - int type = input.readUnsigned(); - switch (type) { - case AnnotationValue.ANNOTATION: - return new AnnotationValue(readAnnotation(input)); - case AnnotationValue.BOOLEAN: - return new AnnotationValue(input.readUnsigned() != 0); - case AnnotationValue.BYTE: - return new AnnotationValue((byte) input.readSigned()); - case AnnotationValue.CLASS: - return new AnnotationValue(referenceCache.getCached(ValueType.parse( - symbolTable.at(input.readUnsigned())))); - case AnnotationValue.DOUBLE: - return new AnnotationValue(input.readDouble()); - case AnnotationValue.ENUM: { - String className = referenceCache.getCached(symbolTable.at(input.readUnsigned())); - String fieldName = referenceCache.getCached(symbolTable.at(input.readUnsigned())); - return new AnnotationValue(referenceCache.getCached(new FieldReference(className, fieldName))); - } - case AnnotationValue.FLOAT: - return new AnnotationValue(input.readFloat()); - case AnnotationValue.INT: - return new AnnotationValue(input.readSigned()); - case AnnotationValue.LIST: { - List list = new ArrayList<>(); - int sz = input.readUnsigned(); - for (int i = 0; i < sz; ++i) { - list.add(readAnnotationValue(input)); - } - return new AnnotationValue(list); - } - case AnnotationValue.LONG: - return new AnnotationValue(input.readSignedLong()); - case AnnotationValue.SHORT: - return new AnnotationValue((short) input.readSigned()); - case AnnotationValue.STRING: - return new AnnotationValue(input.read()); - default: - throw new RuntimeException("Unexpected annotation value type: " + type); - } - } private int packModifiers(Set modifiers) { int result = 0; diff --git a/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java b/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java index 1c108429f..1fbcf5b6d 100644 --- a/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java +++ b/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java @@ -80,7 +80,7 @@ public class MemoryCachedClassReaderSource implements ClassReaderSource, CacheSt private Entry getEntry(String name) { return cache.computeIfAbsent(name, className -> { - ClassReader cls = provider.apply(className); + ClassReader cls = provider != null ? provider.apply(className) : null; Entry en = new Entry(); if (cls != null) { ByteArrayOutputStream output = new ByteArrayOutputStream(); diff --git a/core/src/main/java/org/teavm/cache/ProgramIO.java b/core/src/main/java/org/teavm/cache/ProgramIO.java index 5a2030b3c..d9176bb65 100644 --- a/core/src/main/java/org/teavm/cache/ProgramIO.java +++ b/core/src/main/java/org/teavm/cache/ProgramIO.java @@ -88,12 +88,14 @@ import org.teavm.model.instructions.SwitchInstruction; import org.teavm.model.instructions.SwitchTableEntry; import org.teavm.model.instructions.SwitchTableEntryReader; import org.teavm.model.instructions.UnwrapArrayInstruction; +import org.teavm.model.util.ModelUtils; public class ProgramIO { private SymbolTable symbolTable; private SymbolTable fileTable; private SymbolTable variableTable; private ReferenceCache referenceCache; + private AnnotationIO annotationIO; private static BinaryOperation[] binaryOperations = BinaryOperation.values(); private static NumericOperandType[] numericOperandTypes = NumericOperandType.values(); private static IntegerSubtype[] integerSubtypes = IntegerSubtype.values(); @@ -108,6 +110,7 @@ public class ProgramIO { this.symbolTable = symbolTable; this.fileTable = fileTable; this.variableTable = variableTable; + annotationIO = new AnnotationIO(referenceCache, symbolTable); } public void write(ProgramReader program, OutputStream output) throws IOException { @@ -149,6 +152,7 @@ public class ProgramIO { } data.writeUnsigned(0); } + annotationIO.writeAnnotations(data, program.getAnnotations()); } public Program read(InputStream input) throws IOException { @@ -230,6 +234,7 @@ public class ProgramIO { } } } + ModelUtils.copyAnnotations(annotationIO.readAnnotations(data), program.getAnnotations()); return program; } diff --git a/core/src/main/java/org/teavm/common/GraphIndexer.java b/core/src/main/java/org/teavm/common/GraphIndexer.java index 4627e6ba9..7273a7093 100644 --- a/core/src/main/java/org/teavm/common/GraphIndexer.java +++ b/core/src/main/java/org/teavm/common/GraphIndexer.java @@ -15,9 +15,9 @@ */ package org.teavm.common; +import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.IntHashSet; import com.carrotsearch.hppc.IntSet; -import com.carrotsearch.hppc.cursors.IntCursor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -157,7 +157,8 @@ public class GraphIndexer { List succList = new ArrayList<>(successors.length); IntegerArray orderedSuccessors = new IntegerArray(successors.length); if (terminalNodes.size() > 0) { - IntSet loopNodes = IntHashSet.from(findNaturalLoop(node, terminalNodes.getAll())); + int[] loopNodeList = findNaturalLoop(node, terminalNodes.getAll()); + IntSet loopNodes = IntHashSet.from(loopNodeList); for (int succ : successors) { if (loopNodes.contains(succ)) { succList.add(new WeightedNode(succ, priorities[succ], weights[succ])); @@ -170,8 +171,8 @@ public class GraphIndexer { IntSet outerSuccessors = new IntHashSet(successors.length); succList.clear(); - for (IntCursor loopNode : loopNodes) { - for (int succ : graph.outgoingEdges(loopNode.value)) { + for (int loopNode : loopNodeList) { + for (int succ : graph.outgoingEdges(loopNode)) { if (!loopNodes.contains(succ)) { if (outerSuccessors.add(succ)) { succList.add(new WeightedNode(succ, priorities[succ], weights[succ])); @@ -206,7 +207,9 @@ public class GraphIndexer { private int[] findNaturalLoop(int head, int[] terminals) { IntSet loop = new IntHashSet(); + IntArrayList loopList = new IntArrayList(); loop.add(head); + loopList.add(head); IntegerStack stack = new IntegerStack(1); for (int pred : terminals) { stack.push(pred); @@ -216,11 +219,12 @@ public class GraphIndexer { if (!loop.add(node)) { continue; } + loopList.add(node); for (int pred : graph.incomingEdges(node)) { stack.push(pred); } } - return loop.toArray(); + return loopList.toArray(); } public int nodeAt(int index) { diff --git a/core/src/main/java/org/teavm/dependency/ClassClosureAnalyzer.java b/core/src/main/java/org/teavm/dependency/ClassClosureAnalyzer.java new file mode 100644 index 000000000..e5cdb1967 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/ClassClosureAnalyzer.java @@ -0,0 +1,137 @@ +/* + * Copyright 2019 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.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.model.instructions.InvocationType; + +class ClassClosureAnalyzer extends AbstractInstructionReader { + private ClassReaderSource classSource; + private Set result = new LinkedHashSet<>(); + + ClassClosureAnalyzer(ClassReaderSource classSource) { + this.classSource = classSource; + } + + static Set build(ClassReaderSource classSource, + Collection initialClasses) { + ClassClosureAnalyzer analyzer = new ClassClosureAnalyzer(classSource); + for (String className : initialClasses) { + analyzer.build(className); + } + return analyzer.result; + } + + void build(String className) { + if (!result.add(className)) { + return; + } + + ClassReader cls = classSource.get(className); + if (cls == null) { + return; + } + + if (cls.getParent() != null) { + build(cls.getParent()); + } + for (String itf : cls.getInterfaces()) { + build(itf); + } + + for (FieldReader field : cls.getFields()) { + build(field.getType()); + } + for (MethodReader method : cls.getMethods()) { + build(method.getResultType()); + for (ValueType paramType : method.getParameterTypes()) { + build(paramType); + } + if (method.getProgram() != null) { + for (BasicBlockReader block : method.getProgram().getBasicBlocks()) { + block.readAllInstructions(this); + } + } + } + } + + void build(ValueType type) { + while (type instanceof ValueType.Array) { + type = ((ValueType.Array) type).getItemType(); + } + if (type instanceof ValueType.Object) { + build(((ValueType.Object) type).getClassName()); + } + } + @Override + public void classConstant(VariableReader receiver, ValueType cst) { + build(cst); + } + + @Override + public void cast(VariableReader receiver, VariableReader value, ValueType targetType) { + build(targetType); + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { + build(itemType); + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, + List dimensions) { + build(itemType); + } + + @Override + public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { + build(fieldType); + } + + @Override + public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) { + build(fieldType); + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + build(method.getReturnType()); + for (ValueType paramType : method.getParameterTypes()) { + build(paramType); + } + } + + @Override + public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { + build(type); + } +} diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index 2ed3d5fe3..4e53d444b 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -776,7 +776,8 @@ public abstract class DependencyAnalyzer implements DependencyInfo { unprocessedClassSource = null; classSource.innerHierarchy = null; - agentClassSource = classSourcePacker.pack(classSource, classSource.cache.keySet()); + agentClassSource = classSourcePacker.pack(classSource, + ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet()))); if (classSource != agentClassSource) { classHierarchy = new ClassHierarchy(agentClassSource); generatedClassNames.addAll(classSource.getGeneratedClassNames()); diff --git a/core/src/main/java/org/teavm/model/Program.java b/core/src/main/java/org/teavm/model/Program.java index f23f68d8f..3f1d91214 100644 --- a/core/src/main/java/org/teavm/model/Program.java +++ b/core/src/main/java/org/teavm/model/Program.java @@ -23,6 +23,7 @@ public class Program implements ProgramReader { private List variables = new ArrayList<>(); private boolean packed; private int lastUsedRegister; + private AnnotationContainer annotations = new AnnotationContainer(); public BasicBlock createBasicBlock() { BasicBlock block = new BasicBlock(this, basicBlocks.size()); @@ -149,4 +150,9 @@ public class Program implements ProgramReader { } return variables.get(index); } + + @Override + public AnnotationContainer getAnnotations() { + return annotations; + } } diff --git a/core/src/main/java/org/teavm/model/ProgramReader.java b/core/src/main/java/org/teavm/model/ProgramReader.java index 5aede1cce..2018f2de0 100644 --- a/core/src/main/java/org/teavm/model/ProgramReader.java +++ b/core/src/main/java/org/teavm/model/ProgramReader.java @@ -25,4 +25,6 @@ public interface ProgramReader { int variableCount(); VariableReader variableAt(int index); + + AnnotationContainerReader getAnnotations(); } diff --git a/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptor.java b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptor.java index c42323aaa..ca9c31a12 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptor.java @@ -16,7 +16,19 @@ package org.teavm.model.lowlevel; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import org.teavm.model.AnnotationContainer; +import org.teavm.model.AnnotationContainerReader; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReader; +import org.teavm.model.Program; public class CallSiteDescriptor { private int id; @@ -39,4 +51,86 @@ public class CallSiteDescriptor { public List getHandlers() { return handlers; } + + public static void save(Collection descriptors, AnnotationContainer annotations) { + List descriptorsValue = new ArrayList<>(); + for (CallSiteDescriptor descriptor : descriptors) { + AnnotationHolder descriptorAnnot = new AnnotationHolder(CallSiteDescriptorAnnot.class.getName()); + descriptorAnnot.getValues().put("id", new AnnotationValue(descriptor.id)); + descriptorAnnot.getValues().put("location", new AnnotationValue(descriptor.location.save())); + List handlersValue = descriptor.handlers.stream() + .map(h -> new AnnotationValue(h.save())) + .collect(Collectors.toList()); + descriptorAnnot.getValues().put("handlers", new AnnotationValue(handlersValue)); + descriptorsValue.add(new AnnotationValue(descriptorAnnot)); + } + + AnnotationHolder descriptorsAnnot = new AnnotationHolder(CallSiteDescriptorsAnnot.class.getName()); + descriptorsAnnot.getValues().put("value", new AnnotationValue(descriptorsValue)); + annotations.add(descriptorsAnnot); + } + + public static Collection load(AnnotationContainerReader annotations) { + AnnotationReader descriptorsAnnot = annotations.get(CallSiteDescriptorsAnnot.class.getName()); + if (descriptorsAnnot == null) { + return Collections.emptyList(); + } + + List descriptors = new ArrayList<>(); + for (AnnotationValue descriptorValue : descriptorsAnnot.getValue("value").getList()) { + AnnotationReader descriptorAnnot = descriptorValue.getAnnotation(); + int id = descriptorAnnot.getValue("id").getInt(); + CallSiteLocation location = CallSiteLocation.load(descriptorAnnot.getValue("location").getAnnotation()); + List handlers = descriptorAnnot.getValue("handlers").getList().stream() + .map(a -> ExceptionHandlerDescriptor.load(a.getAnnotation())) + .collect(Collectors.toList()); + CallSiteDescriptor descriptor = new CallSiteDescriptor(id, location); + descriptor.getHandlers().addAll(handlers); + descriptors.add(descriptor); + } + + return descriptors; + } + + public static List extract(Program program) { + List result = new ArrayList<>(); + extractTo(load(program.getAnnotations()), result); + return result; + } + + public static List extract(ClassReaderSource classes, + Collection classNames) { + List result = new ArrayList<>(); + for (String className : classNames) { + ClassReader cls = classes.get(className); + if (cls == null) { + continue; + } + for (MethodReader method : cls.getMethods()) { + if (method.getProgram() != null) { + extractTo(load(method.getProgram().getAnnotations()), result); + } + } + } + return result; + } + + private static void extractTo(Collection descriptors, + List result) { + for (CallSiteDescriptor descriptor : descriptors) { + if (descriptor.id >= result.size()) { + result.addAll(Collections.nCopies(descriptor.id - result.size() + 1, null)); + } + result.set(descriptor.id, descriptor); + } + } + + static AnnotationValue saveNullableString(String s) { + return new AnnotationValue(s != null ? "1" + s : "0"); + } + + static String loadNullableString(AnnotationValue value) { + String s = value.getString(); + return s.startsWith("0") ? null : s.substring(1); + } } diff --git a/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorAnnot.java b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorAnnot.java new file mode 100644 index 000000000..d244dcb8c --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorAnnot.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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.lowlevel; + +@interface CallSiteDescriptorAnnot { + int id(); + + ExceptionHandlerDescriptorAnnot[] handlers(); + + CallSiteLocationAnnot location(); +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorsAnnot.java b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorsAnnot.java new file mode 100644 index 000000000..9321fd238 --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/CallSiteDescriptorsAnnot.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 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.lowlevel; + +@interface CallSiteDescriptorsAnnot { + CallSiteDescriptorAnnot[] value(); +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocation.java b/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocation.java index 07775b803..c3885d5c6 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocation.java +++ b/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocation.java @@ -16,6 +16,9 @@ package org.teavm.model.lowlevel; import java.util.Objects; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; public class CallSiteLocation { private String fileName; @@ -63,4 +66,21 @@ public class CallSiteLocation { public int hashCode() { return Objects.hash(fileName, className, methodName, lineNumber); } + + public AnnotationReader save() { + AnnotationHolder annotation = new AnnotationHolder(CallSiteLocationAnnot.class.getName()); + annotation.getValues().put("fileName", CallSiteDescriptor.saveNullableString(fileName)); + annotation.getValues().put("className", CallSiteDescriptor.saveNullableString(className)); + annotation.getValues().put("methodName", CallSiteDescriptor.saveNullableString(methodName)); + annotation.getValues().put("lineNumber", new AnnotationValue(lineNumber)); + return annotation; + } + + public static CallSiteLocation load(AnnotationReader reader) { + return new CallSiteLocation( + CallSiteDescriptor.loadNullableString(reader.getValue("fileName")), + CallSiteDescriptor.loadNullableString(reader.getValue("className")), + CallSiteDescriptor.loadNullableString(reader.getValue("methodName")), + reader.getValue("lineNumber").getInt()); + } } diff --git a/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocationAnnot.java b/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocationAnnot.java new file mode 100644 index 000000000..fba78397b --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/CallSiteLocationAnnot.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 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.lowlevel; + +@interface CallSiteLocationAnnot { + String fileName(); + + String className(); + + String methodName(); + + int lineNumber(); +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptor.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptor.java index ef3408865..99152a122 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptor.java @@ -15,6 +15,10 @@ */ package org.teavm.model.lowlevel; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; + public class ExceptionHandlerDescriptor { private int id; private String className; @@ -31,4 +35,17 @@ public class ExceptionHandlerDescriptor { public String getClassName() { return className; } + + public AnnotationReader save() { + AnnotationHolder annot = new AnnotationHolder(ExceptionHandlerDescriptorAnnot.class.getName()); + annot.getValues().put("id", new AnnotationValue(id)); + annot.getValues().put("className", CallSiteDescriptor.saveNullableString(className)); + return annot; + } + + public static ExceptionHandlerDescriptor load(AnnotationReader annot) { + return new ExceptionHandlerDescriptor( + annot.getValue("id").getInt(), + CallSiteDescriptor.loadNullableString(annot.getValue("className"))); + } } diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptorAnnot.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptorAnnot.java new file mode 100644 index 000000000..60d15c57e --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlerDescriptorAnnot.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 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.lowlevel; + +@interface ExceptionHandlerDescriptorAnnot { + int id(); + + String className(); +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java b/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java index c8b184f00..0adb0e2c1 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ShadowStackTransformer.java @@ -33,27 +33,26 @@ import org.teavm.model.instructions.JumpInstruction; import org.teavm.runtime.ShadowStack; public class ShadowStackTransformer { - private Characteristics managedMethodRepository; + private Characteristics characteristics; private GCShadowStackContributor gcContributor; private List callSites = new ArrayList<>(); - public ShadowStackTransformer(Characteristics managedMethodRepository) { - gcContributor = new GCShadowStackContributor(managedMethodRepository); - this.managedMethodRepository = managedMethodRepository; - } - - public List getCallSites() { - return callSites; + public ShadowStackTransformer(Characteristics characteristics) { + gcContributor = new GCShadowStackContributor(characteristics); + this.characteristics = characteristics; } public void apply(Program program, MethodReader method) { - if (!managedMethodRepository.isManaged(method.getReference())) { + if (!characteristics.isManaged(method.getReference())) { return; } int shadowStackSize = gcContributor.contribute(program, method); - boolean exceptions = new ExceptionHandlingShadowStackContributor(managedMethodRepository, callSites, + int callSiteStartIndex = callSites.size(); + boolean exceptions = new ExceptionHandlingShadowStackContributor(characteristics, callSites, method.getReference(), program).contribute(); + List programCallSites = callSites.subList(callSiteStartIndex, callSites.size()); + CallSiteDescriptor.save(programCallSites, program.getAnnotations()); if (shadowStackSize > 0 || exceptions) { addStackAllocation(program, shadowStackSize); 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 34ae0600a..7f52dd982 100644 --- a/core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -91,6 +91,7 @@ public final class ProgramUtils { BasicBlock blockCopy = copy.basicBlockAt(i); copyBasicBlock(block, blockCopy); } + ModelUtils.copyAnnotations(program.getAnnotations(), copy.getAnnotations()); return copy; } diff --git a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java index a19a97f4d..71a60304f 100644 --- a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java +++ b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java @@ -342,6 +342,7 @@ public class ClassRefsRenamer extends AbstractInstructionVisitor { public void visit(GetFieldInstruction insn) { String className = classNameMapper.apply(insn.getField().getClassName()); insn.setField(referenceCache.getCached(new FieldReference(className, insn.getField().getFieldName()))); + insn.setFieldType(rename(insn.getFieldType())); } @Override @@ -350,6 +351,7 @@ public class ClassRefsRenamer extends AbstractInstructionVisitor { if (className != insn.getField().getClassName()) { insn.setField(referenceCache.getCached(new FieldReference(className, insn.getField().getFieldName()))); } + insn.setFieldType(rename(insn.getFieldType())); } @Override public void visit(InvokeInstruction insn) { diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java index b7c335bb2..6da49863b 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java @@ -26,13 +26,13 @@ public final class ExceptionHandling { private ExceptionHandling() { } - public static native CallSite findCallSiteById(int id); + public static native CallSite findCallSiteById(int id, Address frame); public static void printStack() { Address stackFrame = ShadowStack.getStackTop(); while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); - CallSite callSite = findCallSiteById(callSiteId); + CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; Console.printString(" at "); @@ -74,7 +74,7 @@ public final class ExceptionHandling { Address stackFrame = ShadowStack.getStackTop(); stackLoop: while (stackFrame != null) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); - CallSite callSite = findCallSiteById(callSiteId); + CallSite callSite = findCallSiteById(callSiteId, stackFrame); ExceptionHandler handler = callSite.firstHandler; for (int i = 0; i < callSite.handlerCount; ++i) { @@ -118,7 +118,7 @@ public final class ExceptionHandling { int index = 0; while (stackFrame != null && index < target.length) { int callSiteId = ShadowStack.getCallSiteId(stackFrame); - CallSite callSite = findCallSiteById(callSiteId); + CallSite callSite = findCallSiteById(callSiteId, stackFrame); CallSiteLocation location = callSite.location; StackTraceElement element = createElement(location != null ? location.className : "", location != null ? location.methodName : "", location != null ? location.fileName : null, diff --git a/core/src/main/java/org/teavm/runtime/RuntimeClass.java b/core/src/main/java/org/teavm/runtime/RuntimeClass.java index 76304b91f..47f0f461e 100644 --- a/core/src/main/java/org/teavm/runtime/RuntimeClass.java +++ b/core/src/main/java/org/teavm/runtime/RuntimeClass.java @@ -45,6 +45,8 @@ public class RuntimeClass extends RuntimeObject { public IsSupertypeFunction isSupertypeOf; public InitFunction init; public RuntimeClass parent; + public int superinterfaceCount; + public RuntimeClassPointer superinterfaces; public Address enumValues; public Address layout; public RuntimeObject simpleName; diff --git a/core/src/main/java/org/teavm/runtime/RuntimeClassPointer.java b/core/src/main/java/org/teavm/runtime/RuntimeClassPointer.java new file mode 100644 index 000000000..30347ea5c --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/RuntimeClassPointer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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.runtime; + +import org.teavm.interop.Structure; +import org.teavm.interop.Unmanaged; + +@Unmanaged +public class RuntimeClassPointer extends Structure { + public RuntimeClass value; +} diff --git a/core/src/main/java/org/teavm/vm/IncrementalDirectoryBuildTarget.java b/core/src/main/java/org/teavm/vm/IncrementalDirectoryBuildTarget.java new file mode 100644 index 000000000..04613eaa3 --- /dev/null +++ b/core/src/main/java/org/teavm/vm/IncrementalDirectoryBuildTarget.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019 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.vm; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +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.HashSet; +import java.util.Set; + +public class IncrementalDirectoryBuildTarget implements BuildTarget { + private File directory; + private Set writtenFiles = new HashSet<>(); + private Set formerWrittenFiles = new HashSet<>(); + + public IncrementalDirectoryBuildTarget(File directory) { + this.directory = directory; + } + + public void reset() { + for (String fileName : formerWrittenFiles) { + if (!writtenFiles.contains(fileName)) { + new File(directory, fileName).delete(); + } + } + formerWrittenFiles.clear(); + formerWrittenFiles.addAll(writtenFiles); + writtenFiles.clear(); + } + + @Override + public OutputStream createResource(String fileName) { + writtenFiles.add(fileName); + return new OutputStreamImpl(new File(directory, fileName)); + } + + static class OutputStreamImpl extends OutputStream { + private File file; + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + OutputStreamImpl(File file) { + this.file = file; + } + + @Override + public void write(int b) throws IOException { + checkNotClosed(); + bytes.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + checkNotClosed(); + bytes.write(b, off, len); + } + + @Override + public void close() throws IOException { + checkNotClosed(); + byte[] data = bytes.toByteArray(); + bytes = null; + if (isChanged(file, data)) { + file.getParentFile().mkdirs(); + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { + output.write(data); + } + } + } + + private static boolean isChanged(File file, byte[] data) throws IOException { + if (!file.exists()) { + return true; + } + + InputStream input = new BufferedInputStream(new FileInputStream(file)); + + byte[] buffer = new byte[4096]; + int index = 0; + while (true) { + int bytesRead = input.read(buffer); + if (bytesRead < 0) { + break; + } + if (bytesRead + index > data.length) { + return true; + } + for (int i = 0; i < bytesRead; ++i) { + if (buffer[i] != data[index++]) { + return true; + } + } + } + + return index < data.length; + } + + private void checkNotClosed() throws IOException { + if (bytes == null) { + throw new IOException("Already closed"); + } + } + } +} diff --git a/core/src/main/java/org/teavm/vm/MemoryBuildTarget.java b/core/src/main/java/org/teavm/vm/MemoryBuildTarget.java index 60bf9c5a0..2ef0ceaed 100644 --- a/core/src/main/java/org/teavm/vm/MemoryBuildTarget.java +++ b/core/src/main/java/org/teavm/vm/MemoryBuildTarget.java @@ -16,7 +16,6 @@ package org.teavm.vm; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.LinkedHashMap; @@ -41,7 +40,7 @@ public class MemoryBuildTarget implements BuildTarget { } @Override - public OutputStream createResource(String fileName) throws IOException { + public OutputStream createResource(String fileName) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); data.put(fileName, stream); return stream; diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.h b/core/src/main/resources/org/teavm/backend/c/runtime.h index f7ee1aef7..67da31bea 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.h +++ b/core/src/main/resources/org/teavm/backend/c/runtime.h @@ -1,4 +1,3 @@ -#pragma once #include #include #include @@ -36,6 +35,8 @@ typedef struct TeaVM_Class { int32_t (*isSupertypeOf)(struct TeaVM_Class*); void (*init)(); struct TeaVM_Class* superclass; + int32_t superinterfaceCount; + struct TeaVM_Class** superinterfaces; void* enumValues; void* layout; TeaVM_Object* simpleName; @@ -97,15 +98,35 @@ static inline void* teavm_checkcast(void* obj, int32_t (*cls)(TeaVM_Class*)) { return obj == NULL || cls(TEAVM_CLASS_OF(obj)) ? obj : teavm_throwClassCastException(); } -#define TEAVM_ALLOC_STACK(size) \ - void* teavm__shadowStack__[(size) + 3]; \ - teavm__shadowStack__[0] = teavm_stackTop; \ - teavm__shadowStack__[2] = (void*) size; \ - teavm_stackTop = teavm__shadowStack__ +#ifdef TEAVM_INCREMENTAL + + #define TEAVM_ALLOC_STACK_DEF(size, callSites) \ + void* teavm__shadowStack__[(size) + 4]; \ + teavm__shadowStack__[0] = teavm_stackTop; \ + teavm__shadowStack__[2] = (void*) size; \ + teavm__shadowStack__[3] = (void*) (callSites); \ + teavm_stackTop = teavm__shadowStack__ + + #define TEAVM_STACK_HEADER_ADD_SIZE 1 + +#else + + #define TEAVM_ALLOC_STACK(size) \ + void* teavm__shadowStack__[(size) + 3]; \ + teavm__shadowStack__[0] = teavm_stackTop; \ + teavm__shadowStack__[2] = (void*) size; \ + teavm_stackTop = teavm__shadowStack__ + + #define TEAVM_STACK_HEADER_ADD_SIZE 0 + +#endif + #define TEAVM_RELEASE_STACK teavm_stackTop = teavm__shadowStack__[0] -#define TEAVM_GC_ROOT(index, ptr) teavm__shadowStack__[3 + (index)] = ptr -#define TEAVM_GC_ROOT_RELEASE(index) teavm__shadowStack__[3 + (index)] = NULL +#define TEAVM_GC_ROOT(index, ptr) teavm__shadowStack__[3 + TEAVM_STACK_HEADER_ADD_SIZE + (index)] = ptr +#define TEAVM_GC_ROOT_RELEASE(index) teavm__shadowStack__[3 + TEAVM_STACK_HEADER_ADD_SIZE + (index)] = NULL +#define TEAVM_GC_ROOTS_COUNT(ptr) ((int32_t) (intptr_t) ((void**) (ptr))[2]) +#define TEAVM_GET_GC_ROOTS(ptr) (((void**) (ptr)) + 3 + TEAVM_STACK_HEADER_ADD_SIZE) #define TEAVM_CALL_SITE(id) (teavm__shadowStack__[1] = (void*) (id)) #define TEAVM_EXCEPTION_HANDLER ((int32_t) (intptr_t) (teavm__shadowStack__[1])) #define TEAVM_SET_EXCEPTION_HANDLER(frame, id) (((void**) (frame))[1] = (void*) (intptr_t) (id)) @@ -113,6 +134,11 @@ static inline void* teavm_checkcast(void* obj, int32_t (*cls)(TeaVM_Class*)) { #define TEAVM_ADDRESS_ADD(address, offset) ((char *) (address) + (offset)) #define TEAVM_STRUCTURE_ADD(structure, address, offset) (((structure*) (address)) + offset) +#define TEAVM_NULL_STRING { \ + .characters = NULL, \ + .hashCode = 0 \ +} + #define TEAVM_STRING(length, hash, s) { \ .characters = (TeaVM_Array*) & (struct { TeaVM_Array hdr; char16_t data[(length) + 1]; }) { \ .hdr = { .size = length }, \ diff --git a/pom.xml b/pom.xml index 50137abb3..8f1bc46ac 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ jso/impl html4j platform + tools/c-incremental tools/core tools/maven tools/chrome-rdp diff --git a/tools/c-incremental/pom.xml b/tools/c-incremental/pom.xml new file mode 100644 index 000000000..b9cfa46f4 --- /dev/null +++ b/tools/c-incremental/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.6.0-SNAPSHOT + ../.. + + teavm-c-incremental + + TeaVM Incremental C builder + Incremental generator of C code + + + + commons-io + commons-io + true + + + org.ow2.asm + asm-commons + true + + + org.ow2.asm + asm-util + true + + + com.carrotsearch + hppc + 0.7.3 + true + + + org.mozilla + rhino + true + + + + org.teavm + teavm-core + ${project.version} + + + org.teavm + teavm-tooling + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../../checkstyle.xml + config_loc=${basedir}/../.. + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/BuilderListener.java b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/BuilderListener.java new file mode 100644 index 000000000..eae2c1183 --- /dev/null +++ b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/BuilderListener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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.tooling.c.incremental; + +import org.teavm.tooling.builder.BuildResult; + +public interface BuilderListener { + void compilationStarted(); + + void compilationProgress(double progress); + + void compilationComplete(BuildResult result); + + void compilationCancelled(); +} diff --git a/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java new file mode 100644 index 000000000..5f9aed473 --- /dev/null +++ b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/IncrementalCBuilder.java @@ -0,0 +1,510 @@ +/* + * Copyright 2019 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.tooling.c.incremental; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import org.teavm.backend.c.CTarget; +import org.teavm.backend.c.generate.SimpleStringPool; +import org.teavm.cache.InMemoryMethodNodeCache; +import org.teavm.cache.InMemoryProgramCache; +import org.teavm.cache.InMemorySymbolTable; +import org.teavm.cache.MemoryCachedClassReaderSource; +import org.teavm.dependency.FastDependencyAnalyzer; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.PreOptimizingClassHolderSource; +import org.teavm.model.ReferenceCache; +import org.teavm.parsing.ClasspathResourceMapper; +import org.teavm.parsing.resource.ClasspathResourceReader; +import org.teavm.parsing.resource.ResourceClassHolderMapper; +import org.teavm.tooling.EmptyTeaVMToolLog; +import org.teavm.tooling.TeaVMProblemRenderer; +import org.teavm.tooling.TeaVMToolLog; +import org.teavm.tooling.builder.SimpleBuildResult; +import org.teavm.tooling.util.FileSystemWatcher; +import org.teavm.vm.IncrementalDirectoryBuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; +import org.teavm.vm.TeaVMOptimizationLevel; +import org.teavm.vm.TeaVMPhase; +import org.teavm.vm.TeaVMProgressFeedback; +import org.teavm.vm.TeaVMProgressListener; + +public class IncrementalCBuilder { + private String mainClass; + private String[] classPath; + private int minHeapSize = 32; + private String targetPath; + + private IncrementalDirectoryBuildTarget buildTarget; + private FileSystemWatcher watcher; + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + private MemoryCachedClassReaderSource classSource; + + private ReferenceCache referenceCache = new ReferenceCache(); + private SimpleStringPool stringPool = new SimpleStringPool(); + private InMemorySymbolTable symbolTable = new InMemorySymbolTable(); + private InMemorySymbolTable fileSymbolTable = new InMemorySymbolTable(); + private InMemorySymbolTable variableSymbolTable = new InMemorySymbolTable(); + private InMemoryProgramCache programCache; + private InMemoryMethodNodeCache astCache; + + private List listeners = new ArrayList<>(); + private final Set progressHandlers = new LinkedHashSet<>(); + + private int lastReachedClasses; + private final Object statusLock = new Object(); + private volatile boolean cancelRequested; + private volatile boolean stopped; + private boolean compiling; + private double progress; + private boolean waiting; + private Thread buildThread; + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public void setClassPath(String[] classPath) { + this.classPath = classPath; + } + + public void setMinHeapSize(int minHeapSize) { + this.minHeapSize = minHeapSize; + } + + public void setTargetPath(String targetPath) { + this.targetPath = targetPath; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public void addProgressHandler(ProgressHandler handler) { + synchronized (progressHandlers) { + progressHandlers.add(handler); + } + + double progress; + synchronized (statusLock) { + if (!compiling) { + return; + } + progress = this.progress; + } + + handler.progress(progress); + } + + public void removeProgressHandler(ProgressHandler handler) { + synchronized (progressHandlers) { + progressHandlers.remove(handler); + } + } + + public void addListener(BuilderListener listener) { + listeners.add(listener); + } + + public void invalidateCache() { + synchronized (statusLock) { + if (compiling) { + return; + } + astCache.invalidate(); + programCache.invalidate(); + classSource.invalidate(); + symbolTable.invalidate(); + fileSymbolTable.invalidate(); + } + } + + public void buildProject() { + synchronized (statusLock) { + if (waiting) { + buildThread.interrupt(); + } + } + } + + public void cancelBuild() { + synchronized (statusLock) { + if (compiling) { + cancelRequested = true; + } + } + } + + public void start() { + buildThread = new Thread(this::run); + buildThread.start(); + } + + public void stop() { + stopped = true; + synchronized (statusLock) { + if (waiting) { + buildThread.interrupt(); + } + } + } + + private void run() { + try { + initBuilder(); + + while (!stopped) { + buildOnce(); + + if (stopped) { + break; + } + + try { + synchronized (statusLock) { + waiting = true; + } + watcher.waitForChange(750); + synchronized (statusLock) { + waiting = false; + } + log.info("Changes detected. Recompiling."); + } catch (InterruptedException e) { + if (stopped) { + break; + } + log.info("Build triggered by user"); + } + + List staleClasses = getChangedClasses(watcher.grabChangedFiles()); + if (staleClasses.size() > 15) { + List displayedStaleClasses = staleClasses.subList(0, 10); + log.debug("Following classes changed (" + staleClasses.size() + "): " + + String.join(", ", displayedStaleClasses) + " and more..."); + } else { + log.debug("Following classes changed (" + staleClasses.size() + "): " + + String.join(", ", staleClasses)); + } + + classSource.evict(staleClasses); + } + log.info("Build process stopped"); + } catch (Throwable e) { + log.error("Compile server crashed", e); + } finally { + shutdownBuilder(); + } + } + + private List getChangedClasses(Collection changedFiles) { + List result = new ArrayList<>(); + + for (File file : changedFiles) { + String path = file.getPath().replace('\\', '/'); + if (!path.endsWith(".class")) { + continue; + } + + String prefix = Arrays.stream(classPath) + .filter(path::startsWith) + .findFirst() + .orElse(""); + int start = prefix.length(); + if (start < path.length() && path.charAt(start) == '/') { + ++start; + } + + path = path.substring(start, path.length() - ".class".length()).replace('/', '.'); + result.add(path); + } + + return result; + } + + private void initBuilder() throws IOException { + buildTarget = new IncrementalDirectoryBuildTarget(new File(targetPath)); + watcher = new FileSystemWatcher(classPath); + + classSource = createCachedSource(); + astCache = new InMemoryMethodNodeCache(referenceCache, symbolTable, fileSymbolTable, variableSymbolTable); + programCache = new InMemoryProgramCache(referenceCache, symbolTable, fileSymbolTable, variableSymbolTable); + } + + private void shutdownBuilder() { + try { + watcher.dispose(); + } catch (IOException e) { + log.debug("Exception caught", e); + } + classSource = null; + watcher = null; + astCache = null; + programCache = null; + + log.info("Build thread complete"); + } + + private MemoryCachedClassReaderSource createCachedSource() { + return new MemoryCachedClassReaderSource(referenceCache, symbolTable, fileSymbolTable, + variableSymbolTable); + } + + private void buildOnce() { + fireBuildStarted(); + reportProgress(0); + + ClassLoader classLoader = initClassLoader(); + ClasspathResourceReader reader = new ClasspathResourceReader(classLoader); + ResourceClassHolderMapper rawMapper = new ResourceClassHolderMapper(reader, referenceCache); + Function classPathMapper = new ClasspathResourceMapper(classLoader, referenceCache, + rawMapper); + classSource.setProvider(name -> PreOptimizingClassHolderSource.optimize(classPathMapper, name)); + + long startTime = System.currentTimeMillis(); + CTarget cTarget = new CTarget(stringPool); + + TeaVM vm = new TeaVMBuilder(cTarget) + .setReferenceCache(referenceCache) + .setClassLoader(classLoader) + .setClassSource(classSource) + .setDependencyAnalyzerFactory(FastDependencyAnalyzer::new) + .setClassSourcePacker(this::packClasses) + .build(); + + cTarget.setIncremental(true); + cTarget.setMinHeapSize(minHeapSize * 1024 * 1024); + vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); + vm.setCacheStatus(classSource); + vm.addVirtualMethods(m -> true); + vm.setProgressListener(progressListener); + vm.setProgramCache(programCache); + vm.installPlugins(); + + vm.setLastKnownClasses(lastReachedClasses); + vm.entryPoint(mainClass); + + log.info("Starting build"); + progressListener.last = 0; + progressListener.lastTime = System.currentTimeMillis(); + vm.build(buildTarget, ""); + + postBuild(vm, startTime); + } + + private void postBuild(TeaVM vm, long startTime) { + if (!vm.wasCancelled()) { + log.info("Recompiled stale methods: " + programCache.getPendingItemsCount()); + fireBuildComplete(vm); + if (vm.getProblemProvider().getSevereProblems().isEmpty()) { + log.info("Build complete successfully"); + lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size(); + classSource.commit(); + programCache.commit(); + astCache.commit(); + reportCompilationComplete(true); + } else { + log.info("Build complete with errors"); + reportCompilationComplete(false); + } + printStats(vm, startTime); + TeaVMProblemRenderer.describeProblems(vm, log); + } else { + log.info("Build cancelled"); + fireBuildCancelled(); + } + + astCache.discard(); + programCache.discard(); + buildTarget.reset(); + stringPool.reset(); + cancelRequested = false; + } + + private void printStats(TeaVM vm, long startTime) { + if (vm.getWrittenClasses() != null) { + int classCount = vm.getWrittenClasses().getClassNames().size(); + int methodCount = 0; + for (String className : vm.getWrittenClasses().getClassNames()) { + ClassReader cls = vm.getWrittenClasses().get(className); + methodCount += cls.getMethods().size(); + } + + log.info("Classes compiled: " + classCount); + log.info("Methods compiled: " + methodCount); + } + + log.info("Compilation took " + (System.currentTimeMillis() - startTime) + " ms"); + } + + private ClassReaderSource packClasses(ClassReaderSource source, Collection classNames) { + MemoryCachedClassReaderSource packedSource = createCachedSource(); + packedSource.setProvider(source::get); + for (String className : classNames) { + packedSource.populate(className); + } + packedSource.setProvider(null); + return packedSource; + } + + private ClassLoader initClassLoader() { + URL[] urls = new URL[classPath.length]; + try { + for (int i = 0; i < classPath.length; i++) { + urls[i] = new File(classPath[i]).toURI().toURL(); + } + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + return new URLClassLoader(urls, IncrementalCBuilder.class.getClassLoader()); + } + + private void reportProgress(double progress) { + synchronized (statusLock) { + if (compiling && this.progress == progress) { + return; + } + compiling = true; + this.progress = progress; + } + + ProgressHandler[] handlers; + synchronized (progressHandlers) { + handlers = progressHandlers.toArray(new ProgressHandler[0]); + } + + for (ProgressHandler handler : handlers) { + handler.progress(progress); + } + + for (BuilderListener listener : listeners) { + listener.compilationProgress(progress); + } + } + + private void reportCompilationComplete(boolean success) { + synchronized (statusLock) { + if (!compiling) { + return; + } + compiling = false; + } + + ProgressHandler[] handlers; + synchronized (progressHandlers) { + handlers = progressHandlers.toArray(new ProgressHandler[0]); + } + + for (ProgressHandler handler : handlers) { + handler.complete(success); + } + } + + private void fireBuildStarted() { + for (BuilderListener listener : listeners) { + listener.compilationStarted(); + } + } + + private void fireBuildCancelled() { + for (BuilderListener listener : listeners) { + listener.compilationCancelled(); + } + } + + private void fireBuildComplete(TeaVM vm) { + SimpleBuildResult result = new SimpleBuildResult(vm, Collections.emptyList()); + for (BuilderListener listener : listeners) { + listener.compilationComplete(result); + } + } + + private final ProgressListenerImpl progressListener = new ProgressListenerImpl(); + + class ProgressListenerImpl implements TeaVMProgressListener { + private int start; + private int end; + private int phaseLimit; + private int last; + private long lastTime; + + @Override + public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) { + switch (phase) { + case DEPENDENCY_ANALYSIS: + start = 0; + end = 500; + break; + case COMPILING: + start = 500; + end = 1000; + break; + } + phaseLimit = count; + return progressReached(0); + } + + @Override + public TeaVMProgressFeedback progressReached(int progress) { + int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit; + if (current != last) { + if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) { + lastTime = System.currentTimeMillis(); + last = current; + reportProgress(current / 10.0); + } + } + return getResult(); + } + + private TeaVMProgressFeedback getResult() { + if (cancelRequested) { + log.info("Trying to cancel compilation due to user request"); + return TeaVMProgressFeedback.CANCEL; + } + + if (stopped) { + log.info("Trying to cancel compilation due to server stopping"); + return TeaVMProgressFeedback.CANCEL; + } + + try { + if (watcher.hasChanges()) { + log.info("Changes detected, cancelling build"); + return TeaVMProgressFeedback.CANCEL; + } + } catch (IOException e) { + log.info("IO error occurred", e); + return TeaVMProgressFeedback.CANCEL; + } + + return TeaVMProgressFeedback.CONTINUE; + } + } +} diff --git a/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/ProgressHandler.java b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/ProgressHandler.java new file mode 100644 index 000000000..39109b1d2 --- /dev/null +++ b/tools/c-incremental/src/main/java/org/teavm/tooling/c/incremental/ProgressHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 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.tooling.c.incremental; + +public interface ProgressHandler { + void complete(boolean success); + + void progress(double value); +} diff --git a/tools/cli/pom.xml b/tools/cli/pom.xml index d0b311d2e..95a56514e 100644 --- a/tools/cli/pom.xml +++ b/tools/cli/pom.xml @@ -56,6 +56,11 @@ teavm-devserver ${project.version} + + org.teavm + teavm-c-incremental + ${project.version} + org.teavm teavm-jso-impl diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java new file mode 100644 index 000000000..098a53022 --- /dev/null +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMCBuilderRunner.java @@ -0,0 +1,139 @@ +/* + * Copyright 2019 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.cli; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.teavm.tooling.ConsoleTeaVMToolLog; +import org.teavm.tooling.c.incremental.IncrementalCBuilder; + +public class TeaVMCBuilderRunner { + private static Options options = new Options(); + private IncrementalCBuilder builder; + private CommandLine commandLine; + + static { + setupOptions(); + } + + @SuppressWarnings("static-access") + private static void setupOptions() { + options.addOption(OptionBuilder + .withArgName("directory") + .hasArg() + .withDescription("a directory in which generated C files will be placed") + .withLongOpt("targetdir") + .create('d')); + options.addOption(OptionBuilder + .withArgName("classpath") + .hasArgs() + .withDescription("classpath element (either directory or jar file)") + .withLongOpt("classpath") + .create('p')); + options.addOption(OptionBuilder + .withDescription("display more messages on server log") + .withLongOpt("verbose") + .create('v')); + options.addOption(OptionBuilder + .withLongOpt("min-heap") + .withArgName("size") + .hasArg() + .withDescription("Minimum heap size in bytes") + .create()); + } + + private TeaVMCBuilderRunner(CommandLine commandLine) { + this.commandLine = commandLine; + builder = new IncrementalCBuilder(); + } + + public static void main(String[] args) { + if (args.length == 0) { + printUsage(); + return; + } + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + printUsage(); + return; + } + + TeaVMCBuilderRunner runner = new TeaVMCBuilderRunner(commandLine); + runner.parseArguments(); + runner.runAll(); + } + + private void parseArguments() { + parseClassPathOptions(); + parseOutputOptions(); + parseHeap(); + + builder.setLog(new ConsoleTeaVMToolLog(commandLine.hasOption('v'))); + + String[] args = commandLine.getArgs(); + if (args.length != 1) { + System.err.println("Unexpected arguments"); + printUsage(); + } else if (args.length == 1) { + builder.setMainClass(args[0]); + } + } + + private void parseOutputOptions() { + if (commandLine.hasOption("d")) { + builder.setTargetPath(commandLine.getOptionValue("d")); + } + } + + private void parseClassPathOptions() { + if (commandLine.hasOption('p')) { + builder.setClassPath(commandLine.getOptionValues('p')); + } + } + + private void parseHeap() { + if (commandLine.hasOption("min-heap")) { + int size; + try { + size = Integer.parseInt(commandLine.getOptionValue("min-heap")); + } catch (NumberFormatException e) { + System.err.print("Wrong heap size"); + printUsage(); + return; + } + builder.setMinHeapSize(size); + } + } + + private void runAll() { + builder.start(); + } + + private static void printUsage() { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java " + TeaVMCBuilderRunner.class.getName() + " [OPTIONS] [qualified.main.Class]", + options); + System.exit(-1); + } +} diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java b/tools/core/src/main/java/org/teavm/tooling/builder/SimpleBuildResult.java similarity index 87% rename from tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java rename to tools/core/src/main/java/org/teavm/tooling/builder/SimpleBuildResult.java index 2df2ff254..1343e1f28 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/SimpleBuildResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Alexey Andreev. + * Copyright 2019 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,22 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.devserver; +package org.teavm.tooling.builder; import java.util.Collection; import java.util.List; import org.teavm.callgraph.CallGraph; import org.teavm.diagnostics.ProblemProvider; import org.teavm.tooling.InstructionLocationReader; -import org.teavm.tooling.builder.BuildResult; import org.teavm.vm.TeaVM; -class CodeServletBuildResult implements BuildResult { +public class SimpleBuildResult implements BuildResult { private TeaVM vm; private List generatedFiles; private Collection usedResources; - public CodeServletBuildResult(TeaVM vm, List generatedFiles) { + public SimpleBuildResult(TeaVM vm, List generatedFiles) { this.vm = vm; this.generatedFiles = generatedFiles; } diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java index 0a0994f39..acffed5da 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java @@ -86,6 +86,7 @@ import org.teavm.parsing.resource.ResourceClassHolderMapper; import org.teavm.tooling.EmptyTeaVMToolLog; import org.teavm.tooling.TeaVMProblemRenderer; import org.teavm.tooling.TeaVMToolLog; +import org.teavm.tooling.builder.SimpleBuildResult; import org.teavm.tooling.util.FileSystemWatcher; import org.teavm.vm.MemoryBuildTarget; import org.teavm.vm.TeaVM; @@ -995,7 +996,7 @@ public class CodeServlet extends HttpServlet { } private void fireBuildComplete(TeaVM vm) { - CodeServletBuildResult result = new CodeServletBuildResult(vm, new ArrayList<>(buildTarget.getNames())); + SimpleBuildResult result = new SimpleBuildResult(vm, new ArrayList<>(buildTarget.getNames())); for (DevServerListener listener : listeners) { listener.compilationComplete(result); }