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);