From 492fd004af8580d99d683d9948b203c1187e7cea Mon Sep 17 00:00:00 2001 From: Alexey Andreev <konsoletyper@gmail.com> Date: Tue, 13 Aug 2019 16:53:14 +0300 Subject: [PATCH] C: add ability to write heap dump when application crashes --- .../java/org/teavm/backend/c/CTarget.java | 74 +- .../backend/c/generate/CallSiteGenerator.java | 70 +- .../backend/c/generate/ClassGenerator.java | 164 +++- .../backend/c/generate/GenerationContext.java | 8 +- .../intrinsic/ExceptionHandlingIntrinsic.java | 1 - .../teavm/backend/c/util/GCVisualizer.java | 2 +- .../backend/c/util/HeapDumpConverter.java | 912 ++++++++++++++++++ .../c/util/json/JsonAllErrorVisitor.java | 61 ++ .../backend/c/util/json/JsonArrayVisitor.java | 29 + .../backend/c/util/json/JsonConsumer.java | 51 + .../c/util/json/JsonErrorReporter.java | 20 + .../c/util/json/JsonObjectVisitor.java | 29 + .../teavm/backend/c/util/json/JsonParser.java | 415 ++++++++ .../c/util/json/JsonPropertyVisitor.java | 40 + .../c/util/json/JsonSyntaxException.java | 41 + .../c/util/json/JsonVisitingConsumer.java | 131 +++ .../backend/c/util/json/JsonVisitor.java | 48 + .../main/java/org/teavm/runtime/CallSite.java | 8 +- .../org/teavm/runtime/CallSiteLocation.java | 8 +- .../org/teavm/runtime/ExceptionHandler.java | 8 +- core/src/main/java/org/teavm/runtime/GC.java | 9 + .../java/org/teavm/runtime/MemoryTrace.java | 2 + .../org/teavm/runtime/MethodLocation.java | 8 +- .../java/org/teavm/runtime/StringPtr.java | 6 +- .../main/resources/org/teavm/backend/c/heap.c | 720 ++++++++++++++ .../resources/org/teavm/backend/c/runtime.c | 327 +------ .../resources/org/teavm/backend/c/runtime.h | 118 ++- .../org/teavm/backend/c/stringhash.c | 19 +- .../c/incremental/IncrementalCBuilder.java | 1 + .../main/java/org/teavm/cli/TeaVMRunner.java | 3 + .../java/org/teavm/tooling/TeaVMTool.java | 6 + .../teavm/tooling/builder/BuildStrategy.java | 2 + .../builder/InProcessBuildStrategy.java | 7 + .../tooling/builder/RemoteBuildStrategy.java | 5 + .../org/teavm/tooling/daemon/BuildDaemon.java | 1 + .../tooling/daemon/RemoteBuildRequest.java | 1 + .../org/teavm/maven/TeaVMCompileMojo.java | 4 + 37 files changed, 2909 insertions(+), 450 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/c/util/HeapDumpConverter.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonAllErrorVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonArrayVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonConsumer.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonErrorReporter.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonObjectVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonParser.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonPropertyVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonSyntaxException.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonVisitingConsumer.java create mode 100644 core/src/main/java/org/teavm/backend/c/util/json/JsonVisitor.java create mode 100644 core/src/main/resources/org/teavm/backend/c/heap.c 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 6de45d62a..f1abd9539 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -39,7 +39,6 @@ import org.teavm.backend.c.generate.BufferedCodeWriter; import org.teavm.backend.c.generate.CallSiteGenerator; import org.teavm.backend.c.generate.ClassGenerator; import org.teavm.backend.c.generate.CodeGenerationVisitor; -import org.teavm.backend.c.generate.CodeGeneratorUtil; import org.teavm.backend.c.generate.CodeWriter; import org.teavm.backend.c.generate.GenerationContext; import org.teavm.backend.c.generate.IncludeManager; @@ -159,6 +158,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private boolean lineNumbersGenerated; private SimpleStringPool stringPool; private boolean longjmpUsed = true; + private boolean heapDump; private List<CallSiteDescriptor> callSites = new ArrayList<>(); public void setMinHeapSize(int minHeapSize) { @@ -177,6 +177,10 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { this.longjmpUsed = longjmpUsed; } + public void setHeapDump(boolean heapDump) { + this.heapDump = heapDump; + } + public void setAstCache(MethodNodeCache astCache) { this.astCache = astCache; } @@ -239,6 +243,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { RuntimeClass.class, Address.class, int.class, RuntimeArray.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "<clinit>", void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(GC.class, "fixHeap", void.class)).use(); dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwException", Throwable.class, void.class)).use(); @@ -362,7 +367,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { GenerationContext context = new GenerationContext(vtableProvider, characteristics, controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, intrinsics, generators, asyncMethods::contains, buildTarget, incremental, longjmpUsed, - vmAssertions); + vmAssertions, vmAssertions || heapDump); BufferedCodeWriter runtimeWriter = new BufferedCodeWriter(false); BufferedCodeWriter runtimeHeaderWriter = new BufferedCodeWriter(false); @@ -378,6 +383,9 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { if (vmAssertions) { runtimeHeaderWriter.println("#define TEAVM_MEMORY_TRACE 1"); } + if (heapDump) { + runtimeHeaderWriter.println("#define TEAVM_HEAP_DUMP 1"); + } emitResource(runtimeHeaderWriter, "runtime.h"); ClassGenerator classGenerator = new ClassGenerator(context, tagRegistry, decompiler, @@ -404,6 +412,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { copyResource("references.c", buildTarget); copyResource("date.c", buildTarget); copyResource("file.c", buildTarget); + copyResource("heap.c", buildTarget); generateCallSites(buildTarget, context, classes.getClassNames()); generateStrings(buildTarget, context); @@ -508,46 +517,25 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private void generateCallSites(BuildTarget buildTarget, GenerationContext context, Collection<? extends String> classNames) throws IOException { BufferedCodeWriter writer = new BufferedCodeWriter(false); - BufferedCodeWriter headerWriter = new BufferedCodeWriter(false); IncludeManager includes = new SimpleIncludeManager(writer); includes.init("callsites.c"); - includes.includePath("callsites.h"); - headerWriter.println("#pragma once"); - IncludeManager headerIncludes = new SimpleIncludeManager(headerWriter); - headerIncludes.init("callsites.h"); - headerIncludes.includePath("runtime.h"); - headerIncludes.includeClass(CallSiteGenerator.CALL_SITE); - - if (incremental) { - generateIncrementalCallSites(context, headerWriter); - } else { - generateFastCallSites(context, writer, includes, headerWriter, classNames); + if (!incremental) { + generateFastCallSites(context, writer, includes, 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<? extends String> 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)"); - + Collection<? extends String> classNames) { List<? extends CallSiteDescriptor> callSites = context.isLongjmp() ? this.callSites : CallSiteDescriptor.extract(context.getClassSource(), classNames); new CallSiteGenerator(context, writer, includes, "teavm_callSites").generate(callSites); } - private void generateIncrementalCallSites(GenerationContext context, CodeWriter headerWriter) { - String callSiteName = context.getNames().forClass(CallSiteGenerator.CALL_SITE); - headerWriter.println("#define TEAVM_FIND_CALLSITE(id, frame) ((" + callSiteName - + "*) ((TeaVM_StackFrame*) (frame))->callSites + id)"); - } - private void generateStrings(BuildTarget buildTarget, GenerationContext context) throws IOException { BufferedCodeWriter writer = new BufferedCodeWriter(false); IncludeManager includes = new SimpleIncludeManager(writer); @@ -709,6 +697,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { files.add("references.c"); files.add("date.c"); files.add("file.c"); + files.add("heap.c"); for (String className : classes.getClassNames()) { files.add(ClassGenerator.fileName(className) + ".c"); @@ -724,7 +713,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private void generateArrayOfClassReferences(GenerationContext context, CodeWriter writer, IncludeManager includes, List<? extends ValueType> types) { - writer.print("static TeaVM_Class* teavm_classReferences[" + types.size() + "] = {").indent(); + writer.print("TeaVM_Class* teavm_classReferences[" + types.size() + "] = {").indent(); boolean first = true; for (ValueType type : types) { if (!first) { @@ -740,6 +729,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { writer.println(); } writer.outdent().println("};"); + + writer.println("int32_t teavm_classReferencesCount = " + types.size() + ";"); } private void generateMain(GenerationContext context, CodeWriter writer, IncludeManager includes, @@ -753,7 +744,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { writer.println("teavm_beforeInit();"); writer.println("teavm_initHeap(" + minHeapSize + ");"); - generateVirtualTableHeaders(context, writer, types); + generateVirtualTableHeaders(context, writer); writer.println("teavm_initStringPool();"); for (ValueType type : types) { includes.includeType(type); @@ -798,23 +789,16 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { } } - private void generateVirtualTableHeaders(GenerationContext context, CodeWriter writer, - List<? extends ValueType> types) { - writer.println("teavm_beforeClasses = (char*) teavm_classReferences[0];"); - writer.println("for (int i = 1; i < " + types.size() + "; ++i) {").indent(); - writer.println("char* c = (char*) teavm_classReferences[i];"); - writer.println("if (c < teavm_beforeClasses) teavm_beforeClasses = c;"); - writer.outdent().println("}"); - writer.println("teavm_beforeClasses -= 4096;"); - - String classClassName = context.getNames().forClassInstance(ValueType.object("java.lang.Class")); - writer.print("int32_t classHeader = TEAVM_PACK_CLASS(&" + classClassName + ") | "); - CodeGeneratorUtil.writeIntValue(writer, RuntimeObject.GC_MARKED); - writer.println(";"); - - writer.println("for (int i = 0; i < " + types.size() + "; ++i) {").indent(); - writer.println("teavm_classReferences[i]->parent.header = classHeader;"); - writer.outdent().println("}"); + private void generateVirtualTableHeaders(GenerationContext context, CodeWriter writer) { + writer.println("teavm_classClass = (TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.object("java.lang.Class")) + ";"); + writer.println("teavm_objectClass = (TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.object("java.lang.Object")) + ";"); + writer.println("teavm_stringClass = (TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.object("java.lang.String")) + ";"); + writer.println("teavm_charArrayClass = (TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.arrayOf(ValueType.CHARACTER)) + ";"); + writer.println("teavm_initClasses();"); } private void generateFiberStart(GenerationContext context, CodeWriter writer, IncludeManager includes) { 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 251ba4041..aa0c4be30 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 @@ -20,17 +20,12 @@ import com.carrotsearch.hppc.ObjectIntMap; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import org.teavm.model.FieldReference; import org.teavm.model.ValueType; import org.teavm.model.lowlevel.CallSiteDescriptor; import org.teavm.model.lowlevel.CallSiteLocation; import org.teavm.model.lowlevel.ExceptionHandlerDescriptor; public class CallSiteGenerator { - public static final String CALL_SITE = "org.teavm.runtime.CallSite"; - private static final String CALL_SITE_LOCATION = "org.teavm.runtime.CallSiteLocation"; - private static final String METHOD_LOCATION = "org.teavm.runtime.MethodLocation"; - private static final String EXCEPTION_HANDLER = "org.teavm.runtime.ExceptionHandler"; private GenerationContext context; private CodeWriter writer; @@ -40,9 +35,6 @@ public class CallSiteGenerator { private List<HandlerContainer> exceptionHandlers = new ArrayList<>(); private ObjectIntMap<MethodLocation> methodLocationMap = new ObjectIntHashMap<>(); private List<MethodLocation> methodLocations = new ArrayList<>(); - private String callSiteLocationName; - private String methodLocationName; - private String exceptionHandlerName; private String callSitesName; private boolean isStatic; @@ -51,9 +43,6 @@ public class CallSiteGenerator { this.context = context; this.writer = writer; this.includes = includes; - callSiteLocationName = context.getNames().forClass(CALL_SITE_LOCATION); - methodLocationName = context.getNames().forClass(METHOD_LOCATION); - exceptionHandlerName = context.getNames().forClass(EXCEPTION_HANDLER); this.callSitesName = callSitesName; } @@ -76,16 +65,11 @@ public class CallSiteGenerator { } private void generateCallSites(List<? extends CallSiteDescriptor> callSites) { - String callSiteName = context.getNames().forClass(CALL_SITE); - - includes.includeClass(CALL_SITE); includes.includePath("strings.h"); if (isStatic) { writer.print("static "); } - writer.print(callSiteName).print(" " + callSitesName + "[" + callSites.size() + "] = {").indent(); - String firstHandlerName = fieldName(CALL_SITE, "firstHandler"); - String locationName = fieldName(CALL_SITE, "location"); + writer.print("TeaVM_CallSite " + callSitesName + "[" + callSites.size() + "] = {").indent(); boolean first = true; for (CallSiteDescriptor callSite : callSites) { @@ -108,8 +92,8 @@ public class CallSiteGenerator { ? "exceptionHandlers_" + callSitesName + " + " + exceptionHandlers.size() : "NULL"; writer.println().print("{ "); - writer.print(".").print(firstHandlerName).print(" = ").print(firstHandlerExpr).print(", "); - writer.print(".").print(locationName).print(" = ") + writer.print(".firstHandler = ").print(firstHandlerExpr).print(", "); + writer.print(".location = ") .print(locationIndex >= 0 ? "callSiteLocations_" + callSitesName + " + " + locationIndex : "NULL"); writer.print(" }"); @@ -128,13 +112,9 @@ public class CallSiteGenerator { if (locations.isEmpty()) { return; } - includes.includeClass(CALL_SITE_LOCATION); - writer.print("static ").print(callSiteLocationName).print(" callSiteLocations_" + callSitesName + writer.print("static TeaVM_CallSiteLocation callSiteLocations_" + callSitesName + "[" + locations.size() + "] = {").indent(); - String methodName = fieldName(CALL_SITE_LOCATION, "method"); - String lineNumberName = fieldName(CALL_SITE_LOCATION, "lineNumber"); - boolean first = true; for (CallSiteLocation location : locations) { if (!first) { @@ -151,10 +131,9 @@ public class CallSiteGenerator { methodLocationMap.put(methodLocation, index); methodLocations.add(methodLocation); } - writer.print(".").print(methodName).print(" = ").print("methodLocations_" + callSitesName + " + " + index) + writer.print(".method = ").print("methodLocations_" + callSitesName + " + " + index) .print(", "); - writer.print(".").print(lineNumberName).print(" = ") - .print(String.valueOf(location.getLineNumber())); + writer.print(".lineNumber = ").print(String.valueOf(location.getLineNumber())); writer.print(" }"); } @@ -167,14 +146,9 @@ public class CallSiteGenerator { return; } - includes.includeClass(METHOD_LOCATION); - writer.print("static ").print(methodLocationName).print(" methodLocations_" + callSitesName + writer.print("static TeaVM_MethodLocation methodLocations_" + callSitesName + "[" + methodLocations.size() + "] = {").indent(); - String fileNameName = fieldName(METHOD_LOCATION, "fileName"); - String classNameName = fieldName(METHOD_LOCATION, "className"); - String methodNameName = fieldName(METHOD_LOCATION, "methodName"); - boolean first = true; for (MethodLocation location : methodLocations) { if (!first) { @@ -183,12 +157,9 @@ public class CallSiteGenerator { first = false; writer.println().print("{ "); - writer.print(".").print(fileNameName).print(" = ") - .print(getStringExpr(location.file)).print(", "); - writer.print(".").print(classNameName).print(" = ") - .print(getStringExpr(location.className)).print(", "); - writer.print(".").print(methodNameName).print(" = ") - .print(getStringExpr(location.methodName)); + writer.print(".fileName = ").print(getStringExpr(location.file)).print(", "); + writer.print(".className = ").print(getStringExpr(location.className)).print(", "); + writer.print(".methodName = ").print(getStringExpr(location.methodName)); writer.print(" }"); } @@ -200,15 +171,10 @@ public class CallSiteGenerator { if (exceptionHandlers.isEmpty()) { return; } - includes.includeClass(EXCEPTION_HANDLER); String name = "exceptionHandlers_" + callSitesName; - writer.print("static ").print(exceptionHandlerName).print(" " + name + "[" + writer.print("static TeaVM_ExceptionHandler " + name + "[" + exceptionHandlers.size() + "] = {").indent(); - String idName = fieldName(EXCEPTION_HANDLER, "id"); - String exceptionClassName = fieldName(EXCEPTION_HANDLER, "exceptionClass"); - String nextName = fieldName(EXCEPTION_HANDLER, "next"); - boolean first = true; for (HandlerContainer handler : exceptionHandlers) { if (!first) { @@ -222,12 +188,12 @@ public class CallSiteGenerator { includes.includeClass(handler.descriptor.getClassName()); } String classExpr = handler.descriptor.getClassName() != null - ? "&" + context.getNames().forClassInstance(ValueType.object(handler.descriptor.getClassName())) + ? "(TeaVM_Class*) &" + + context.getNames().forClassInstance(ValueType.object(handler.descriptor.getClassName())) : "NULL"; - writer.print(".").print(idName).print(" = ").print(String.valueOf(handler.descriptor.getId())).print(", "); - writer.print(".").print(exceptionClassName).print(" = ").print(classExpr).print(", "); - writer.print(".").print(nextName).print(" = ") - .print(handler.nextIndex < 0 ? "NULL" : name + "+" + handler.nextIndex); + writer.print(".id = ").print(String.valueOf(handler.descriptor.getId())).print(", "); + writer.print(".exceptionClass = ").print(classExpr).print(", "); + writer.print(".next = ").print(handler.nextIndex < 0 ? "NULL" : name + "+" + handler.nextIndex); writer.print(" }"); } @@ -235,10 +201,6 @@ public class CallSiteGenerator { writer.println().outdent().println("};"); } - private String fieldName(String className, String fieldName) { - return context.getNames().forMemberField(new FieldReference(className, fieldName)); - } - private String getStringExpr(String s) { return s != null ? "&TEAVM_GET_STRING(" + context.getStringPool().getStringIndex(s) + ")" : "NULL"; } 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 8dff00b42..c79ea9f8e 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 @@ -661,6 +661,8 @@ public class ClassGenerator { tag = 0; sizeExpr = "0"; itemTypeExpr = "NULL"; + flags |= RuntimeClass.PRIMITIVE; + flags = ClassGeneratorUtil.applyPrimitiveFlags(flags, type); } else { parent = "NULL"; tag = Integer.MAX_VALUE; @@ -697,7 +699,167 @@ public class ClassGenerator { codeWriter.println(".superinterfaces = " + superinterfaces + ","); codeWriter.println(".layout = " + layout + ","); codeWriter.println(".enumValues = " + enumConstants + ","); - codeWriter.println(".init = " + initFunction); + codeWriter.print(".init = " + initFunction); + + if (context.isHeapDump() && type instanceof ValueType.Object) { + ClassReader cls = context.getClassSource().get(((ValueType.Object) type).getClassName()); + generateHeapDumpMetadata(cls); + } + codeWriter.println(); + } + + private void generateHeapDumpMetadata(ClassReader cls) { + List<HeapDumpField> fields = getHeapDumpFields(cls); + List<HeapDumpField> staticFields = getHeapDumpStaticFields(cls); + if (staticFields.isEmpty() && fields.isEmpty()) { + return; + } + codeWriter.println().println("#if TEAVM_HEAP_DUMP").indent(); + if (!fields.isEmpty()) { + codeWriter.println(","); + codeWriter.println(".fieldDescriptors = (TeaVM_FieldDescriptors*) " + + "&(struct { uint32_t count; TeaVM_FieldDescriptor data[" + + fields.size() + "]; }) {").indent(); + generateHeapDumpFields(fields); + codeWriter.outdent().print("}"); + } + if (!staticFields.isEmpty()) { + codeWriter.println(","); + codeWriter.println(".staticFieldDescriptors = (TeaVM_StaticFieldDescriptors*) " + + "&(struct { uint32_t count; TeaVM_StaticFieldDescriptor data[" + + staticFields.size() + "]; }) {").indent(); + generateHeapDumpFields(staticFields); + codeWriter.outdent().print("}"); + } + codeWriter.println().outdent().println("#endif"); + } + + private void generateHeapDumpFields(List<HeapDumpField> fields) { + codeWriter.println(".count = " + fields.size() + ","); + codeWriter.println(".data = {").indent(); + for (int i = 0; i < fields.size(); ++i) { + if (i > 0) { + codeWriter.println(","); + } + HeapDumpField field = fields.get(i); + codeWriter.print("{ .name = u"); + StringPoolGenerator.generateSimpleStringLiteral(codeWriter, field.name); + codeWriter.print(", .offset = " + field.offset + ", .type = " + field.type + " }"); + } + codeWriter.println().outdent().println("}"); + } + + private static final String TYPE_OBJECT = "TEAVM_FIELD_TYPE_OBJECT"; + + private List<HeapDumpField> getHeapDumpFields(ClassReader cls) { + List<HeapDumpField> fields = new ArrayList<>(); + switch (cls.getName()) { + case "java.lang.Object": + case "java.lang.ref.ReferenceQueue": + case "java.lang.ref.WeakReference": + case "java.lang.ref.SoftReference": + break; + case "java.lang.Class": + fields.add(new HeapDumpField("name", "offsetof(TeaVM_Class, name)", TYPE_OBJECT)); + fields.add(new HeapDumpField("simpleName", "offsetof(TeaVM_Class, simpleName)", TYPE_OBJECT)); + break; + case "java.lang.ref.Reference": + fields.add(new HeapDumpField("referent", "offsetof(TeaVM_Reference, object)", TYPE_OBJECT)); + fields.add(new HeapDumpField("queue", "offsetof(TeaVM_Reference, queue)", TYPE_OBJECT)); + break; + default: { + for (FieldReader field : cls.getFields()) { + if (field.hasModifier(ElementModifier.STATIC) || !isManaged(field)) { + continue; + } + String className = context.getNames().forClass(cls.getName()); + String offset = "offsetof(" + className + ", " + + context.getNames().forMemberField(field.getReference()) + ")"; + fields.add(new HeapDumpField(field.getName(), offset, typeForHeapDump(field.getType()))); + } + break; + } + } + return fields; + } + + private List<HeapDumpField> getHeapDumpStaticFields(ClassReader cls) { + List<HeapDumpField> fields = new ArrayList<>(); + switch (cls.getName()) { + case "java.lang.Object": + case "java.lang.Class": + case "java.lang.ref.ReferenceQueue": + case "java.lang.ref.Reference": + case "java.lang.ref.WeakReference": + case "java.lang.ref.SoftReference": + break; + default: { + for (FieldReader field : cls.getFields()) { + if (!field.hasModifier(ElementModifier.STATIC) || !isManaged(field)) { + continue; + } + String offset = "(unsigned char*) &" + context.getNames().forStaticField(field.getReference()); + fields.add(new HeapDumpField(field.getName(), offset, typeForHeapDump(field.getType()))); + } + break; + } + } + return fields; + } + + private boolean isManaged(FieldReader field) { + ValueType type = field.getType(); + return !(type instanceof ValueType.Object) + || context.getCharacteristics().isManaged(((ValueType.Object) type).getClassName()); + } + + static String typeForHeapDump(ValueType type) { + String result = "127"; + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + result = "TEAVM_FIELD_TYPE_BOOLEAN"; + break; + case BYTE: + result = "TEAVM_FIELD_TYPE_BYTE"; + break; + case SHORT: + result = "TEAVM_FIELD_TYPE_SHORT"; + break; + case CHARACTER: + result = "TEAVM_FIELD_TYPE_CHAR"; + break; + case INTEGER: + result = "TEAVM_FIELD_TYPE_INT"; + break; + case FLOAT: + result = "TEAVM_FIELD_TYPE_FLOAT"; + break; + case LONG: + result = "TEAVM_FIELD_TYPE_LONG"; + break; + case DOUBLE: + result = "TEAVM_FIELD_TYPE_DOUBLE"; + break; + } + } else if (type instanceof ValueType.Array) { + result = "TEAVM_FIELD_TYPE_ARRAY"; + } else { + result = "TEAVM_FIELD_TYPE_OBJECT"; + } + return result; + } + + static class HeapDumpField { + String name; + String offset; + String type; + + HeapDumpField(String name, String offset, String type) { + this.name = name; + this.offset = offset; + this.type = type; + } } private void generateVirtualTableStructure(String className) { 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 3ed45c85a..0fc389c4f 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 @@ -46,12 +46,13 @@ public class GenerationContext { private boolean incremental; private boolean longjmp; private boolean vmAssertions; + private boolean heapDump; public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, ClassReaderSource classSource, List<Intrinsic> intrinsics, List<Generator> generators, Predicate<MethodReference> asyncMethods, BuildTarget buildTarget, boolean incremental, - boolean longjmp, boolean vmAssertions) { + boolean longjmp, boolean vmAssertions, boolean heapDump) { this.virtualTableProvider = virtualTableProvider; this.characteristics = characteristics; this.dependencies = dependencies; @@ -66,6 +67,7 @@ public class GenerationContext { this.incremental = incremental; this.longjmp = longjmp; this.vmAssertions = vmAssertions; + this.heapDump = heapDump; } public void addIntrinsic(Intrinsic intrinsic) { @@ -134,6 +136,10 @@ public class GenerationContext { return longjmp; } + public boolean isHeapDump() { + return heapDump; + } + public boolean isVmAssertions() { return vmAssertions; } 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 a5e38e59f..477d87a2b 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 @@ -41,7 +41,6 @@ public class ExceptionHandlingIntrinsic implements Intrinsic { public void apply(IntrinsicContext context, InvocationExpr invocation) { switch (invocation.getMethod().getName()) { case "findCallSiteById": - context.includes().includePath("callsites.h"); context.writer().print("TEAVM_FIND_CALLSITE("); context.emit(invocation.getArguments().get(0)); context.writer().print(", "); diff --git a/core/src/main/java/org/teavm/backend/c/util/GCVisualizer.java b/core/src/main/java/org/teavm/backend/c/util/GCVisualizer.java index cd92fed5c..cea0412d6 100644 --- a/core/src/main/java/org/teavm/backend/c/util/GCVisualizer.java +++ b/core/src/main/java/org/teavm/backend/c/util/GCVisualizer.java @@ -37,7 +37,7 @@ public final class GCVisualizer { public static void main(String[] args) throws IOException { if (args.length != 2) { - System.err.println("Two arguments (input, ouput) expected"); + System.err.println("Two arguments (input, output) expected"); System.exit(1); } diff --git a/core/src/main/java/org/teavm/backend/c/util/HeapDumpConverter.java b/core/src/main/java/org/teavm/backend/c/util/HeapDumpConverter.java new file mode 100644 index 000000000..dccd3f5d9 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/HeapDumpConverter.java @@ -0,0 +1,912 @@ +/* + * 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.backend.c.util; + +import com.carrotsearch.hppc.LongArrayList; +import com.carrotsearch.hppc.LongObjectHashMap; +import com.carrotsearch.hppc.LongObjectMap; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import java.io.BufferedInputStream; +import java.io.DataOutput; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.teavm.backend.c.util.json.JsonAllErrorVisitor; +import org.teavm.backend.c.util.json.JsonArrayVisitor; +import org.teavm.backend.c.util.json.JsonErrorReporter; +import org.teavm.backend.c.util.json.JsonObjectVisitor; +import org.teavm.backend.c.util.json.JsonParser; +import org.teavm.backend.c.util.json.JsonPropertyVisitor; +import org.teavm.backend.c.util.json.JsonVisitingConsumer; +import org.teavm.backend.c.util.json.JsonVisitor; + +public final class HeapDumpConverter { + private static byte[] buffer = new byte[8]; + private static int idSize; + + private HeapDumpConverter() { + } + + public static void main(String[] args) throws IOException { + if (args.length != 2) { + System.err.println("Converts TeaVM/C heap dump into HotSpot compatible format (hprof)"); + System.err.println("Two arguments expected: input file (JSON), output file (hprof)"); + System.exit(-1); + } + + SymbolTable symbolTable; + try (Reader reader = createReader(args[0])) { + symbolTable = fillSymbolTable(reader); + } + + try (Reader reader = createReader(args[0]); + RandomAccessFile output = new RandomAccessFile(args[1], "rw")) { + generateHprofFile(reader, output, symbolTable); + } + } + + private static Reader createReader(String fileName) throws IOException { + return new InputStreamReader(new BufferedInputStream(new FileInputStream(fileName)), StandardCharsets.UTF_8); + } + + private static SymbolTable fillSymbolTable(Reader reader) throws IOException { + SymbolTable symbolTable = new SymbolTable(); + JsonPropertyVisitor rootObjectVisitor = new JsonPropertyVisitor(true); + rootObjectVisitor.addProperty("pointerSize", pointerSizeVisitor); + rootObjectVisitor.addProperty("classes", new JsonArrayVisitor(new SymbolTableClassVisitor(symbolTable))); + rootObjectVisitor.addProperty("stack", new JsonArrayVisitor(new SymbolTableStackVisitor(symbolTable))); + JsonParser parser = new JsonParser(new JsonVisitingConsumer(new JsonObjectVisitor(rootObjectVisitor))); + parser.parse(reader); + + if (symbolTable.classLoaderClassId == 0) { + addFakeClass(1, symbolTable, "java.lang.ClassLoader", symbolTable.objectClassId); + } + + if (symbolTable.referenceClassId == 0) { + addFakeClass(2, symbolTable, "java.lang.ref.Reference", symbolTable.objectClassId); + symbolTable.referenceClassId = 2; + } + if (symbolTable.weakReferenceClassId == 0) { + addFakeClass(3, symbolTable, "java.lang.ref.WeakReference", symbolTable.referenceClassId); + } + if (symbolTable.softReferenceClassId == 0) { + addFakeClass(4, symbolTable, "java.lang.ref.SoftReference", symbolTable.referenceClassId); + } + if (symbolTable.phantomReferenceClassId == 0) { + addFakeClass(5, symbolTable, "java.lang.ref.PhantomReference", symbolTable.referenceClassId); + } + if (symbolTable.finalReferenceClassId == 0) { + addFakeClass(6, symbolTable, "java.lang.ref.FinalReference", symbolTable.referenceClassId); + } + + fixClassNames(symbolTable); + return symbolTable; + } + + private static void addFakeClass(long id, SymbolTable symbolTable, String name, long parent) { + ClassDescriptor cls = new ClassDescriptor(); + cls.id = id; + cls.superClassId = parent; + cls.name = name; + symbolTable.classesById.put(cls.id, cls); + symbolTable.classList.add(cls); + } + + private static void fixClassNames(SymbolTable symbolTable) { + int serialIdGen = 1; + int anonymousIdGen = 0; + + for (ClassDescriptor descriptor : symbolTable.classList) { + if (descriptor.primitiveType != null) { + continue; + } + descriptor.serialId = serialIdGen++; + + if (descriptor.itemClassId != 0) { + continue; + } + + if (descriptor.name == null) { + do { + descriptor.name = "anonymous_" + anonymousIdGen++; + } while (symbolTable.getClass(descriptor.name) != null); + } else { + descriptor.name = descriptor.name.replace('.', '/'); + } + } + + List<ClassDescriptor> arrayClasses = new ArrayList<>(); + for (ClassDescriptor descriptor : symbolTable.classList) { + if (descriptor.itemClassId == 0 || descriptor.name != null) { + continue; + } + + while (descriptor.itemClassId != 0) { + arrayClasses.add(descriptor); + descriptor = symbolTable.classesById.get(descriptor.itemClassId); + if (descriptor.name != null) { + break; + } + } + + String name = descriptor.name; + if (descriptor.primitiveType != null) { + switch (descriptor.primitiveType) { + case BOOLEAN: + name = "Z"; + break; + case BYTE: + name = "B"; + break; + case SHORT: + name = "S"; + break; + case CHAR: + name = "C"; + break; + case INT: + name = "I"; + break; + case LONG: + name = "J"; + break; + case FLOAT: + name = "F"; + break; + case DOUBLE: + name = "D"; + break; + case OBJECT: + case ARRAY: + assert false; + break; + } + } else if (descriptor.itemClassId == 0) { + name = "L" + name + ";"; + } + + for (int i = arrayClasses.size() - 1; i >= 0; --i) { + name = "[" + name; + arrayClasses.get(i).name = name; + } + + arrayClasses.clear(); + } + + for (ClassDescriptor descriptor : symbolTable.classList) { + if (descriptor.name != null) { + symbolTable.lookup(descriptor.name); + symbolTable.classes.put(descriptor.name, descriptor); + } + } + } + + private static JsonVisitor pointerSizeVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + idSize = (int) value; + } + }; + + private static void generateHprofFile(Reader reader, RandomAccessFile output, SymbolTable symbolTable) + throws IOException { + output.write("JAVA PROFILE 1.0.2\0".getBytes(StandardCharsets.UTF_8)); + output.writeInt(idSize); + output.writeLong(System.currentTimeMillis()); + writeSymbols(output, symbolTable); + writeStack(output, symbolTable); + writeHeapDump(reader, output, symbolTable); + + output.write(0x2C); + output.writeInt(0); + output.writeInt(0); + output.setLength(output.getFilePointer()); + } + + private static void writeSymbols(RandomAccessFile output, SymbolTable symbolTable) throws IOException { + List<String> strings = symbolTable.getStrings(); + for (int i = 0; i < strings.size(); ++i) { + byte[] bytes = strings.get(i).getBytes(StandardCharsets.UTF_8); + output.write(1); + output.writeInt(0); + output.writeInt(idSize + bytes.length); + + writeId(output, i + 1); + output.write(bytes); + } + } + + private static void writeStack(RandomAccessFile output, SymbolTable symbolTable) throws IOException { + for (int i = 0; i < symbolTable.stack.size(); ++i) { + Frame frame = symbolTable.stack.get(i); + output.write(4); + output.writeInt(0); + output.writeInt(4 * idSize + 8); + writeId(output, i + 1); + writeId(output, frame.methodName != null ? symbolTable.lookup(frame.methodName) : 0); + writeId(output, 0); + writeId(output, frame.fileName != null ? symbolTable.lookup(frame.fileName) : 0); + int classSerialId = 0; + if (frame.className != null) { + ClassDescriptor cls = symbolTable.getClass(frame.className); + if (cls != null) { + classSerialId = cls.serialId; + } + } + output.writeInt(classSerialId); + output.writeInt(frame.lineNumber); + } + + output.write(5); + output.writeInt(0); + output.writeInt(12 + idSize * symbolTable.stack.size()); + + output.writeInt(1); + output.writeInt(0); + output.writeInt(symbolTable.stack.size()); + for (int i = 0; i < symbolTable.stack.size(); ++i) { + writeId(output, i + 1); + } + } + + private static void writeGcRoots(RandomAccessFile output, SymbolTable symbolTable) throws IOException { + List<Frame> stack = symbolTable.stack; + for (int i = 0; i < stack.size(); i++) { + Frame frame = stack.get(i); + if (frame.roots == null) { + continue; + } + for (long rootId : frame.roots) { + output.write(3); + writeId(output, rootId); + output.writeInt(0); + output.writeInt(i); + } + } + + for (ClassDescriptor classDescriptor : symbolTable.getClasses()) { + if (classDescriptor.primitiveType != null) { + continue; + } + output.write(5); + writeId(output, classDescriptor.id); + } + } + + private static void writeHeapDump(Reader reader, RandomAccessFile output, SymbolTable symbolTable) + throws IOException { + for (ClassDescriptor classDescriptor : symbolTable.getClasses()) { + if (classDescriptor.primitiveType != null) { + continue; + } + output.write(2); + output.writeInt(0); + output.writeInt(8 + 2 * idSize); + output.writeInt(classDescriptor.serialId); + writeId(output, classDescriptor.id); + output.writeInt(1); + writeId(output, symbolTable.lookup(classDescriptor.name)); + } + + output.write(0x0C); + output.writeInt(0); + output.writeInt(0); + long mark = output.getFilePointer(); + + writeGcRoots(output, symbolTable); + writeClassObjects(output, symbolTable); + + JsonPropertyVisitor rootPropertyVisitor = new JsonPropertyVisitor(true); + rootPropertyVisitor.addProperty("objects", new JsonArrayVisitor(new ObjectDumpVisitor(output, symbolTable))); + JsonParser parser = new JsonParser(new JsonVisitingConsumer(new JsonObjectVisitor(rootPropertyVisitor))); + parser.parse(reader); + + long pointerBackup = output.getFilePointer(); + int size = (int) (output.getFilePointer() - mark); + output.seek(mark - 4); + output.writeInt(size); + output.seek(pointerBackup); + } + + private static void writeClassObjects(RandomAccessFile output, SymbolTable symbolTable) throws IOException { + Collection<ClassDescriptor> classes = symbolTable.getClasses(); + for (ClassDescriptor cls : classes) { + if (cls.primitiveType == null) { + writeClassDump(output, symbolTable, cls); + } + } + } + + private static void writeClassDump(RandomAccessFile output, SymbolTable symbolTable, ClassDescriptor cls) + throws IOException { + output.write(0x20); + writeId(output, cls.id); + output.writeInt(1); + + long superClassId = cls.superClassId; + if (cls.itemClassId != 0) { + superClassId = symbolTable.objectClassId; + } + writeId(output, superClassId); + writeId(output, 0); + writeId(output, 0); + writeId(output, 0); + writeId(output, 0); + writeId(output, 0); + + output.writeInt(cls.size); + output.writeShort((short) 0); + + output.writeShort((short) cls.staticFields.size()); + int dataPtr = 0; + for (FieldDescriptor field : cls.staticFields) { + writeId(output, symbolTable.lookup(field.name)); + output.write(typeToInt(field.type)); + int sz = typeSize(field.type); + output.write(cls.data, dataPtr, sz); + dataPtr += sz; + } + + output.writeShort((short) cls.fields.size()); + for (FieldDescriptor field : cls.fields) { + writeId(output, symbolTable.lookup(field.name)); + output.write(typeToInt(field.type)); + } + } + + static class ObjectDumpVisitor extends JsonAllErrorVisitor { + private DataOutput output; + private SymbolTable symbolTable; + private JsonPropertyVisitor propertyVisitor = new JsonPropertyVisitor(true); + private long id; + private long classId; + private byte[] data; + + ObjectDumpVisitor(DataOutput output, SymbolTable symbolTable) { + this.output = output; + this.symbolTable = symbolTable; + propertyVisitor.addProperty("id", idVisitor); + propertyVisitor.addProperty("class", classVisitor); + propertyVisitor.addProperty("data", dataVisitor); + } + + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + id = 0; + classId = 0; + data = null; + return propertyVisitor; + } + + @Override + public void end(JsonErrorReporter reporter) { + try { + ClassDescriptor cls = symbolTable.getClassById(classId); + if (cls == null) { + reporter.error("Unknown class: " + classId); + } + if (cls.itemClassId == 0) { + output.write(0x21); + writeId(output, id); + output.writeInt(1); + writeId(output, classId); + output.writeInt(data.length); + int dataPtr = data.length; + while (cls != null) { + for (FieldDescriptor field : cls.fields) { + dataPtr -= typeSize(field.type); + } + int ptr = dataPtr; + for (FieldDescriptor field : cls.fields) { + int size = typeSize(field.type); + output.write(data, ptr, size); + ptr += size; + } + cls = cls.superClassId != 0 ? symbolTable.getClassById(cls.superClassId) : null; + } + } else { + ClassDescriptor itemCls = symbolTable.getClassById(cls.itemClassId); + output.write(itemCls.primitiveType == null ? 0x22 : 0x23); + writeId(output, id); + output.writeInt(1); + int itemSize = itemCls.primitiveType != null ? typeSize(itemCls.primitiveType) : idSize; + int size = data.length / itemSize; + output.writeInt(size); + if (itemCls.primitiveType == null) { + writeId(output, classId); + } else { + output.writeByte(typeToInt(itemCls.primitiveType)); + } + for (int i = 0; i < size; ++i) { + int ptr = i * itemSize; + output.write(data, ptr, itemSize); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private JsonVisitor idVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + id = value; + } + }; + + private JsonVisitor classVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + classId = value; + } + }; + + private JsonVisitor dataVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + data = parseData(reporter, value); + } + }; + } + + static class SymbolTableClassVisitor extends JsonAllErrorVisitor { + SymbolTable symbolTable; + ClassDescriptor descriptor; + FieldDescriptor fieldDescriptor; + private JsonPropertyVisitor propertyVisitor; + private List<FieldDescriptor> fields; + + SymbolTableClassVisitor(SymbolTable symbolTable) { + this.symbolTable = symbolTable; + propertyVisitor = new JsonPropertyVisitor(true); + propertyVisitor.addProperty("id", idVisitor); + propertyVisitor.addProperty("name", nameVisitor); + propertyVisitor.addProperty("super", superVisitor); + propertyVisitor.addProperty("size", sizeVisitor); + propertyVisitor.addProperty("primitive", primitiveVisitor); + propertyVisitor.addProperty("item", itemVisitor); + propertyVisitor.addProperty("fields", fieldsVisitor); + propertyVisitor.addProperty("staticFields", staticFieldsVisitor); + propertyVisitor.addProperty("data", dataVisitor); + } + + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + descriptor = new ClassDescriptor(); + return propertyVisitor; + } + + @Override + public void end(JsonErrorReporter reporter) { + if (descriptor.id == 0) { + reporter.error("Required property 'id' was not set"); + } + symbolTable.classList.add(descriptor); + if (symbolTable.classesById.put(descriptor.id, descriptor) != null) { + reporter.error("Duplicate class id: " + descriptor.id); + } + if (descriptor.name != null) { + switch (descriptor.name) { + case "java.lang.Object": + symbolTable.objectClassId = descriptor.id; + break; + case "java.lang.ClassLoader": + symbolTable.classLoaderClassId = descriptor.id; + break; + case "java.lang.ref.Reference": + symbolTable.referenceClassId = descriptor.id; + break; + case "java.lang.ref.WeakReference": + symbolTable.weakReferenceClassId = descriptor.id; + break; + case "java.lang.ref.SoftReference": + symbolTable.softReferenceClassId = descriptor.id; + break; + case "java.lang.ref.PhantomReference": + symbolTable.phantomReferenceClassId = descriptor.id; + break; + case "java.lang.ref.FinalReference": + symbolTable.finalReferenceClassId = descriptor.id; + break; + } + } + } + + JsonVisitor idVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + descriptor.id = value; + } + }; + + JsonVisitor nameVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + descriptor.name = value; + } + }; + + JsonVisitor superVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + descriptor.superClassId = value; + } + + @Override + public void nullValue(JsonErrorReporter reporter) { + } + }; + + JsonVisitor sizeVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + descriptor.size = (int) value; + } + }; + + JsonVisitor primitiveVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + descriptor.primitiveType = parseType(reporter, value); + } + }; + + JsonVisitor itemVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + descriptor.itemClassId = value; + } + }; + + JsonVisitor fieldsVisitor = new JsonAllErrorVisitor() { + @Override + public JsonVisitor array(JsonErrorReporter reporter) { + fields = descriptor.fields; + return fieldVisitor; + } + }; + + JsonVisitor staticFieldsVisitor = new JsonAllErrorVisitor() { + @Override + public JsonVisitor array(JsonErrorReporter reporter) { + fields = descriptor.staticFields; + return fieldVisitor; + } + }; + + JsonVisitor fieldNameVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + fieldDescriptor.name = value; + symbolTable.lookup(value); + } + }; + + JsonVisitor fieldTypeVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + fieldDescriptor.type = parseType(reporter, value); + } + }; + + JsonVisitor fieldVisitor = new JsonAllErrorVisitor() { + private JsonPropertyVisitor propertyVisitor = new JsonPropertyVisitor(true); + + { + propertyVisitor.addProperty("name", fieldNameVisitor); + propertyVisitor.addProperty("type", fieldTypeVisitor); + } + + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + fieldDescriptor = new FieldDescriptor(); + return propertyVisitor; + } + + @Override + public void end(JsonErrorReporter reporter) { + if (fieldDescriptor.type == null) { + reporter.error("Type for this field not specified"); + } + fields.add(fieldDescriptor); + fieldDescriptor = null; + } + }; + + JsonVisitor dataVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + descriptor.data = parseData(reporter, value); + } + }; + } + + private static Type parseType(JsonErrorReporter errorReporter, String type) { + switch (type) { + case "object": + return Type.OBJECT; + case "array": + return Type.ARRAY; + case "boolean": + return Type.BOOLEAN; + case "byte": + return Type.BYTE; + case "char": + return Type.CHAR; + case "short": + return Type.SHORT; + case "int": + return Type.INT; + case "long": + return Type.LONG; + case "float": + return Type.FLOAT; + case "double": + return Type.DOUBLE; + default: + errorReporter.error("Unknown type: " + type); + return Type.OBJECT; + } + } + + static class SymbolTableStackVisitor extends JsonAllErrorVisitor { + SymbolTable symbolTable; + private JsonPropertyVisitor propertyVisitor = new JsonPropertyVisitor(true); + private Frame frame; + private LongArrayList roots = new LongArrayList(); + + SymbolTableStackVisitor(SymbolTable symbolTable) { + this.symbolTable = symbolTable; + propertyVisitor.addProperty("file", fileVisitor); + propertyVisitor.addProperty("class", classVisitor); + propertyVisitor.addProperty("method", methodVisitor); + propertyVisitor.addProperty("line", lineVisitor); + propertyVisitor.addProperty("roots", new JsonArrayVisitor(rootsVisitor)); + } + + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + frame = new Frame(); + return propertyVisitor; + } + + @Override + public void end(JsonErrorReporter reporter) { + if (!roots.isEmpty()) { + frame.roots = roots.toArray(); + roots.clear(); + } + symbolTable.stack.add(frame); + } + + JsonVisitor fileVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + symbolTable.lookup(value); + frame.fileName = value; + } + }; + + JsonVisitor classVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + symbolTable.lookup(value); + frame.className = value; + } + }; + + JsonVisitor methodVisitor = new JsonAllErrorVisitor() { + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + symbolTable.lookup(value); + frame.methodName = value; + } + }; + + JsonVisitor lineVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + frame.lineNumber = (int) value; + } + }; + + JsonVisitor rootsVisitor = new JsonAllErrorVisitor() { + @Override + public void intValue(JsonErrorReporter reporter, long value) { + roots.add(value); + } + }; + } + + static class Frame { + String className; + String methodName; + String fileName; + int lineNumber = -1; + long[] roots; + } + + static class ClassDescriptor { + long id; + String name; + int serialId; + Type primitiveType; + long itemClassId; + long superClassId; + int size; + List<FieldDescriptor> fields = new ArrayList<>(); + List<FieldDescriptor> staticFields = new ArrayList<>(); + byte[] data; + } + + static class FieldDescriptor { + String name; + Type type; + } + + enum Type { + OBJECT, + ARRAY, + BOOLEAN, + BYTE, + CHAR, + SHORT, + INT, + LONG, + FLOAT, + DOUBLE + } + + private static int typeToInt(Type type) { + switch (type) { + case OBJECT: + case ARRAY: + return 2; + case BOOLEAN: + return 4; + case CHAR: + return 5; + case FLOAT: + return 6; + case DOUBLE: + return 7; + case BYTE: + return 8; + case SHORT: + return 9; + case INT: + return 10; + case LONG: + return 11; + default: + return 0; + } + } + + private static int typeSize(Type type) { + switch (type) { + case OBJECT: + case ARRAY: + return idSize; + case BYTE: + case BOOLEAN: + return 1; + case CHAR: + case SHORT: + return 2; + case INT: + case FLOAT: + return 4; + case LONG: + case DOUBLE: + return 8; + default: + return 0; + } + } + + static class SymbolTable { + private List<String> strings = new ArrayList<>(); + private ObjectIntMap<String> stringIndexes = new ObjectIntHashMap<>(); + List<ClassDescriptor> classList = new ArrayList<>(); + Map<String, ClassDescriptor> classes = new LinkedHashMap<>(); + LongObjectMap<ClassDescriptor> classesById = new LongObjectHashMap<>(); + List<Frame> stack = new ArrayList<>(); + long objectClassId; + long classLoaderClassId; + long referenceClassId; + long weakReferenceClassId; + long softReferenceClassId; + long finalReferenceClassId; + long phantomReferenceClassId; + + List<String> getStrings() { + return strings; + } + + ClassDescriptor getClass(String name) { + return classes.get(name); + } + + ClassDescriptor getClassById(long id) { + return classesById.get(id); + } + + Collection<ClassDescriptor> getClasses() { + return classList; + } + + int lookup(String str) { + int value = stringIndexes.getOrDefault(str, -1); + if (value < 0) { + value = strings.size() + 1; + strings.add(str); + stringIndexes.put(str, value); + } + return value; + } + } + + private static void writeId(DataOutput out, long id) throws IOException { + writeLongBytes(out, id, idSize); + } + + private static void writeLongBytes(DataOutput out, long v, int size) throws IOException { + for (int i = size - 1; i >= 0; --i) { + buffer[i] = (byte) (v & 255); + v >>>= 8; + } + + out.write(buffer, 0, size); + } + + static byte[] parseData(JsonErrorReporter errorReporter, String data) { + if (data.length() % 2 != 0) { + errorReporter.error("Invalid hex sequence"); + return new byte[0]; + } + byte[] bytes = new byte[data.length() / 2]; + int j = 0; + for (int i = 0; i < data.length(); i += 2) { + int b = (hexDigit(errorReporter, data.charAt(i)) << 4) | hexDigit(errorReporter, data.charAt(i + 1)); + bytes[j++] = (byte) b; + } + return bytes; + } + + private static int hexDigit(JsonErrorReporter errorReporter, char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else { + errorReporter.error("Invalid hex sequence"); + return -1; + } + } +} + diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonAllErrorVisitor.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonAllErrorVisitor.java new file mode 100644 index 000000000..aabf87347 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonAllErrorVisitor.java @@ -0,0 +1,61 @@ +/* + * 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.backend.c.util.json; + +public class JsonAllErrorVisitor extends JsonVisitor { + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + reporter.error("Unexpected object"); + return null; + } + + @Override + public JsonVisitor array(JsonErrorReporter reporter) { + reporter.error("Unexpected array"); + return null; + } + + @Override + public JsonVisitor property(JsonErrorReporter reporter, String name) { + reporter.error("Unexpected property"); + return null; + } + + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + reporter.error("Unexpected string"); + } + + @Override + public void intValue(JsonErrorReporter reporter, long value) { + reporter.error("Unexpected number"); + } + + @Override + public void floatValue(JsonErrorReporter reporter, double value) { + reporter.error("Unexpected number"); + } + + @Override + public void nullValue(JsonErrorReporter reporter) { + reporter.error("Unexpected null"); + } + + @Override + public void booleanValue(JsonErrorReporter reporter, boolean value) { + reporter.error("Unexpected boolean"); + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonArrayVisitor.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonArrayVisitor.java new file mode 100644 index 000000000..0b556c057 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonArrayVisitor.java @@ -0,0 +1,29 @@ +/* + * 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.backend.c.util.json; + +public class JsonArrayVisitor extends JsonAllErrorVisitor { + private JsonVisitor itemVisitor; + + public JsonArrayVisitor(JsonVisitor itemVisitor) { + this.itemVisitor = itemVisitor; + } + + @Override + public JsonVisitor array(JsonErrorReporter reporter) { + return itemVisitor; + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonConsumer.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonConsumer.java new file mode 100644 index 000000000..79f8e45c0 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonConsumer.java @@ -0,0 +1,51 @@ +/* + * 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.backend.c.util.json; + +public abstract class JsonConsumer { + public void enterObject(JsonErrorReporter reporter) { + } + + public void exitObject(JsonErrorReporter reporter) { + } + + public void enterArray(JsonErrorReporter reporter) { + } + + public void exitArray(JsonErrorReporter reporter) { + } + + public void enterProperty(JsonErrorReporter reporter, String name) { + } + + public void exitProperty(JsonErrorReporter reporter, String name) { + } + + public void stringValue(JsonErrorReporter reporter, String value) { + } + + public void intValue(JsonErrorReporter reporter, long value) { + } + + public void floatValue(JsonErrorReporter reporter, double value) { + } + + public void nullValue(JsonErrorReporter reporter) { + } + + public void booleanValue(JsonErrorReporter reporter, boolean value) { + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonErrorReporter.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonErrorReporter.java new file mode 100644 index 000000000..5db7a283e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonErrorReporter.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.backend.c.util.json; + +public abstract class JsonErrorReporter { + public abstract void error(String message); +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonObjectVisitor.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonObjectVisitor.java new file mode 100644 index 000000000..08a423a7c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonObjectVisitor.java @@ -0,0 +1,29 @@ +/* + * 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.backend.c.util.json; + +public class JsonObjectVisitor extends JsonVisitor { + private JsonPropertyVisitor propertyVisitor; + + public JsonObjectVisitor(JsonPropertyVisitor propertyVisitor) { + this.propertyVisitor = propertyVisitor; + } + + @Override + public JsonVisitor object(JsonErrorReporter reporter) { + return propertyVisitor; + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonParser.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonParser.java new file mode 100644 index 000000000..7d3ded375 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonParser.java @@ -0,0 +1,415 @@ +/* + * 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.backend.c.util.json; + +import java.io.IOException; +import java.io.Reader; +import java.util.HashSet; +import java.util.Set; + +public class JsonParser { + private JsonConsumer consumer; + private int lastChar; + private int lineNumber; + private int columnNumber; + private boolean cr; + + public JsonParser(JsonConsumer consumer) { + this.consumer = consumer; + } + + public void parse(Reader reader) throws IOException { + lastChar = reader.read(); + skipWhitespaces(reader); + if (lastChar == -1) { + error("Unexpected end of file"); + } + parseValue(reader); + skipWhitespaces(reader); + if (lastChar != -1) { + error("Unexpected characters after end of JSON string"); + } + } + + private void parseValue(Reader reader) throws IOException { + switch (lastChar) { + case '{': + parseObject(reader); + break; + case '[': + parseArray(reader); + break; + case '\"': + parseString(reader); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + parseNumber(reader); + break; + case 'n': + parseNull(reader); + break; + case 't': + parseTrue(reader); + break; + case 'f': + parseFalse(reader); + break; + default: + error("Unexpected character"); + break; + } + } + + private void parseObject(Reader reader) throws IOException { + Set<String> usedPropertyNames = new HashSet<>(); + + consumer.enterObject(errorReporter); + nextChar(reader); + skipWhitespaces(reader); + if (lastChar == '}') { + nextChar(reader); + consumer.exitObject(errorReporter); + return; + } + + parseProperty(reader, usedPropertyNames); + while (lastChar != '}') { + if (lastChar != ',') { + error("Either property delimiter (',') or end of object ('}') expected"); + } + nextChar(reader); + skipWhitespaces(reader); + parseProperty(reader, usedPropertyNames); + } + nextChar(reader); + consumer.exitObject(errorReporter); + } + + private void parseProperty(Reader reader, Set<String> usedPropertyNames) throws IOException { + skipWhitespaces(reader); + if (lastChar != '"') { + error("Object key (string literal) expected"); + } + String name = parseStringLiteral(reader); + if (!usedPropertyNames.add(name)) { + error("Duplicate object property: " + name); + } + consumer.enterProperty(errorReporter, name); + skipWhitespaces(reader); + if (lastChar != ':') { + error("':' character expected after property name"); + } + nextChar(reader); + skipWhitespaces(reader); + parseValue(reader); + consumer.exitProperty(errorReporter, name); + skipWhitespaces(reader); + } + + private void parseArray(Reader reader) throws IOException { + consumer.enterArray(errorReporter); + nextChar(reader); + skipWhitespaces(reader); + + if (lastChar == ']') { + nextChar(reader); + consumer.exitObject(errorReporter); + return; + } + + parseValue(reader); + skipWhitespaces(reader); + while (lastChar != ']') { + if (lastChar != ',') { + error("Either array item delimiter (',') or end of array (']') expected"); + } + nextChar(reader); + skipWhitespaces(reader); + parseValue(reader); + skipWhitespaces(reader); + } + nextChar(reader); + + consumer.exitArray(errorReporter); + } + + private void parseString(Reader reader) throws IOException { + consumer.stringValue(errorReporter, parseStringLiteral(reader)); + } + + private String parseStringLiteral(Reader reader) throws IOException { + nextChar(reader); + StringBuilder sb = new StringBuilder(); + while (lastChar != '\"') { + if (lastChar == -1) { + error("Unexpected end of input inside string literal"); + } else if (lastChar < ' ') { + error("Unexpected control character inside string literal"); + } + if (lastChar == '\\') { + nextChar(reader); + switch (lastChar) { + case '\"': + case '\\': + case '/': + sb.append(lastChar); + nextChar(reader); + break; + case 'b': + sb.append('\b'); + nextChar(reader); + break; + case 'f': + sb.append('\f'); + nextChar(reader); + break; + case 'n': + sb.append('\n'); + nextChar(reader); + break; + case 'r': + sb.append('\r'); + nextChar(reader); + break; + case 't': + sb.append('\t'); + nextChar(reader); + break; + case 'u': + nextChar(reader); + int code = (getHexDigit(reader) << 12) | (getHexDigit(reader) << 8) + | (getHexDigit(reader) << 4) | getHexDigit(reader); + sb.append((char) code); + break; + default: + error("Wrong escape sequence"); + break; + } + } else { + sb.append((char) lastChar); + nextChar(reader); + } + } + nextChar(reader); + return sb.toString(); + } + + private int getHexDigit(Reader reader) throws IOException { + int value; + if (lastChar >= '0' && lastChar <= '9') { + value = lastChar - '0'; + } else if (lastChar >= 'A' && lastChar <= 'F') { + value = lastChar - 'A' + 10; + } else if (lastChar >= 'a' && lastChar <= 'f') { + value = lastChar - 'a' + 10; + } else { + error("Wrong escape sequence"); + value = 0; + } + nextChar(reader); + return value; + } + + private void parseNumber(Reader reader) throws IOException { + boolean isFloatingPoint = false; + StringBuilder sb = new StringBuilder(); + if (lastChar == '-') { + acceptChar(sb, reader); + } + if (lastChar == '0') { + acceptChar(sb, reader); + } else { + if (!isDigit(lastChar)) { + if (lastChar == 'e' || lastChar == 'E' || lastChar == '.') { + error("Wrong number, at least one digit expected in integer part"); + } else { + error("Wrong number, digits must follow '-' sign"); + } + } + acceptChar(sb, reader); + while (isDigit(lastChar)) { + acceptChar(sb, reader); + } + } + + if (lastChar == '.') { + isFloatingPoint = true; + acceptChar(sb, reader); + if (!isDigit(lastChar)) { + error("Wrong number, at least one digit must be in fraction part"); + } + acceptChar(sb, reader); + while (isDigit(lastChar)) { + acceptChar(sb, reader); + } + } + + if (lastChar == 'e' || lastChar == 'E') { + isFloatingPoint = true; + acceptChar(sb, reader); + if (lastChar == '+' || lastChar == '-') { + acceptChar(sb, reader); + } + if (!isDigit(lastChar)) { + error("Wrong number, at least one digit must be in exponent"); + } + acceptChar(sb, reader); + while (isDigit(lastChar)) { + acceptChar(sb, reader); + } + } + + expectEndOfToken("Wrong number"); + + if (isFloatingPoint) { + tryParseDouble(sb); + } else { + long value; + try { + value = Long.parseLong(sb.toString()); + } catch (NumberFormatException e) { + tryParseDouble(sb); + return; + } + consumer.intValue(errorReporter, value); + } + } + + private void tryParseDouble(StringBuilder sb) { + double value; + try { + value = Double.parseDouble(sb.toString()); + } catch (NumberFormatException e) { + error("Wrong number"); + value = 0; + } + consumer.floatValue(errorReporter, value); + } + + private void acceptChar(StringBuilder sb, Reader reader) throws IOException { + sb.append((char) lastChar); + nextChar(reader); + } + + private void parseNull(Reader reader) throws IOException { + expectIdentifier(reader, "null"); + consumer.nullValue(errorReporter); + } + + private void parseTrue(Reader reader) throws IOException { + expectIdentifier(reader, "true"); + consumer.booleanValue(errorReporter, true); + } + + private void parseFalse(Reader reader) throws IOException { + expectIdentifier(reader, "false"); + consumer.booleanValue(errorReporter, false); + } + + private void expectIdentifier(Reader reader, String identifier) throws IOException { + for (int i = 0; i < identifier.length(); ++i) { + if (lastChar != identifier.charAt(i)) { + error("Unexpected identifier"); + } + nextChar(reader); + } + expectEndOfToken("Wrong identifier"); + } + + private void expectEndOfToken(String errorMessage) { + switch (lastChar) { + case '}': + case '{': + case '[': + case ']': + case ',': + case ':': + break; + default: + if (!isWhitespace(lastChar)) { + error(errorMessage); + } + break; + } + } + + private void skipWhitespaces(Reader reader) throws IOException { + while (isWhitespace(lastChar)) { + nextChar(reader); + } + } + + private void nextChar(Reader reader) throws IOException { + boolean wasCr = cr; + if (cr) { + lineNumber++; + columnNumber = 0; + cr = false; + } + switch (lastChar) { + case '\r': + cr = true; + break; + case '\n': + if (!wasCr) { + lineNumber++; + columnNumber = 0; + } + break; + default: + columnNumber++; + break; + } + lastChar = reader.read(); + } + + void error(String error) { + throw new JsonSyntaxException(lineNumber, columnNumber, error); + } + + private JsonErrorReporter errorReporter = new JsonErrorReporter() { + @Override + public void error(String message) { + JsonParser.this.error(message); + } + }; + + private static boolean isWhitespace(int c) { + switch (c) { + case ' ': + case '\t': + case '\n': + case '\r': + return true; + default: + return false; + } + } + + private static boolean isDigit(int c) { + return c >= '0' && c <= '9'; + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonPropertyVisitor.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonPropertyVisitor.java new file mode 100644 index 000000000..0dd9696e8 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonPropertyVisitor.java @@ -0,0 +1,40 @@ +/* + * 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.backend.c.util.json; + +import java.util.HashMap; +import java.util.Map; + +public class JsonPropertyVisitor extends JsonAllErrorVisitor { + private Map<String, JsonVisitor> properties = new HashMap<>(); + private boolean skipNonExistentProperties; + + public JsonPropertyVisitor(boolean skipNonExistentProperties) { + this.skipNonExistentProperties = skipNonExistentProperties; + } + + public void addProperty(String propertyName, JsonVisitor visitor) { + properties.put(propertyName, visitor); + } + + @Override + public JsonVisitor property(JsonErrorReporter reporter, String name) { + if (!skipNonExistentProperties && !properties.containsKey(name)) { + reporter.error("Unexpected property name: " + name); + } + return properties.get(name); + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonSyntaxException.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonSyntaxException.java new file mode 100644 index 000000000..2b093a50a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonSyntaxException.java @@ -0,0 +1,41 @@ +/* + * 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.backend.c.util.json; + +public class JsonSyntaxException extends RuntimeException { + private final int lineNumber; + private final int columnNumber; + private final String error; + + public JsonSyntaxException(int lineNumber, int columnNumber, String error) { + super("JSON syntax error at " + (lineNumber + 1) + ":" + (columnNumber + 1) + ": " + error); + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + this.error = error; + } + + public int getLineNumber() { + return lineNumber; + } + + public int getColumnNumber() { + return columnNumber; + } + + public String getError() { + return error; + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitingConsumer.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitingConsumer.java new file mode 100644 index 000000000..3189de26c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitingConsumer.java @@ -0,0 +1,131 @@ +/* + * 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.backend.c.util.json; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class JsonVisitingConsumer extends JsonConsumer { + private Deque<JsonVisitor> visitorStack = new ArrayDeque<>(); + private int noVisitorLevel; + + public JsonVisitingConsumer(JsonVisitor visitor) { + visitorStack.push(visitor); + } + + @Override + public void enterObject(JsonErrorReporter reporter) { + if (noVisitorLevel == 0) { + JsonVisitor next = visitorStack.peek().object(reporter); + if (next == null) { + noVisitorLevel = 1; + } else { + visitorStack.push(next); + } + } else { + noVisitorLevel++; + } + } + + @Override + public void exitObject(JsonErrorReporter reporter) { + exit(reporter); + } + + @Override + public void enterArray(JsonErrorReporter reporter) { + if (noVisitorLevel == 0) { + JsonVisitor next = visitorStack.peek().array(reporter); + if (next == null) { + noVisitorLevel = 1; + } else { + visitorStack.push(next); + } + } else { + noVisitorLevel++; + } + } + + @Override + public void exitArray(JsonErrorReporter reporter) { + exit(reporter); + } + + @Override + public void enterProperty(JsonErrorReporter reporter, String name) { + if (noVisitorLevel == 0) { + JsonVisitor next = visitorStack.peek().property(reporter, name); + if (next == null) { + noVisitorLevel = 1; + } else { + visitorStack.push(next); + } + } else { + noVisitorLevel++; + } + } + + @Override + public void exitProperty(JsonErrorReporter reporter, String name) { + exit(reporter); + } + + private void exit(JsonErrorReporter reporter) { + if (noVisitorLevel > 0) { + noVisitorLevel--; + } else { + visitorStack.pop(); + } + if (noVisitorLevel == 0 && !visitorStack.isEmpty()) { + visitorStack.peek().end(reporter); + } + } + + @Override + public void stringValue(JsonErrorReporter reporter, String value) { + if (noVisitorLevel == 0) { + visitorStack.peek().stringValue(reporter, value); + } + } + + @Override + public void intValue(JsonErrorReporter reporter, long value) { + if (noVisitorLevel == 0) { + visitorStack.peek().intValue(reporter, value); + } + } + + @Override + public void floatValue(JsonErrorReporter reporter, double value) { + if (noVisitorLevel == 0) { + visitorStack.peek().floatValue(reporter, value); + } + } + + @Override + public void nullValue(JsonErrorReporter reporter) { + if (noVisitorLevel == 0) { + visitorStack.peek().nullValue(reporter); + } + } + + @Override + public void booleanValue(JsonErrorReporter reporter, boolean value) { + if (noVisitorLevel == 0) { + visitorStack.peek().booleanValue(reporter, value); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitor.java b/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitor.java new file mode 100644 index 000000000..87c825565 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/json/JsonVisitor.java @@ -0,0 +1,48 @@ +/* + * 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.backend.c.util.json; + +public abstract class JsonVisitor { + public JsonVisitor object(JsonErrorReporter reporter) { + return null; + } + + public JsonVisitor array(JsonErrorReporter reporter) { + return null; + } + + public JsonVisitor property(JsonErrorReporter reporter, String name) { + return null; + } + + public void stringValue(JsonErrorReporter reporter, String value) { + } + + public void intValue(JsonErrorReporter reporter, long value) { + } + + public void floatValue(JsonErrorReporter reporter, double value) { + } + + public void nullValue(JsonErrorReporter reporter) { + } + + public void booleanValue(JsonErrorReporter reporter, boolean value) { + } + + public void end(JsonErrorReporter reporter) { + } +} diff --git a/core/src/main/java/org/teavm/runtime/CallSite.java b/core/src/main/java/org/teavm/runtime/CallSite.java index 4f10bf1fa..ef8434e7f 100644 --- a/core/src/main/java/org/teavm/runtime/CallSite.java +++ b/core/src/main/java/org/teavm/runtime/CallSite.java @@ -15,12 +15,12 @@ */ package org.teavm.runtime; -import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; -import org.teavm.interop.Unmanaged; +import org.teavm.interop.c.Name; +import org.teavm.interop.c.Native; -@Unmanaged -@StaticInit +@Native +@Name("TeaVM_CallSite") public class CallSite extends Structure { public ExceptionHandler firstHandler; public CallSiteLocation location; diff --git a/core/src/main/java/org/teavm/runtime/CallSiteLocation.java b/core/src/main/java/org/teavm/runtime/CallSiteLocation.java index 412d9abd0..f70731ae2 100644 --- a/core/src/main/java/org/teavm/runtime/CallSiteLocation.java +++ b/core/src/main/java/org/teavm/runtime/CallSiteLocation.java @@ -15,12 +15,12 @@ */ package org.teavm.runtime; -import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; -import org.teavm.interop.Unmanaged; +import org.teavm.interop.c.Name; +import org.teavm.interop.c.Native; -@Unmanaged -@StaticInit +@Native +@Name("TeaVM_CallSiteLocation") public class CallSiteLocation extends Structure { public MethodLocation method; public int lineNumber; diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandler.java b/core/src/main/java/org/teavm/runtime/ExceptionHandler.java index 7e964c180..db3f6dc22 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandler.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandler.java @@ -15,12 +15,12 @@ */ package org.teavm.runtime; -import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; -import org.teavm.interop.Unmanaged; +import org.teavm.interop.c.Name; +import org.teavm.interop.c.Native; -@Unmanaged -@StaticInit +@Native +@Name("TeaVM_ExceptionHandler") public class ExceptionHandler extends Structure { public int id; public RuntimeClass exceptionClass; diff --git a/core/src/main/java/org/teavm/runtime/GC.java b/core/src/main/java/org/teavm/runtime/GC.java index 4f257c014..63e71a314 100644 --- a/core/src/main/java/org/teavm/runtime/GC.java +++ b/core/src/main/java/org/teavm/runtime/GC.java @@ -16,6 +16,7 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.Export; import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; @@ -126,6 +127,14 @@ public final class GC { currentChunkLimit = currentChunk.toAddress().add(currentChunk.size); } + @Export(name = "teavm_gc_fixHeap") + public static void fixHeap() { + if (freeChunks > 0) { + currentChunk.classReference = 0; + currentChunk.size = (int) (currentChunkLimit.toLong() - currentChunk.toAddress().toLong()); + } + } + private static void mark() { MemoryTrace.initMark(); firstWeakReference = null; diff --git a/core/src/main/java/org/teavm/runtime/MemoryTrace.java b/core/src/main/java/org/teavm/runtime/MemoryTrace.java index 86b6a86ff..58b91d5e0 100644 --- a/core/src/main/java/org/teavm/runtime/MemoryTrace.java +++ b/core/src/main/java/org/teavm/runtime/MemoryTrace.java @@ -40,4 +40,6 @@ public class MemoryTrace { public static native void sweepCompleted(); public static native void defragCompleted(); + + public static native void writeHeapDump(); } diff --git a/core/src/main/java/org/teavm/runtime/MethodLocation.java b/core/src/main/java/org/teavm/runtime/MethodLocation.java index 814ceabe0..f8f5cda11 100644 --- a/core/src/main/java/org/teavm/runtime/MethodLocation.java +++ b/core/src/main/java/org/teavm/runtime/MethodLocation.java @@ -15,12 +15,12 @@ */ package org.teavm.runtime; -import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; -import org.teavm.interop.Unmanaged; +import org.teavm.interop.c.Name; +import org.teavm.interop.c.Native; -@Unmanaged -@StaticInit +@Native +@Name("TeaVM_MethodLocation") public class MethodLocation extends Structure { public StringPtr fileName; public StringPtr className; diff --git a/core/src/main/java/org/teavm/runtime/StringPtr.java b/core/src/main/java/org/teavm/runtime/StringPtr.java index 4a4fcfffe..00aabb71a 100644 --- a/core/src/main/java/org/teavm/runtime/StringPtr.java +++ b/core/src/main/java/org/teavm/runtime/StringPtr.java @@ -16,7 +16,11 @@ package org.teavm.runtime; import org.teavm.interop.Structure; +import org.teavm.interop.c.Name; +import org.teavm.interop.c.Native; +@Native +@Name("TeaVM_StringPtr") public class StringPtr extends Structure { - String value; + public String value; } diff --git a/core/src/main/resources/org/teavm/backend/c/heap.c b/core/src/main/resources/org/teavm/backend/c/heap.c new file mode 100644 index 000000000..a4c6f5971 --- /dev/null +++ b/core/src/main/resources/org/teavm/backend/c/heap.c @@ -0,0 +1,720 @@ +#include "runtime.h" +#include <string.h> +#include <stdint.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> +#include <uchar.h> + +#ifndef TEAVM_WINDOWS_LOG + #define TEAVM_OUTPUT_STRING(s) fprintf(stderr, s) +#else + #define TEAVM_OUTPUT_STRING(s) OutputDebugStringW(L##s) +#endif + +void teavm_outOfMemory() { + TEAVM_OUTPUT_STRING("Application crashed due to lack of free memory\n"); + teavm_gc_writeHeapDump(); + abort(); +} + +static wchar_t* teavm_gc_dumpDirectory = NULL; + +#if TEAVM_MEMORY_TRACE + void teavm_gc_assertSize(int32_t size) { + if (size % sizeof(void*) != 0) { + abort(); + } + } +#endif + +void teavm_gc_allocate(void* address, int32_t size) { + #if TEAVM_MEMORY_TRACE + teavm_gc_assertAddress(address); + teavm_gc_assertSize(size); + + size /= sizeof(void*); + uint8_t* map = teavm_gc_heapMap + (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); + + if (*map != 0) { + fprintf(stderr, "[GC] trying allocate at memory in use at: %d\n", + (int) ((char*) address - (char*) teavm_gc_heapAddress)); + abort(); + } + *map++ = 1; + + for (int32_t i = 1; i < size; ++i) { + if (*map != 0) { + fprintf(stderr, "[GC] trying allocate at memory in use at: %d\n", + (int) ((char*) address - (char*) teavm_gc_heapAddress)); + abort(); + } + *map++ = 2; + } + #endif +} + +void teavm_gc_free(void* address, int32_t size) { + #if TEAVM_MEMORY_TRACE + teavm_gc_assertAddress(address); + teavm_gc_assertSize(size); + + int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); + uint8_t* markMap = teavm_gc_markMap + offset; + size /= sizeof(void*); + for (int32_t i = 0; i < size; ++i) { + if (markMap[i] != 0) { + fprintf(stderr, "[GC] trying to release reachable object at: %d\n", + (int) ((char*) address - (char*) teavm_gc_heapAddress)); + abort(); + } + } + + uint8_t* map = teavm_gc_heapMap + offset; + memset(map, 0, size); + #endif +} + +void teavm_gc_assertFree(void* address, int32_t size) { + #if TEAVM_MEMORY_TRACE + teavm_gc_assertAddress(address); + teavm_gc_assertSize(size); + + int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); + uint8_t* map = teavm_gc_heapMap + offset; + size /= sizeof(void*); + for (int32_t i = 0; i < size; ++i) { + if (map[i] != 0) { + fprintf(stderr, "[GC] memory supposed to be free at: %d\n", + (int) ((char*) address - (char*) teavm_gc_heapAddress)); + abort(); + } + } + #endif +} + +void teavm_gc_initMark() { + #if TEAVM_MEMORY_TRACE + memset(teavm_gc_markMap, 0, teavm_gc_availableBytes / sizeof(void*)); + #endif +} + +int32_t teavm_gc_objectSize(void* address) { + TeaVM_Class* cls = TEAVM_CLASS_OF(address); + if (cls->itemType == NULL) { + return cls->size; + } + + int32_t itemSize = cls->itemType->flags & 2 ? cls->itemType->size : sizeof(void*); + TeaVM_Array* array = (TeaVM_Array*) address; + char* size = TEAVM_ALIGN((void*) sizeof(TeaVM_Array), itemSize); + size += array->size * itemSize; + size = TEAVM_ALIGN(size, sizeof(void*)); + return (int32_t) (intptr_t) size; +} + +void teavm_gc_mark(void* address) { + #if TEAVM_MEMORY_TRACE + if (address < teavm_gc_heapAddress + || (char*) address >= (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { + return; + } + + teavm_gc_assertAddress(address); + + int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); + uint8_t* map = teavm_gc_heapMap + offset; + uint8_t* markMap = teavm_gc_markMap + offset; + + int32_t size = teavm_gc_objectSize(address); + teavm_gc_assertSize(size); + size /= sizeof(void*); + + if (*map++ != 1 || *markMap != 0) { + fprintf(stderr, "[GC] assertion failed marking object at: %d\n", (int) ((char*) address - (char*) teavm_gc_heapAddress)); + abort(); + } + *markMap++ = 1; + + for (int32_t i = 1; i < size; ++i) { + if (*map++ != 2 || *markMap != 0) { + abort(); + } + *markMap++ = 1; + } + #endif +} + +void teavm_gc_move(void* from, void* to, int32_t size) { + #if TEAVM_MEMORY_TRACE + teavm_gc_assertAddress(from); + teavm_gc_assertAddress(to); + teavm_gc_assertSize(size); + + uint8_t* mapFrom = teavm_gc_heapMap + (((char*) from - (char*) teavm_gc_heapAddress) / sizeof(void*)); + uint8_t* mapTo = teavm_gc_heapMap + (((char*) to - (char*) teavm_gc_heapAddress) / sizeof(void*)); + size /= sizeof(void*); + + if (mapFrom > mapTo) { + for (int32_t i = 0; i < size; ++i) { + if (mapFrom[i] == 0 || mapTo[i] != 0) { + fprintf(stderr, "[GC] assertion failed moving object from: %d to %d\n", + (int) ((char*) from - (char*) teavm_gc_heapAddress), (int) ((char*) to - (char*) teavm_gc_heapAddress)); + abort(); + } + mapTo[i] = mapFrom[i]; + mapFrom[i] = 0; + } + } else { + for (int32_t i = size - 1; i >= 0; --i) { + if (mapFrom[i] == 0 || mapTo[i] != 0) { + abort(); + } + mapTo[i] = mapFrom[i]; + mapFrom[i] = 0; + } + } + #endif +} + +static FILE* teavm_gc_traceFile = NULL; + +static FILE* teavm_gc_openDumpFile(wchar_t* name) { + wchar_t* fullName = name; + size_t fullNameLen = wcslen(name); + if (teavm_gc_dumpDirectory != NULL) { + size_t prefixLen = wcslen(teavm_gc_dumpDirectory); + size_t nameLen = fullNameLen; + fullNameLen = nameLen + prefixLen; + fullName = malloc((prefixLen + nameLen + 1) * sizeof(wchar_t)); + memcpy(fullName, teavm_gc_dumpDirectory, prefixLen * sizeof(wchar_t)); + memcpy(fullName + prefixLen, name, (nameLen + 1) * sizeof(wchar_t)); + } + + FILE* result; + #ifdef _MSC_VER + _wfopen_s(&result, fullName, L"w"); + #else + size_t fullNameMbSize = 3 * (fullNameLen + 1); + char* fullNameMb = malloc(fullNameMbSize); + mbstate_t state = { 0 }; + wcsrtombs(fullNameMb, (const wchar_t **) &fullName, fullNameMbSize, &state); + result = fopen(fullNameMb, "w"); + free(fullNameMb); + #endif + + if (fullName != name) { + free(fullName); + } + + return result; +} + +#if TEAVM_MEMORY_TRACE + static void teavm_writeHeapMemory(char* name) { + #ifdef TEAVM_GC_LOG + if (teavm_gc_traceFile == NULL) { + teavm_gc_traceFile = teavm_gc_openDumpFile(L"teavm-gc-trace.txt"); + } + FILE* file = teavm_gc_traceFile; + fprintf(file, "%s:", name); + + int32_t numbers = 4096; + int64_t mapSize = teavm_gc_availableBytes / sizeof(void*); + for (int i = 0; i < numbers; ++i) { + int64_t start = mapSize * i / numbers; + int64_t end = mapSize * (i + 1) / numbers; + int count = 0; + for (int j = start; j < end; ++j) { + if (teavm_gc_heapMap[j] != 0) { + count++; + } + } + int rate = count * 4096 / (end - start); + fprintf(file, " %d", rate); + } + fprintf(file, "\n"); + fflush(file); + #endif + } + + void teavm_gc_checkHeapConsistency() { + TeaVM_Object* obj = teavm_gc_heapAddress; + while ((char*) obj < (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { + int32_t size; + if (obj->header == 0) { + size = obj->hash; + teavm_gc_assertFree(obj, size); + } else { + teavm_verify(obj); + TeaVM_Class* cls = TEAVM_CLASS_OF(obj); + if (cls->itemType != NULL) { + if (!(cls->itemType->flags & 2)) { + char* offset = NULL; + offset += sizeof(TeaVM_Array); + offset = TEAVM_ALIGN(offset, sizeof(void*)); + void** data = (void**)((char*)obj + (uintptr_t)offset); + int32_t size = ((TeaVM_Array*)obj)->size; + for (int32_t i = 0; i < size; ++i) { + teavm_verify(data[i]); + } + } + } else { + while (cls != NULL) { + int32_t kind = (cls->flags >> 7) & 7; + if (kind == 1) { + + } else if (kind == 2) { + + } else { + int16_t* layout = cls->layout; + if (layout != NULL) { + int16_t size = *layout++; + for (int32_t i = 0; i < size; ++i) { + void** ptr = (void**) ((char*) obj + *layout++); + teavm_verify(*ptr); + } + } + } + + cls = cls->superclass; + } + } + size = teavm_gc_objectSize(obj); + } + obj = (TeaVM_Object*) ((char*) obj + size); + } + } +#endif + +void teavm_gc_gcStarted() { + #if TEAVM_MEMORY_TRACE + teavm_writeHeapMemory("start"); + teavm_gc_checkHeapConsistency(); + #endif +} + +void teavm_gc_sweepCompleted() { + #if TEAVM_MEMORY_TRACE + teavm_writeHeapMemory("sweep"); + teavm_gc_checkHeapConsistency(); + #endif +} + +void teavm_gc_defragCompleted() { + #if TEAVM_MEMORY_TRACE + teavm_writeHeapMemory("defrag"); + #endif +} + +void teavm_gc_setDumpDirectory(const wchar_t* path) { + #if TEAVM_MEMORY_TRACE + if (teavm_gc_dumpDirectory != NULL) { + free(teavm_gc_dumpDirectory); + } + size_t pathLen = wcslen(path); + size_t bytesLen = sizeof(wchar_t) * (pathLen + 1); + teavm_gc_dumpDirectory = malloc(bytesLen); + memcpy(teavm_gc_dumpDirectory, path, bytesLen); + #endif +} + +#if TEAVM_HEAP_DUMP + static char* teavm_hexChars = "0123456789abcdef"; + + static void teavm_gc_escapeJsonString(FILE* out, char16_t* str) { + while (1) { + char16_t c = (char32_t) *str++; + if (c == 0) { + break; + } + + switch (c) { + case '\n': + fputc('\\', out); + fputc('n', out); + break; + case '\r': + fputc('\\', out); + fputc('r', out); + break; + case '\t': + fputc('\\', out); + fputc('t', out); + break; + case '\b': + fputc('\\', out); + fputc('b', out); + break; + case '\f': + fputc('\\', out); + fputc('f', out); + break; + case '\"': + fputc('\\', out); + fputc('\"', out); + break; + case '\\': + fputc('\\', out); + fputc('\\', out); + break; + default: + if (c < ' ' || c >= 127) { + fputc('\\', out); + fputc('u', out); + fputc(teavm_hexChars[(c >> 12) & 15], out); + fputc(teavm_hexChars[(c >> 8) & 15], out); + fputc(teavm_hexChars[(c >> 4) & 15], out); + fputc(teavm_hexChars[c & 15], out); + } else { + fputc(c, out); + } + break; + } + } + } + + static uint8_t teavm_gc_fieldTypeSize(uint8_t type) { + switch (type) { + case TEAVM_FIELD_TYPE_OBJECT: + case TEAVM_FIELD_TYPE_ARRAY: + return sizeof(void*); + case TEAVM_FIELD_TYPE_BOOLEAN: + case TEAVM_FIELD_TYPE_BYTE: + return 1; + case TEAVM_FIELD_TYPE_CHAR: + case TEAVM_FIELD_TYPE_SHORT: + return 2; + case TEAVM_FIELD_TYPE_INT: + case TEAVM_FIELD_TYPE_FLOAT: + return 4; + case TEAVM_FIELD_TYPE_LONG: + case TEAVM_FIELD_TYPE_DOUBLE: + return 8; + default: + return 0; + } + } + + static void teavm_gc_writeHeapDumpData(FILE* out, unsigned char* base, uint8_t size) { + uint64_t value = *(uint64_t*) base; + size *= 2; + for (int i = size - 1; i >= 0; --i) { + int shift = i * 4; + fputc(teavm_hexChars[(value >> shift) & 15], out); + } + } + + static char* teavm_gc_fieldTypeName(uint8_t type) { + switch (type) { + case TEAVM_FIELD_TYPE_OBJECT: + return "object"; + case TEAVM_FIELD_TYPE_ARRAY: + return "array"; + case TEAVM_FIELD_TYPE_BOOLEAN: + return "boolean"; + case TEAVM_FIELD_TYPE_BYTE: + return "byte"; + case TEAVM_FIELD_TYPE_CHAR: + return "char"; + case TEAVM_FIELD_TYPE_SHORT: + return "short"; + case TEAVM_FIELD_TYPE_INT: + return "int"; + case TEAVM_FIELD_TYPE_LONG: + return "long"; + case TEAVM_FIELD_TYPE_FLOAT: + return "float"; + case TEAVM_FIELD_TYPE_DOUBLE: + return "double"; + default: + return "unknown"; + } + } + + static char* teavm_gc_primitiveTypeName(int32_t type) { + switch (type) { + case 0: + return "boolean"; + case 1: + return "byte"; + case 2: + return "short"; + case 3: + return "char"; + case 4: + return "int"; + case 5: + return "long"; + case 6: + return "float"; + case 7: + return "double"; + default: + return "unknown"; + } + } + + static void teavm_gc_writeHeapDumpClasses(FILE* out) { + fprintf(out, "\"classes\":["); + for (int i = 0; i < teavm_classReferencesCount; ++i) { + if (i > 0) { + fprintf(out, ",\n"); + } + TeaVM_Class* cls = teavm_classReferences[i]; + fprintf(out, "{\"id\":%" PRIuPTR, (uintptr_t) cls); + + if (cls->name != NULL && !(cls->flags & 2) && cls->itemType == NULL) { + fprintf(out, ",\"name\":"); + char16_t* name = teavm_stringToC16(*cls->name); + fprintf(out, "\""); + teavm_gc_escapeJsonString(out, name); + fprintf(out, "\""); + free(name); + } + + if (cls->flags & 2) { + fprintf(out, ",\"primitive\":\"%s\"", teavm_gc_primitiveTypeName((cls->flags >> 3) & 7)); + } else if (cls->itemType != NULL) { + fprintf(out, ",\"item\":%" PRIuPTR, (uintptr_t) cls->itemType); + } else { + fprintf(out, ",\"size\":%" PRId32, cls->size); + fprintf(out, ",\"super\":"); + if (cls->superclass != NULL) { + fprintf(out, "%" PRIuPTR, (uintptr_t) cls->superclass); + } else { + fprintf(out, "null"); + } + + if (cls->fieldDescriptors != NULL) { + fprintf(out, ",\"fields\":["); + for (int j = 0; j < cls->fieldDescriptors->count; ++j) { + if (j > 0) { + fprintf(out, ","); + } + TeaVM_FieldDescriptor* field = &cls->fieldDescriptors->data[j]; + fprintf(out, "{\"name\":\""); + teavm_gc_escapeJsonString(out, field->name); + fprintf(out, "\",\"type\":\"%s\"}", teavm_gc_fieldTypeName(field->type)); + } + fprintf(out, "]"); + } + + if (cls->staticFieldDescriptors != NULL) { + fprintf(out, ",\"staticFields\":["); + for (int j = 0; j < cls->staticFieldDescriptors->count; ++j) { + if (j > 0) { + fprintf(out, ","); + } + TeaVM_StaticFieldDescriptor* field = &cls->staticFieldDescriptors->data[j]; + fprintf(out, "{\"name\":\""); + teavm_gc_escapeJsonString(out, field->name); + fprintf(out, "\",\"type\":\"%s\"}", teavm_gc_fieldTypeName(field->type)); + } + fprintf(out, "]"); + + fprintf(out, ",\"data\":\""); + for (int j = 0; j < cls->staticFieldDescriptors->count; ++j) { + TeaVM_StaticFieldDescriptor* field = &cls->staticFieldDescriptors->data[j]; + teavm_gc_writeHeapDumpData(out, field->offset, teavm_gc_fieldTypeSize(field->type)); + } + fprintf(out, "\""); + } + } + + fprintf(out, "}"); + } + fprintf(out, "\n]"); + } + + static int teavm_gc_classDepth(TeaVM_Class* cls) { + int depth = 0; + while (cls) { + depth++; + cls = cls->superclass; + } + return depth; + } + + static void teavm_gc_writeHeapDumpObject(FILE* out, TeaVM_Object* obj) { + TeaVM_Class* cls = TEAVM_CLASS_OF(obj); + fprintf(out, "{\"id\":%" PRIuPTR ",\"class\":%" PRIuPTR, (uintptr_t) obj, (uintptr_t) cls); + + if (cls->itemType != NULL) { + int32_t itemSize; + if (cls->itemType->flags & 2) { + itemSize = cls->itemType->size; + } else { + itemSize = sizeof(void*); + } + char* offset = NULL; + offset += sizeof(TeaVM_Array); + offset = TEAVM_ALIGN(offset, itemSize); + unsigned char* data = (unsigned char*) obj + (uintptr_t) offset; + int32_t size = ((TeaVM_Array*) obj)->size; + + fprintf(out, ",\"data\":\""); + int32_t limit = size * itemSize; + for (int32_t i = 0; i < limit; i += itemSize) { + teavm_gc_writeHeapDumpData(out, data + i, itemSize); + } + fprintf(out, "\""); + } else { + fprintf(out, ",\"data\":\""); + int classDepth = teavm_gc_classDepth(cls); + TeaVM_Class** classes = malloc(classDepth * sizeof(TeaVM_Class*)); + int i = classDepth; + while (cls != NULL) { + classes[--i] = cls; + cls = cls->superclass; + } + for (; i < classDepth; ++i) { + cls = classes[i]; + if (cls->fieldDescriptors != NULL) { + TeaVM_FieldDescriptors* fieldDescriptors = cls->fieldDescriptors; + for (int j = 0; j < fieldDescriptors->count; ++j) { + TeaVM_FieldDescriptor* field = &fieldDescriptors->data[j]; + teavm_gc_writeHeapDumpData(out, (unsigned char*) obj + field->offset, + teavm_gc_fieldTypeSize(field->type)); + } + } + } + fprintf(out, "\""); + } + + fprintf(out, "}"); + } + + static void teavm_gc_writeHeapDumpObjects(FILE* out) { + fprintf(out, "\"objects\":["); + + int first = 1; + TeaVM_Object* obj = teavm_gc_heapAddress; + while ((char*) obj < (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { + int32_t size; + if (obj->header == 0) { + size = obj->hash; + } else { + if (!first) { + fprintf(out, ","); + } + first = 0; + fprintf(out, "\n"); + teavm_gc_writeHeapDumpObject(out, obj); + size = teavm_gc_objectSize(obj); + } + obj = (TeaVM_Object*) ((char*) obj + size); + } + + TeaVM_HashtableEntrySet* strings = teavm_stringHashtableData; + while (strings != NULL) { + for (int32_t i = 0; i < strings->size; ++i) { + TeaVM_String* str = strings->data[i].data; + if ((char*) str >= (char*) teavm_gc_heapAddress + && (char*) str < (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { + break; + } + if (!first) { + fprintf(out, ","); + } + first = 0; + fprintf(out, "\n"); + teavm_gc_writeHeapDumpObject(out, (TeaVM_Object*) str); + fprintf(out, ",\n"); + teavm_gc_writeHeapDumpObject(out, (TeaVM_Object*) str->characters); + } + strings = strings->next; + } + + fprintf(out, "\n]"); + } + + static void teavm_gc_writeHeapDumpStack(FILE* out) { + fprintf(out, "\"stack\":["); + + TeaVM_StackFrame* frame = teavm_stackTop; + int first = 1; + while (frame != NULL) { + if (!first) { + fprintf(out, ","); + } + first = 0; + fprintf(out, "\n{"); + + void** data = &((struct { TeaVM_StackFrame frame; void* data; }*) frame)->data; + TeaVM_CallSite* callSite = TEAVM_FIND_CALLSITE(frame->callSiteId, frame); + + if (callSite->location != NULL) { + TeaVM_MethodLocation* method = callSite->location->method; + if (method != NULL) { + if (method->fileName != NULL) { + fprintf(out, "\"file\":\""); + char16_t* mbName = teavm_stringToC16(*method->fileName); + teavm_gc_escapeJsonString(out, mbName); + fprintf(out, "\","); + free(mbName); + } + if (method->className != NULL) { + fprintf(out, "\"class\":\""); + char16_t* mbName = teavm_stringToC16(*method->className); + teavm_gc_escapeJsonString(out, mbName); + fprintf(out, "\","); + free(mbName); + } + if (method->methodName != NULL) { + fprintf(out, "\"method\":\""); + char16_t* mbName = teavm_stringToC16(*method->methodName); + teavm_gc_escapeJsonString(out, mbName); + fprintf(out, "\","); + free(mbName); + } + } + if (callSite->location->lineNumber >= 0) { + fprintf(out, "\"line\":%" PRId32 ",", callSite->location->lineNumber); + } + } + + fprintf(out, "\"roots\":["); + int rootsFirst = 1; + for (int32_t i = 0; i < frame->size; ++i) { + if (data[i] != NULL) { + if (!rootsFirst) { + fprintf(out, ","); + } + rootsFirst = 0; + fprintf(out, "%" PRIuPTR, (uintptr_t) data[i]); + } + } + fprintf(out, "]}"); + frame = frame->next; + } + + fprintf(out, "\n]"); + } + + static void teavm_gc_writeHeapDumpTo(FILE* out) { + fprintf(out, "{\n"); + fprintf(out, "\"pointerSize\":%u,\n", (unsigned int) sizeof(void*)); + teavm_gc_writeHeapDumpClasses(out); + fprintf(out, ",\n"); + teavm_gc_writeHeapDumpObjects(out); + fprintf(out, ",\n"); + teavm_gc_writeHeapDumpStack(out); + fprintf(out, "\n}"); + } +#endif + +void teavm_gc_writeHeapDump() { + #if TEAVM_HEAP_DUMP + teavm_gc_fixHeap(); + FILE* out = teavm_gc_openDumpFile(L"teavm-heap-dump.json"); + if (out == NULL) { + fprintf(stdout, "Error: could not write heap dump"); + return; + } + teavm_gc_writeHeapDumpTo(out); + fclose(out); + #endif +} diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.c b/core/src/main/resources/org/teavm/backend/c/runtime.c index db5b346d1..b9b5d9304 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime.c @@ -254,17 +254,6 @@ void teavm_interrupt() { #endif -#ifndef TEAVM_WINDOWS_LOG - #define TEAVM_OUTPUT_STRING(s) fprintf(stderr, s) -#else - #define TEAVM_OUTPUT_STRING(s) OutputDebugStringW(L##s) -#endif - -void teavm_outOfMemory() { - TEAVM_OUTPUT_STRING("Application crashed due to lack of free memory\n"); - abort(); -} - static char16_t* teavm_utf16ToUtf32(char16_t* source, char32_t* target) { char16_t c = *source; if ((c & 0xFC00) == 0xD800) { @@ -426,7 +415,9 @@ char16_t* teavm_stringToC16(void* obj) { char16_t* javaChars = TEAVM_ARRAY_DATA(charArray, char16_t); size_t sz = charArray->size; char16_t* result = malloc((sz + 1) * sizeof(char16_t)); - memcpy(result, javaChars, sz * sizeof(char16_t)); + if (sz > 0) { + memcpy(result, javaChars, sz * sizeof(char16_t)); + } result[sz] = 0; return result; } @@ -598,306 +589,20 @@ void teavm_logchar(int32_t c) { } #endif -#ifdef TEAVM_MEMORY_TRACE +TeaVM_Class* teavm_classClass; +TeaVM_Class* teavm_objectClass; +TeaVM_Class* teavm_stringClass; +TeaVM_Class* teavm_charArrayClass; -static wchar_t* teavm_gc_dumpDirectory = NULL; - -void teavm_gc_assertSize(int32_t size) { - if (size % sizeof(void*) != 0) { - abort(); +void teavm_initClasses() { + teavm_beforeClasses = (char*) teavm_classReferences[0]; + for (int i = 1; i < teavm_classReferencesCount; ++i) { + char* c = (char*) teavm_classReferences[i]; + if (c < teavm_beforeClasses) teavm_beforeClasses = c; } -} - -#endif - -void teavm_gc_allocate(void* address, int32_t size) { - #ifdef TEAVM_MEMORY_TRACE - teavm_gc_assertAddress(address); - teavm_gc_assertSize(size); - - size /= sizeof(void*); - uint8_t* map = teavm_gc_heapMap + (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); - - if (*map != 0) { - fprintf(stderr, "[GC] trying allocate at memory in use at: %d\n", - (int) ((char*) address - (char*) teavm_gc_heapAddress)); - abort(); - } - *map++ = 1; - - for (int32_t i = 1; i < size; ++i) { - if (*map != 0) { - fprintf(stderr, "[GC] trying allocate at memory in use at: %d\n", - (int) ((char*) address - (char*) teavm_gc_heapAddress)); - abort(); - } - *map++ = 2; - } - #endif -} - -void teavm_gc_free(void* address, int32_t size) { - #ifdef TEAVM_MEMORY_TRACE - teavm_gc_assertAddress(address); - teavm_gc_assertSize(size); - - int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); - uint8_t* markMap = teavm_gc_markMap + offset; - size /= sizeof(void*); - for (int32_t i = 0; i < size; ++i) { - if (markMap[i] != 0) { - fprintf(stderr, "[GC] trying to release reachable object at: %d\n", - (int) ((char*) address - (char*) teavm_gc_heapAddress)); - abort(); - } - } - - uint8_t* map = teavm_gc_heapMap + offset; - memset(map, 0, size); - #endif -} - -void teavm_gc_assertFree(void* address, int32_t size) { - #ifdef TEAVM_MEMORY_TRACE - teavm_gc_assertAddress(address); - teavm_gc_assertSize(size); - - int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); - uint8_t* map = teavm_gc_heapMap + offset; - size /= sizeof(void*); - for (int32_t i = 0; i < size; ++i) { - if (map[i] != 0) { - fprintf(stderr, "[GC] memory supposed to be free at: %d\n", - (int) ((char*) address - (char*) teavm_gc_heapAddress)); - abort(); - } - } - #endif -} - -void teavm_gc_initMark() { - #ifdef TEAVM_MEMORY_TRACE - memset(teavm_gc_markMap, 0, teavm_gc_availableBytes / sizeof(void*)); - #endif -} - -#ifdef TEAVM_MEMORY_TRACE -int32_t teavm_gc_objectSize(void* address) { - TeaVM_Class* cls = TEAVM_CLASS_OF(address); - if (cls->itemType == NULL) { - return cls->size; + teavm_beforeClasses -= 4096; + int32_t classHeader = TEAVM_PACK_CLASS(teavm_classClass) | (int32_t) INT32_C(0x80000000); + for (int i = 0; i < teavm_classReferencesCount; ++i) { + teavm_classReferences[i]->parent.header = classHeader; } - - int32_t itemSize = cls->itemType->flags & 2 ? cls->itemType->size : sizeof(void*); - TeaVM_Array* array = (TeaVM_Array*) address; - char* size = TEAVM_ALIGN((void*) sizeof(TeaVM_Array), itemSize); - size += array->size * itemSize; - size = TEAVM_ALIGN(size, sizeof(void*)); - return (int32_t) (intptr_t) size; -} -#endif - -void teavm_gc_mark(void* address) { - #ifdef TEAVM_MEMORY_TRACE - if (address < teavm_gc_heapAddress - || (char*) address >= (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { - return; - } - - teavm_gc_assertAddress(address); - - int32_t offset = (int32_t) (((char*) address - (char*) teavm_gc_heapAddress) / sizeof(void*)); - uint8_t* map = teavm_gc_heapMap + offset; - uint8_t* markMap = teavm_gc_markMap + offset; - - int32_t size = teavm_gc_objectSize(address); - teavm_gc_assertSize(size); - size /= sizeof(void*); - - if (*map++ != 1 || *markMap != 0) { - fprintf(stderr, "[GC] assertion failed marking object at: %d\n", (int) ((char*) address - (char*) teavm_gc_heapAddress)); - abort(); - } - *markMap++ = 1; - - for (int32_t i = 1; i < size; ++i) { - if (*map++ != 2 || *markMap != 0) { - abort(); - } - *markMap++ = 1; - } - #endif -} - -void teavm_gc_move(void* from, void* to, int32_t size) { - #ifdef TEAVM_MEMORY_TRACE - teavm_gc_assertAddress(from); - teavm_gc_assertAddress(to); - teavm_gc_assertSize(size); - - uint8_t* mapFrom = teavm_gc_heapMap + (((char*) from - (char*) teavm_gc_heapAddress) / sizeof(void*)); - uint8_t* mapTo = teavm_gc_heapMap + (((char*) to - (char*) teavm_gc_heapAddress) / sizeof(void*)); - size /= sizeof(void*); - - if (mapFrom > mapTo) { - for (int32_t i = 0; i < size; ++i) { - if (mapFrom[i] == 0 || mapTo[i] != 0) { - fprintf(stderr, "[GC] assertion failed moving object from: %d to %d\n", - (int) ((char*) from - (char*) teavm_gc_heapAddress), (int) ((char*) to - (char*) teavm_gc_heapAddress)); - abort(); - } - mapTo[i] = mapFrom[i]; - mapFrom[i] = 0; - } - } else { - for (int32_t i = size - 1; i >= 0; --i) { - if (mapFrom[i] == 0 || mapTo[i] != 0) { - abort(); - } - mapTo[i] = mapFrom[i]; - mapFrom[i] = 0; - } - } - #endif -} - -static FILE* teavm_gc_traceFile = NULL; - -static FILE* teavm_gc_openDumpFile(wchar_t* name) { - wchar_t* fullName = name; - size_t fullNameLen = wcslen(name); - if (teavm_gc_dumpDirectory != NULL) { - size_t prefixLen = wcslen(teavm_gc_dumpDirectory); - size_t nameLen = fullNameLen; - fullNameLen = nameLen + prefixLen; - fullName = malloc((prefixLen + nameLen + 1) * sizeof(wchar_t)); - memcpy(fullName, teavm_gc_dumpDirectory, prefixLen * sizeof(wchar_t)); - memcpy(fullName + prefixLen, name, (nameLen + 1) * sizeof(wchar_t)); - } - - FILE* result; - #ifdef _MSC_VER - _wfopen_s(&result, fullName, L"w"); - #else - size_t fullNameMbSize = 3 * (fullNameLen + 1) * sizeof(wchar_t); - char* fullNameMb = malloc(fullNameMbSize); - mbstate_t state = { 0 }; - wcsrtombs(fullNameMb, fullName, fullNameMbSize, state); - result = fopen(fullNameMb, "w"); - #endif - - if (fullName != name) { - free(fullName); - } - - return result; -} - -#ifdef TEAVM_MEMORY_TRACE - static void teavm_writeHeapMemory(char* name) { - #ifdef TEAVM_GC_LOG - if (teavm_gc_traceFile == NULL) { - teavm_gc_traceFile = teavm_gc_openDumpFile(L"teavm-gc-trace.txt"); - } - FILE* file = teavm_gc_traceFile; - fprintf(file, "%s:", name); - - int32_t numbers = 4096; - int64_t mapSize = teavm_gc_availableBytes / sizeof(void*); - for (int i = 0; i < numbers; ++i) { - int64_t start = mapSize * i / numbers; - int64_t end = mapSize * (i + 1) / numbers; - int count = 0; - for (int j = start; j < end; ++j) { - if (teavm_gc_heapMap[j] != 0) { - count++; - } - } - int rate = count * 4096 / (end - start); - fprintf(file, " %d", rate); - } - fprintf(file, "\n"); - fflush(file); - #endif - } - - void teavm_gc_checkHeapConsistency() { - TeaVM_Object* obj = teavm_gc_heapAddress; - while ((char*) obj < (char*) teavm_gc_heapAddress + teavm_gc_availableBytes) { - int32_t size; - if (obj->header == 0) { - size = obj->hash; - teavm_gc_assertFree(obj, size); - } else { - teavm_verify(obj); - TeaVM_Class* cls = TEAVM_CLASS_OF(obj); - if (cls->itemType != NULL) { - if (!(cls->itemType->flags & 2)) { - char* offset = NULL; - offset += sizeof(TeaVM_Array); - offset = TEAVM_ALIGN(offset, sizeof(void*)); - void** data = (void**)((char*)obj + (uintptr_t)offset); - int32_t size = ((TeaVM_Array*)obj)->size; - for (int32_t i = 0; i < size; ++i) { - teavm_verify(data[i]); - } - } - } else { - while (cls != NULL) { - int32_t kind = (cls->flags >> 7) & 7; - if (kind == 1) { - - } else if (kind == 2) { - - } else { - int16_t* layout = cls->layout; - if (layout != NULL) { - int16_t size = *layout++; - for (int32_t i = 0; i < size; ++i) { - void** ptr = (void**) ((char*) obj + *layout++); - teavm_verify(*ptr); - } - } - } - - cls = cls->superclass; - } - } - size = teavm_gc_objectSize(obj); - } - obj = (TeaVM_Object*) ((char*) obj + size); - } - } -#endif - -void teavm_gc_gcStarted() { - #ifdef TEAVM_MEMORY_TRACE - teavm_writeHeapMemory("start"); - teavm_gc_checkHeapConsistency(); - #endif -} - -void teavm_gc_sweepCompleted() { - #ifdef TEAVM_MEMORY_TRACE - teavm_writeHeapMemory("sweep"); - teavm_gc_checkHeapConsistency(); - #endif -} - -void teavm_gc_defragCompleted() { - #ifdef TEAVM_MEMORY_TRACE - teavm_writeHeapMemory("defrag"); - #endif -} - -void teavm_gc_setDumpDirectory(const wchar_t* path) { - #ifdef TEAVM_MEMORY_TRACE - if (teavm_gc_dumpDirectory != NULL) { - free(teavm_gc_dumpDirectory); - } - size_t pathLen = wcslen(path); - size_t bytesLen = sizeof(wchar_t) * (pathLen + 1); - teavm_gc_dumpDirectory = malloc(bytesLen); - memcpy(teavm_gc_dumpDirectory, path, bytesLen); - #endif } \ No newline at end of file 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 b23343e0a..52ffe865a 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.h +++ b/core/src/main/resources/org/teavm/backend/c/runtime.h @@ -27,6 +27,53 @@ #endif +#ifndef TEAVM_MEMORY_TRACE + #define TEAVM_MEMORY_TRACE 0 +#endif + +#if TEAVM_MEMORY_TRACE + #ifndef TEAVM_HEAP_DUMP + #define TEAVM_HEAP_DUMP 1 + #endif +#endif + +#ifndef TEAVM_HEAP_DUMP + #define TEAVM_HEAP_DUMP 0 +#endif + +#define TEAVM_FIELD_TYPE_OBJECT 0 +#define TEAVM_FIELD_TYPE_ARRAY 1 +#define TEAVM_FIELD_TYPE_BOOLEAN 2 +#define TEAVM_FIELD_TYPE_BYTE 3 +#define TEAVM_FIELD_TYPE_CHAR 4 +#define TEAVM_FIELD_TYPE_SHORT 5 +#define TEAVM_FIELD_TYPE_INT 6 +#define TEAVM_FIELD_TYPE_LONG 7 +#define TEAVM_FIELD_TYPE_FLOAT 8 +#define TEAVM_FIELD_TYPE_DOUBLE 9 + +typedef struct { + uint16_t offset; + uint8_t type; + char16_t* name; +} TeaVM_FieldDescriptor; + +typedef struct { + uint32_t count; + TeaVM_FieldDescriptor data[65536]; +} TeaVM_FieldDescriptors; + +typedef struct { + unsigned char* offset; + uint8_t type; + char16_t* name; +} TeaVM_StaticFieldDescriptor; + +typedef struct { + uint32_t count; + TeaVM_StaticFieldDescriptor data[65536]; +} TeaVM_StaticFieldDescriptors; + typedef struct TeaVM_Object { int32_t header; int32_t hash; @@ -54,6 +101,10 @@ typedef struct TeaVM_Class { void* enumValues; void* layout; TeaVM_Object* simpleName; + #if TEAVM_HEAP_DUMP + TeaVM_FieldDescriptors* fieldDescriptors; + TeaVM_StaticFieldDescriptors* staticFieldDescriptors; + #endif } TeaVM_Class; typedef struct TeaVM_String { @@ -62,10 +113,52 @@ typedef struct TeaVM_String { int32_t hashCode; } TeaVM_String; +#define TEAVM_HASHTABLE_ENTRIES 512 + +typedef struct TeaVM_HashtableEntry { + TeaVM_String* data; + int32_t hash; + struct TeaVM_HashtableEntry* next; +} TeaVM_HashtableEntry; + +typedef struct TeaVM_HashtableEntrySet { + TeaVM_HashtableEntry data[TEAVM_HASHTABLE_ENTRIES]; + int32_t size; + struct TeaVM_HashtableEntrySet* next; +} TeaVM_HashtableEntrySet; + +extern TeaVM_HashtableEntrySet* teavm_stringHashtableData; + +typedef struct { + TeaVM_String* value; +} TeaVM_StringPtr; + +typedef struct TeaVM_MethodLocation { + TeaVM_String** fileName; + TeaVM_String** className; + TeaVM_String** methodName; +} TeaVM_MethodLocation; + +typedef struct TeaVM_CallSiteLocation { + TeaVM_MethodLocation* method; + int32_t lineNumber; +} TeaVM_CallSiteLocation; + +typedef struct TeaVM_ExceptionHandler { + int32_t id; + TeaVM_Class* exceptionClass; + struct TeaVM_ExceptionHandler* next; +} TeaVM_ExceptionHandler; + +typedef struct TeaVM_CallSite { + TeaVM_ExceptionHandler* firstHandler; + TeaVM_CallSiteLocation* location; +} TeaVM_CallSite; + typedef struct TeaVM_StackFrame { struct TeaVM_StackFrame* next; #ifdef TEAVM_INCREMENTAL - void* callSites; + TeaVM_CallSite* callSites; #endif #ifdef TEAVM_USE_SETJMP jmp_buf* jmpTarget; @@ -74,6 +167,13 @@ typedef struct TeaVM_StackFrame { int32_t callSiteId; } TeaVM_StackFrame; +#ifndef TEAVM_INCREMENTAL + extern TeaVM_CallSite teavm_callSites[]; + #define TEAVM_FIND_CALLSITE(id, frame) (teavm_callSites + id) +#else + #define TEAVM_FIND_CALLSITE(id, frame) (((TeaVM_StackFrame*) (frame))->callSites + id) +#endif + extern void* teavm_gc_heapAddress; extern void* teavm_gc_gcStorageAddress; extern int32_t teavm_gc_gcStorageSize; @@ -84,7 +184,7 @@ extern int64_t teavm_gc_availableBytes; extern void*** teavm_gc_staticRoots; extern char* teavm_beforeClasses; -#ifdef TEAVM_MEMORY_TRACE +#if TEAVM_MEMORY_TRACE extern uint8_t* teavm_gc_heapMap; extern uint8_t* teavm_gc_markMap; #endif @@ -94,7 +194,7 @@ extern char* teavm_beforeClasses; #define TEAVM_CLASS_OF(obj) (TEAVM_UNPACK_CLASS(((TeaVM_Object*) (obj))->header)) #define TEAVM_AS(ptr, type) ((type*) (ptr)) -#ifdef TEAVM_MEMORY_TRACE +#if TEAVM_MEMORY_TRACE static inline void teavm_gc_assertAddress(void* address) { if ((unsigned int) (uintptr_t) address % sizeof(void*) != 0) { abort(); @@ -474,4 +574,14 @@ extern void teavm_gc_move(void* from, void* to, int32_t size); extern void teavm_gc_gcStarted(); extern void teavm_gc_sweepCompleted(); extern void teavm_gc_defragCompleted(); -extern void teavm_gc_setDumpDirectory(const wchar_t* path); \ No newline at end of file +extern void teavm_gc_setDumpDirectory(const wchar_t* path); +extern void teavm_gc_fixHeap(); +extern void teavm_gc_writeHeapDump(); + +extern TeaVM_Class* teavm_classReferences[]; +extern TeaVM_Class* teavm_classClass; +extern TeaVM_Class* teavm_objectClass; +extern TeaVM_Class* teavm_stringClass; +extern TeaVM_Class* teavm_charArrayClass; +extern int32_t teavm_classReferencesCount; +extern void teavm_initClasses(); \ No newline at end of file diff --git a/core/src/main/resources/org/teavm/backend/c/stringhash.c b/core/src/main/resources/org/teavm/backend/c/stringhash.c index a4cbd62db..29b5029b1 100644 --- a/core/src/main/resources/org/teavm/backend/c/stringhash.c +++ b/core/src/main/resources/org/teavm/backend/c/stringhash.c @@ -5,22 +5,8 @@ #include <uchar.h> #include <wchar.h> -#define TEAVM_HASHTABLE_ENTRIES 512 - -typedef struct TeaVM_HashtableEntry { - TeaVM_String* data; - int32_t hash; - struct TeaVM_HashtableEntry* next; -} TeaVM_HashtableEntry; - -typedef struct TeaVM_HashtableEntrySet { - TeaVM_HashtableEntry data[TEAVM_HASHTABLE_ENTRIES]; - int32_t size; - struct TeaVM_HashtableEntrySet* next; -} TeaVM_HashtableEntrySet; - static TeaVM_HashtableEntry** teavm_stringHashtable = NULL; -static TeaVM_HashtableEntrySet* teavm_stringHashtableData = NULL; +TeaVM_HashtableEntrySet* teavm_stringHashtableData = NULL; static int32_t teavm_stringHashtableSize = 0; static int32_t teavm_stringHashtableFill = 0; static int32_t teavm_stringHashtableThreshold = 0; @@ -80,6 +66,9 @@ static void teavm_rehashStrings() { } TeaVM_String* teavm_registerString(TeaVM_String* str) { + str->parent.header = TEAVM_PACK_CLASS(teavm_stringClass); + str->characters->parent.header = TEAVM_PACK_CLASS(teavm_charArrayClass); + if (teavm_stringHashtable == NULL) { teavm_stringHashtableSize = 256; teavm_updateStringHashtableThreshold(); 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 index 168e55e47..889fd55dc 100644 --- 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 @@ -333,6 +333,7 @@ public class IncrementalCBuilder { cTarget.setMinHeapSize(minHeapSize * 1024 * 1024); cTarget.setLineNumbersGenerated(lineNumbersGenerated); cTarget.setLongjmpUsed(longjmpSupported); + cTarget.setHeapDump(true); vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); vm.setCacheStatus(classSource); vm.addVirtualMethods(m -> true); diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index 0666555b7..4d8d8659b 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -322,6 +322,9 @@ public final class TeaVMRunner { if (commandLine.hasOption("no-longjmp")) { tool.setLongjmpSupported(false); } + if (commandLine.hasOption("heap-dump")) { + tool.setHeapDump(true); + } } private void parseHeap() { diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 6975e5cdd..742d0de63 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -104,6 +104,7 @@ public class TeaVMTool { private int minHeapSize = 32 * (1 << 20); private ReferenceCache referenceCache; private boolean longjmpSupported = true; + private boolean heapDump; public File getTargetDirectory() { return targetDirectory; @@ -253,6 +254,10 @@ public class TeaVMTool { this.longjmpSupported = longjmpSupported; } + public void setHeapDump(boolean heapDump) { + this.heapDump = heapDump; + } + public void setProgressListener(TeaVMProgressListener progressListener) { this.progressListener = progressListener; } @@ -328,6 +333,7 @@ public class TeaVMTool { cTarget.setMinHeapSize(minHeapSize); cTarget.setLineNumbersGenerated(debugInformationGenerated); cTarget.setLongjmpUsed(longjmpSupported); + cTarget.setHeapDump(heapDump); return cTarget; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 38af51eda..e222b4618 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -76,5 +76,7 @@ public interface BuildStrategy { void setLongjmpSupported(boolean value); + void setHeapDump(boolean heapDump); + BuildResult build() throws BuildException; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index 1c1bcd2f9..d23f51710 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -62,6 +62,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private int heapSize = 32; private final List<SourceFileProvider> sourceFileProviders = new ArrayList<>(); private boolean longjmpSupported = true; + private boolean heapDump; private TeaVMProgressListener progressListener; private Properties properties = new Properties(); private TeaVMToolLog log = new EmptyTeaVMToolLog(); @@ -202,6 +203,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.longjmpSupported = longjmpSupported; } + @Override + public void setHeapDump(boolean heapDump) { + this.heapDump = heapDump; + } + @Override public BuildResult build() throws BuildException { TeaVMTool tool = new TeaVMTool(); @@ -229,6 +235,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setWasmVersion(wasmVersion); tool.setMinHeapSize(heapSize); tool.setLongjmpSupported(longjmpSupported); + tool.setHeapDump(heapDump); tool.getProperties().putAll(properties); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index b04acc5ed..3fb413bb8 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -180,6 +180,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.longjmpSupported = value; } + @Override + public void setHeapDump(boolean heapDump) { + request.heapDump = heapDump; + } + @Override public BuildResult build() throws BuildException { RemoteBuildResponse response; diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java index e5371e1d9..e1abcc46b 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java @@ -160,6 +160,7 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi tool.setWasmVersion(request.wasmVersion); tool.setMinHeapSize(request.heapSize); tool.setLongjmpSupported(request.longjmpSupported); + tool.setHeapDump(request.heapDump); for (String sourceDirectory : request.sourceDirectories) { tool.addSourceFileProvider(new DirectorySourceFileProvider(new File(sourceDirectory))); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index ae008f59c..ce64eaa85 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -47,4 +47,5 @@ public class RemoteBuildRequest implements Serializable { public WasmBinaryVersion wasmVersion; public int heapSize; public boolean longjmpSupported; + public boolean heapDump; } diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index 5f8a71eb4..d9f3342f9 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -149,6 +149,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.longjmpSupported", defaultValue = "true") private boolean longjmpSupported; + @Parameter(property = "teavm.heapDump", defaultValue = "false") + private boolean heapDump; + private void setupBuilder(BuildStrategy builder) throws MojoExecutionException { builder.setLog(new MavenTeaVMToolLog(getLog())); try { @@ -281,6 +284,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setTargetType(targetType); builder.setWasmVersion(wasmVersion); builder.setLongjmpSupported(longjmpSupported); + builder.setHeapDump(heapDump); BuildResult result; result = builder.build(); TeaVMProblemRenderer.describeProblems(result.getCallGraph(), result.getProblems(), toolLog);