C: add ability to write heap dump when application crashes

This commit is contained in:
Alexey Andreev 2019-08-13 16:53:14 +03:00
parent e0ce6d1df6
commit 492fd004af
37 changed files with 2909 additions and 450 deletions

View File

@ -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) {

View File

@ -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";
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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(", ");

View File

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

View File

@ -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;
}
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

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

View File

@ -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;
}
}

View File

@ -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';
}
}

View File

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

View File

@ -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;
}
}

View File

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

View File

@ -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) {
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -40,4 +40,6 @@ public class MemoryTrace {
public static native void sweepCompleted();
public static native void defragCompleted();
public static native void writeHeapDump();
}

View File

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

View File

@ -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;
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

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

View File

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

View File

@ -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() {

View File

@ -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;
}

View File

@ -76,5 +76,7 @@ public interface BuildStrategy {
void setLongjmpSupported(boolean value);
void setHeapDump(boolean heapDump);
BuildResult build() throws BuildException;
}

View File

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

View File

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

View File

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

View File

@ -47,4 +47,5 @@ public class RemoteBuildRequest implements Serializable {
public WasmBinaryVersion wasmVersion;
public int heapSize;
public boolean longjmpSupported;
public boolean heapDump;
}

View File

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