From 7b3905246ba0ac61b8f9c4b2098270721c9e934c Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 16 Dec 2022 17:13:17 +0100 Subject: [PATCH] Wasm: support class layout in debug information --- .../org/teavm/backend/wasm/WasmTarget.java | 3 + .../backend/wasm/debug/DebugClassLayout.java | 37 ++ .../wasm/debug/DebugClassLayoutBuilder.java | 183 ++++++++ .../backend/wasm/debug/DebugConstants.java | 29 ++ .../backend/wasm/debug/DebugInfoBuilder.java | 7 + .../backend/wasm/debug/info/ArrayLayout.java | 25 ++ .../backend/wasm/debug/info/ClassLayout.java | 35 ++ .../wasm/debug/info/ClassLayoutInfo.java | 105 +++++ .../backend/wasm/debug/info/DebugInfo.java | 13 +- .../backend/wasm/debug/info/FieldInfo.java | 24 ++ .../backend/wasm/debug/info/FieldType.java | 30 ++ .../wasm/debug/info/InterfaceLayout.java | 25 ++ .../wasm/debug/info/PrimitiveLayout.java | 27 ++ .../backend/wasm/debug/info/TypeLayout.java | 22 + .../wasm/debug/info/TypeLayoutKind.java | 24 ++ .../wasm/debug/info/UnknownLayout.java | 23 + .../debug/parser/DebugClassLayoutParser.java | 400 ++++++++++++++++++ .../wasm/debug/parser/DebugInfoParser.java | 5 +- .../wasm/generate/WasmClassGenerator.java | 115 ++++- 19 files changed, 1125 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayoutBuilder.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/ArrayLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayoutInfo.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/FieldInfo.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/FieldType.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/InterfaceLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/PrimitiveLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayoutKind.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/info/UnknownLayout.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassLayoutParser.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index daf92e6dd..91a842b10 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -552,6 +552,9 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { var writer = new WasmBinaryWriter(); var debugBuilder = debugging ? new DebugInfoBuilder() : null; + if (debugBuilder != null) { + classGenerator.writeDebug(debugBuilder.classLayout()); + } var renderer = new WasmBinaryRenderer( writer, version, obfuscated, dwarfGenerator, dwarfClassGen, debugBuilder != null ? debugBuilder.lines() : null, diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayout.java new file mode 100644 index 000000000..9c59ca333 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayout.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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.wasm.debug; + +import org.teavm.backend.wasm.debug.info.FieldType; +import org.teavm.model.PrimitiveType; + +public interface DebugClassLayout { + void startClass(String name, int parent, int address, int size); + + void instanceField(String name, int offset, FieldType type); + + void staticField(String name, int offset, FieldType type); + + void endClass(); + + void writeInterface(String name, int address); + + void writePrimitive(PrimitiveType type, int address); + + void writeArray(int itemType, int address); + + void writeUnknown(int address); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayoutBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayoutBuilder.java new file mode 100644 index 000000000..56b0c30f2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassLayoutBuilder.java @@ -0,0 +1,183 @@ +/* + * Copyright 2022 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.wasm.debug; + +import org.teavm.backend.wasm.debug.info.FieldType; +import org.teavm.model.PrimitiveType; + +public class DebugClassLayoutBuilder extends DebugSectionBuilder implements DebugClassLayout { + private DebugClasses classes; + private DebugStrings strings; + private int currentIndex; + private int currentAddress; + private ClassPhase phase = ClassPhase.NO_CLASS; + private int lastFieldOffset; + + public DebugClassLayoutBuilder(DebugClasses classes, DebugStrings strings) { + super(DebugConstants.SECTION_CLASS_LAYOUT); + this.classes = classes; + this.strings = strings; + } + + @Override + public void startClass(String name, int parent, int address, int size) { + blob.writeByte(parent >= 0 ? DebugConstants.CLASS_CLASS : DebugConstants.CLASS_ROOT); + blob.writeLEB(classes.classPtr(name)); + if (parent >= 0) { + blob.writeSLEB(currentIndex - parent); + } + writeAddress(address); + blob.writeLEB(size); + lastFieldOffset = 0; + phase = ClassPhase.STATIC_FIELDS; + } + + @Override + public void instanceField(String name, int offset, FieldType type) { + if (phase == ClassPhase.STATIC_FIELDS) { + blob.writeByte(DebugConstants.FIELD_END_SEQUENCE); + lastFieldOffset = 0; + phase = ClassPhase.INSTANCE_FIELDS; + } + writeFieldType(type); + blob.writeLEB(strings.stringPtr(name)); + blob.writeSLEB(offset - lastFieldOffset); + lastFieldOffset = offset; + } + + @Override + public void staticField(String name, int offset, FieldType type) { + writeFieldType(type); + blob.writeLEB(strings.stringPtr(name)); + blob.writeSLEB(offset - lastFieldOffset); + lastFieldOffset = offset; + } + + private void writeFieldType(FieldType type) { + switch (type) { + case BOOLEAN: + blob.writeByte(DebugConstants.FIELD_BOOLEAN); + break; + case BYTE: + blob.writeByte(DebugConstants.FIELD_BYTE); + break; + case SHORT: + blob.writeByte(DebugConstants.FIELD_SHORT); + break; + case CHAR: + blob.writeByte(DebugConstants.FIELD_CHAR); + break; + case INT: + blob.writeByte(DebugConstants.FIELD_INT); + break; + case LONG: + blob.writeByte(DebugConstants.FIELD_LONG); + break; + case FLOAT: + blob.writeByte(DebugConstants.FIELD_FLOAT); + break; + case DOUBLE: + blob.writeByte(DebugConstants.FIELD_DOUBLE); + break; + case OBJECT: + blob.writeByte(DebugConstants.FIELD_OBJECT); + break; + case ADDRESS: + blob.writeByte(DebugConstants.FIELD_ADDRESS); + break; + case UNDEFINED: + blob.writeByte(DebugConstants.FIELD_UNDEFINED); + break; + } + } + + @Override + public void endClass() { + if (phase == ClassPhase.STATIC_FIELDS) { + blob.writeByte(DebugConstants.FIELD_END); + } + if (phase == ClassPhase.INSTANCE_FIELDS) { + blob.writeByte(DebugConstants.FIELD_END_SEQUENCE); + } + ++currentIndex; + } + + @Override + public void writeInterface(String name, int address) { + blob.writeByte(DebugConstants.CLASS_INTERFACE); + blob.writeLEB(classes.classPtr(name)); + writeAddress(address); + ++currentIndex; + } + + @Override + public void writePrimitive(PrimitiveType type, int address) { + switch (type) { + case BOOLEAN: + blob.writeLEB(DebugConstants.CLASS_BOOLEAN); + break; + case BYTE: + blob.writeLEB(DebugConstants.CLASS_BYTE); + break; + case SHORT: + blob.writeLEB(DebugConstants.CLASS_SHORT); + break; + case CHARACTER: + blob.writeLEB(DebugConstants.CLASS_CHAR); + break; + case INTEGER: + blob.writeLEB(DebugConstants.CLASS_INT); + break; + case LONG: + blob.writeLEB(DebugConstants.CLASS_LONG); + break; + case FLOAT: + blob.writeLEB(DebugConstants.CLASS_FLOAT); + break; + case DOUBLE: + blob.writeLEB(DebugConstants.CLASS_DOUBLE); + break; + } + writeAddress(address); + ++currentIndex; + } + + @Override + public void writeArray(int itemType, int address) { + blob.writeByte(DebugConstants.CLASS_ARRAY); + blob.writeSLEB(currentIndex - itemType); + writeAddress(address); + ++currentIndex; + } + + @Override + public void writeUnknown(int address) { + blob.writeByte(DebugConstants.CLASS_UNKNOWN); + writeAddress(address); + ++currentIndex; + } + + private void writeAddress(int address) { + blob.writeSLEB(address - currentAddress); + currentAddress = address; + } + + private enum ClassPhase { + NO_CLASS, + STATIC_FIELDS, + INSTANCE_FIELDS + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java index fcc3cc85a..b53cc2578 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugConstants.java @@ -26,10 +26,39 @@ public final class DebugConstants { public static final int LOC_PTR = 4; public static final int LOC_USER = 10; + public static final int CLASS_ROOT = 0; + public static final int CLASS_CLASS = 1; + public static final int CLASS_INTERFACE = 2; + public static final int CLASS_ARRAY = 3; + public static final int CLASS_BOOLEAN = 4; + public static final int CLASS_BYTE = 5; + public static final int CLASS_SHORT = 6; + public static final int CLASS_CHAR = 7; + public static final int CLASS_INT = 8; + public static final int CLASS_LONG = 9; + public static final int CLASS_FLOAT = 10; + public static final int CLASS_DOUBLE = 11; + public static final int CLASS_UNKNOWN = 12; + + public static final int FIELD_END = 0; + public static final int FIELD_END_SEQUENCE = 1; + public static final int FIELD_BOOLEAN = 2; + public static final int FIELD_BYTE = 3; + public static final int FIELD_SHORT = 4; + public static final int FIELD_CHAR = 5; + public static final int FIELD_INT = 6; + public static final int FIELD_LONG = 7; + public static final int FIELD_FLOAT = 8; + public static final int FIELD_DOUBLE = 9; + public static final int FIELD_OBJECT = 10; + public static final int FIELD_ADDRESS = 11; + public static final int FIELD_UNDEFINED = 12; + public static final String SECTION_STRINGS = "teavm_str"; public static final String SECTION_FILES = "teavm_file"; public static final String SECTION_PACKAGES = "teavm_pkg"; public static final String SECTION_CLASSES = "teavm_cls"; + public static final String SECTION_CLASS_LAYOUT = "teavm_cll"; public static final String SECTION_METHODS = "teavm_mtd"; public static final String SECTION_LINES = "teavm_line"; public static final String SECTION_VARIABLES = "teavm_var"; diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java index 17cbc5f35..66434724f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugInfoBuilder.java @@ -27,6 +27,7 @@ public class DebugInfoBuilder { private DebugMethodsBuilder methods; private DebugVariablesBuilder variables; private DebugLinesBuilder lines; + private DebugClassLayoutBuilder classLayout; public DebugInfoBuilder() { strings = new DebugStringsBuilder(); @@ -36,6 +37,7 @@ public class DebugInfoBuilder { methods = new DebugMethodsBuilder(classes, strings); variables = new DebugVariablesBuilder(strings); lines = new DebugLinesBuilder(files, methods); + classLayout = new DebugClassLayoutBuilder(classes, strings); } public DebugStrings strings() { @@ -66,6 +68,10 @@ public class DebugInfoBuilder { return lines; } + public DebugClassLayout classLayout() { + return classLayout; + } + public List build() { var result = new ArrayList(); addSection(result, strings); @@ -75,6 +81,7 @@ public class DebugInfoBuilder { addSection(result, methods); addSection(result, variables); addSection(result, lines); + addSection(result, classLayout); return result; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/ArrayLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/ArrayLayout.java new file mode 100644 index 000000000..c2d01fe6d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/ArrayLayout.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public interface ArrayLayout extends TypeLayout { + TypeLayout elementType(); + + @Override + default TypeLayoutKind kind() { + return TypeLayoutKind.ARRAY; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayout.java new file mode 100644 index 000000000..526d0babe --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayout.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +import java.util.Collection; + +public interface ClassLayout extends TypeLayout { + ClassInfo classRef(); + + ClassLayout superclass(); + + Collection instanceFields(); + + Collection staticFields(); + + int size(); + + @Override + default TypeLayoutKind kind() { + return TypeLayoutKind.CLASS; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayoutInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayoutInfo.java new file mode 100644 index 000000000..6038ac91e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassLayoutInfo.java @@ -0,0 +1,105 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +import com.carrotsearch.hppc.IntObjectHashMap; +import com.carrotsearch.hppc.IntObjectMap; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import java.io.PrintStream; +import java.util.Collection; +import java.util.List; + +public abstract class ClassLayoutInfo { + private IntObjectMap layoutByAddress; + + public abstract List types(); + + public TypeLayout find(int address) { + if (layoutByAddress == null) { + layoutByAddress = new IntObjectHashMap<>(); + for (var typeLayout : types()) { + layoutByAddress.put(typeLayout.address(), typeLayout); + } + } + return layoutByAddress.get(address); + } + + public void dump(PrintStream out) { + var indexes = new ObjectIntHashMap(); + for (var i = 0; i < types().size(); ++i) { + indexes.put(types().get(i), i); + } + for (var i = 0; i < types().size(); ++i) { + out.print("#" + i + ": "); + var type = types().get(i); + out.println(type.kind().name().toLowerCase()); + out.println(" address: " + Integer.toHexString(type.address())); + switch (type.kind()) { + case CLASS: + dumpClass(out, indexes, (ClassLayout) type); + break; + case INTERFACE: + dumpInterface(out, (InterfaceLayout) type); + break; + case ARRAY: + dumpArray(out, indexes, (ArrayLayout) type); + break; + case PRIMITIVE: + dumpPrimitive(out, (PrimitiveLayout) type); + break; + default: + break; + } + } + } + + private static void dumpClass(PrintStream out, ObjectIntMap indexes, ClassLayout cls) { + out.println(" name: " + cls.classRef().fullName()); + out.println(" size: " + cls.size()); + if (cls.superclass() != null) { + out.println(" superclass: #" + indexes.get(cls.superclass())); + } + if (!cls.staticFields().isEmpty()) { + out.println(" static fields:"); + dumpFields(out, cls.staticFields()); + } + if (!cls.instanceFields().isEmpty()) { + out.println(" instance fields:"); + dumpFields(out, cls.instanceFields()); + } + } + + private static void dumpFields(PrintStream out, Collection fields) { + for (var field : fields) { + out.println(" " + field.name() + ": "); + out.println(" offset: " + Integer.toHexString(field.address())); + out.println(" type: " + field.type().name().toLowerCase()); + } + } + + private static void dumpInterface(PrintStream out, InterfaceLayout cls) { + out.println(" name: " + cls.classRef().fullName()); + } + + private static void dumpArray(PrintStream out, ObjectIntMap indexes, ArrayLayout array) { + out.println(" element: #" + indexes.get(array.elementType())); + } + + private static void dumpPrimitive(PrintStream out, PrimitiveLayout primitive) { + out.println(" primitive type: " + primitive.primitiveType().name().toLowerCase()); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java index 34a487666..3168b06d7 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/DebugInfo.java @@ -21,12 +21,15 @@ public class DebugInfo { private VariablesInfo variables; private LineInfo lines; private ControlFlowInfo controlFlow; + private ClassLayoutInfo classLayoutInfo; private int offset; - public DebugInfo(VariablesInfo variables, LineInfo lines, ControlFlowInfo controlFlow, int offset) { + public DebugInfo(VariablesInfo variables, LineInfo lines, ControlFlowInfo controlFlow, + ClassLayoutInfo classLayoutInfo, int offset) { this.variables = variables; this.lines = lines; this.controlFlow = controlFlow; + this.classLayoutInfo = classLayoutInfo; this.offset = offset; } @@ -46,6 +49,10 @@ public class DebugInfo { return offset; } + public ClassLayoutInfo classLayoutInfo() { + return classLayoutInfo; + } + public void dump(PrintStream out) { if (offset != 0) { out.println("Code section offset: " + Integer.toHexString(offset)); @@ -62,5 +69,9 @@ public class DebugInfo { out.println("VARIABLES"); variables.dump(out); } + if (classLayoutInfo != null) { + out.println("CLASS LAYOUT:"); + classLayoutInfo.dump(out); + } } } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldInfo.java new file mode 100644 index 000000000..6b05d1450 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldInfo.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public abstract class FieldInfo { + public abstract int address(); + + public abstract String name(); + + public abstract FieldType type(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldType.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldType.java new file mode 100644 index 000000000..6004cd79a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/FieldType.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public enum FieldType { + BOOLEAN, + BYTE, + SHORT, + CHAR, + INT, + LONG, + FLOAT, + DOUBLE, + OBJECT, + ADDRESS, + UNDEFINED +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/InterfaceLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/InterfaceLayout.java new file mode 100644 index 000000000..c7eb999fa --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/InterfaceLayout.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public interface InterfaceLayout extends TypeLayout { + ClassInfo classRef(); + + @Override + default TypeLayoutKind kind() { + return TypeLayoutKind.INTERFACE; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/PrimitiveLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/PrimitiveLayout.java new file mode 100644 index 000000000..eb6a19e8a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/PrimitiveLayout.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +import org.teavm.model.PrimitiveType; + +public interface PrimitiveLayout extends TypeLayout { + PrimitiveType primitiveType(); + + @Override + default TypeLayoutKind kind() { + return TypeLayoutKind.PRIMITIVE; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayout.java new file mode 100644 index 000000000..924961cad --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayout.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public interface TypeLayout { + TypeLayoutKind kind(); + + int address(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayoutKind.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayoutKind.java new file mode 100644 index 000000000..1ef7d4076 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/TypeLayoutKind.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public enum TypeLayoutKind { + CLASS, + INTERFACE, + ARRAY, + PRIMITIVE, + UNKNOWN +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/UnknownLayout.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/UnknownLayout.java new file mode 100644 index 000000000..4a5c534c3 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/UnknownLayout.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 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.wasm.debug.info; + +public interface UnknownLayout extends TypeLayout { + @Override + default TypeLayoutKind kind() { + return TypeLayoutKind.UNKNOWN; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassLayoutParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassLayoutParser.java new file mode 100644 index 000000000..f57b9145c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassLayoutParser.java @@ -0,0 +1,400 @@ +/* + * Copyright 2022 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.wasm.debug.parser; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.ArrayLayout; +import org.teavm.backend.wasm.debug.info.ClassInfo; +import org.teavm.backend.wasm.debug.info.ClassLayout; +import org.teavm.backend.wasm.debug.info.ClassLayoutInfo; +import org.teavm.backend.wasm.debug.info.FieldInfo; +import org.teavm.backend.wasm.debug.info.FieldType; +import org.teavm.backend.wasm.debug.info.InterfaceLayout; +import org.teavm.backend.wasm.debug.info.PrimitiveLayout; +import org.teavm.backend.wasm.debug.info.TypeLayout; +import org.teavm.backend.wasm.debug.info.UnknownLayout; +import org.teavm.model.PrimitiveType; + +public class DebugClassLayoutParser extends DebugSectionParser { + private DebugStringParser strings; + private DebugClassParser classes; + private ArrayList types = new ArrayList<>(); + private List>> forwardReferences = new ArrayList<>(); + private int lastAddress; + private int lastFieldOffset; + private ClassLayoutInfoImpl classLayoutInfo; + + public DebugClassLayoutParser(DebugStringParser strings, DebugClassParser classes) { + super(DebugConstants.SECTION_CLASS_LAYOUT, strings, classes); + this.strings = strings; + this.classes = classes; + } + + @Override + protected void doParse() { + while (ptr < data.length) { + var kind = data[ptr++]; + switch (kind) { + case DebugConstants.CLASS_ROOT: + parseRootClass(); + break; + case DebugConstants.CLASS_CLASS: + parseClass(); + break; + case DebugConstants.CLASS_INTERFACE: + parseInterface(); + break; + case DebugConstants.CLASS_ARRAY: + parseArray(); + break; + case DebugConstants.CLASS_BOOLEAN: + parsePrimitive(PrimitiveType.BOOLEAN); + break; + case DebugConstants.CLASS_BYTE: + parsePrimitive(PrimitiveType.BYTE); + break; + case DebugConstants.CLASS_SHORT: + parsePrimitive(PrimitiveType.SHORT); + break; + case DebugConstants.CLASS_CHAR: + parsePrimitive(PrimitiveType.CHARACTER); + break; + case DebugConstants.CLASS_INT: + parsePrimitive(PrimitiveType.INTEGER); + break; + case DebugConstants.CLASS_LONG: + parsePrimitive(PrimitiveType.LONG); + break; + case DebugConstants.CLASS_FLOAT: + parsePrimitive(PrimitiveType.FLOAT); + break; + case DebugConstants.CLASS_DOUBLE: + parsePrimitive(PrimitiveType.DOUBLE); + break; + case DebugConstants.CLASS_UNKNOWN: + parseUnknown(); + break; + } + } + types.trimToSize(); + classLayoutInfo = new ClassLayoutInfoImpl(Collections.unmodifiableList(types)); + } + + public ClassLayoutInfo getInfo() { + return classLayoutInfo; + } + + private void parseRootClass() { + var classPtr = classes.getClass(readLEB()); + var address = readAddress(); + var size = readLEB(); + var type = new ClassLayoutImpl(address, classPtr, size); + addType(type); + parseClassFields(type); + } + + private void parseClass() { + var classPtr = classes.getClass(readLEB()); + var superclassIndex = types.size() - readSignedLEB(); + var address = readAddress(); + var size = readLEB(); + var type = new ClassLayoutImpl(address, classPtr, size); + addType(type); + ref(superclassIndex, superclass -> type.superclass = (ClassLayoutImpl) superclass); + parseClassFields(type); + } + + private void parseClassFields(ClassLayoutImpl type) { + lastFieldOffset = 0; + var staticFields = new ArrayList(); + while (true) { + var fieldType = data[ptr++]; + if (fieldType == DebugConstants.FIELD_END) { + staticFields.trimToSize(); + type.staticFields = staticFields.isEmpty() ? Collections.emptyList() + : Collections.unmodifiableList(staticFields); + type.instanceFields = Collections.emptyList(); + return; + } + if (fieldType == DebugConstants.FIELD_END_SEQUENCE) { + break; + } + staticFields.add(parseField(fieldType)); + } + + lastFieldOffset = 0; + var instanceFields = new ArrayList(); + while (true) { + var fieldType = data[ptr++]; + if (fieldType == DebugConstants.FIELD_END || fieldType == DebugConstants.FIELD_END_SEQUENCE) { + break; + } + instanceFields.add(parseField(fieldType)); + } + + staticFields.trimToSize(); + instanceFields.trimToSize(); + type.staticFields = staticFields.isEmpty() ? Collections.emptyList() + : Collections.unmodifiableList(staticFields); + type.instanceFields = instanceFields.isEmpty() ? Collections.emptyList() + : Collections.unmodifiableList(instanceFields); + } + + private FieldInfoImpl parseField(byte type) { + FieldType fieldType; + switch (type) { + case DebugConstants.FIELD_BOOLEAN: + fieldType = FieldType.BOOLEAN; + break; + case DebugConstants.FIELD_BYTE: + fieldType = FieldType.BYTE; + break; + case DebugConstants.FIELD_SHORT: + fieldType = FieldType.SHORT; + break; + case DebugConstants.FIELD_CHAR: + fieldType = FieldType.CHAR; + break; + case DebugConstants.FIELD_INT: + fieldType = FieldType.INT; + break; + case DebugConstants.FIELD_LONG: + fieldType = FieldType.LONG; + break; + case DebugConstants.FIELD_FLOAT: + fieldType = FieldType.FLOAT; + break; + case DebugConstants.FIELD_DOUBLE: + fieldType = FieldType.DOUBLE; + break; + case DebugConstants.FIELD_OBJECT: + fieldType = FieldType.OBJECT; + break; + case DebugConstants.FIELD_ADDRESS: + fieldType = FieldType.ADDRESS; + break; + default: + fieldType = FieldType.UNDEFINED; + break; + } + var name = strings.getString(readLEB()); + var offset = lastFieldOffset + readSignedLEB(); + lastFieldOffset = offset; + return new FieldInfoImpl(offset, name, fieldType); + } + + private void parseInterface() { + var classRef = classes.getClass(readLEB()); + var address = readAddress(); + addType(new InterfaceLayoutImpl(address, classRef)); + } + + private void parseArray() { + var elementPtr = types.size() - readSignedLEB(); + var address = readAddress(); + var type = new ArrayLayoutImpl(address); + addType(type); + ref(elementPtr, element -> type.elementType = element); + } + + private void parsePrimitive(PrimitiveType primitiveType) { + var address = readAddress(); + addType(new PrimitiveLayoutImpl(address, primitiveType)); + } + + private void parseUnknown() { + var address = readAddress(); + addType(new UnknownLayoutImpl(address)); + } + + private int readAddress() { + var result = readSignedLEB() + lastAddress; + lastAddress = result; + return result; + } + + private void addType(TypeLayoutImpl type) { + var index = types.size(); + types.add(type); + if (index < forwardReferences.size()) { + var refs = forwardReferences.get(index); + forwardReferences.set(index, null); + for (var ref : refs) { + ref.accept(type); + } + } + } + + private void ref(int index, Consumer handler) { + if (index < types.size()) { + handler.accept(types.get(index)); + } else { + if (index >= forwardReferences.size()) { + forwardReferences.addAll(Collections.nCopies(index + 1 - forwardReferences.size(), null)); + } + var refs = forwardReferences.get(index); + if (refs == null) { + refs = new ArrayList<>(); + forwardReferences.set(index, refs); + } + refs.add(handler); + } + } + + private static abstract class TypeLayoutImpl implements TypeLayout { + private int address; + + private TypeLayoutImpl(int address) { + this.address = address; + } + + @Override + public int address() { + return address; + } + } + + private static class ClassLayoutImpl extends TypeLayoutImpl implements ClassLayout { + private ClassInfo classRef; + private List instanceFields; + private List staticFields; + private ClassLayoutImpl superclass; + private int size; + + ClassLayoutImpl(int address, ClassInfo classRef, int size) { + super(address); + this.classRef = classRef; + this.size = size; + } + + @Override + public ClassInfo classRef() { + return classRef; + } + + @Override + public ClassLayout superclass() { + return superclass; + } + + @Override + public Collection instanceFields() { + return instanceFields; + } + + @Override + public Collection staticFields() { + return staticFields; + } + + @Override + public int size() { + return size; + } + } + + private static class FieldInfoImpl extends FieldInfo { + private int address; + private String name; + private FieldType type; + + FieldInfoImpl(int address, String name, FieldType type) { + this.address = address; + this.name = name; + this.type = type; + } + + @Override + public int address() { + return address; + } + + @Override + public String name() { + return name; + } + + @Override + public FieldType type() { + return type; + } + } + + private static class InterfaceLayoutImpl extends TypeLayoutImpl implements InterfaceLayout { + private ClassInfo classRef; + + InterfaceLayoutImpl(int address, ClassInfo classRef) { + super(address); + this.classRef = classRef; + } + + @Override + public ClassInfo classRef() { + return classRef; + } + } + + private static class ArrayLayoutImpl extends TypeLayoutImpl implements ArrayLayout { + private TypeLayoutImpl elementType; + + ArrayLayoutImpl(int address) { + super(address); + } + + @Override + public TypeLayout elementType() { + return elementType; + } + } + + private static class PrimitiveLayoutImpl extends TypeLayoutImpl implements PrimitiveLayout { + private PrimitiveType primitiveType; + + PrimitiveLayoutImpl(int address, PrimitiveType primitiveType) { + super(address); + this.primitiveType = primitiveType; + } + + @Override + public PrimitiveType primitiveType() { + return primitiveType; + } + } + + private static class UnknownLayoutImpl extends TypeLayoutImpl implements UnknownLayout { + UnknownLayoutImpl(int address) { + super(address); + } + } + + private static class ClassLayoutInfoImpl extends ClassLayoutInfo { + private List types; + + ClassLayoutInfoImpl(List types) { + this.types = types; + } + + @Override + public List types() { + return types; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java index 812d6cce2..22f1e8f01 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java @@ -33,6 +33,7 @@ public class DebugInfoParser extends ModuleParser { private DebugLinesParser lines; private DebugVariablesParser variables; private ControlFlowInfo controlFlow; + private DebugClassLayoutParser classLayoutInfo; private int offset; public DebugInfoParser(AsyncInputStream reader) { @@ -44,6 +45,7 @@ public class DebugInfoParser extends ModuleParser { var methods = addSection(new DebugMethodParser(strings, classes)); variables = addSection(new DebugVariablesParser(strings)); lines = addSection(new DebugLinesParser(files, methods)); + classLayoutInfo = addSection(new DebugClassLayoutParser(strings, classes)); } private T addSection(T section) { @@ -52,7 +54,8 @@ public class DebugInfoParser extends ModuleParser { } public DebugInfo getDebugInfo() { - return new DebugInfo(variables.getVariablesInfo(), lines.getLineInfo(), controlFlow, offset); + return new DebugInfo(variables.getVariablesInfo(), lines.getLineInfo(), controlFlow, + classLayoutInfo.getInfo(), offset); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java index 3944fb30b..5db1527e4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java @@ -31,6 +31,8 @@ import org.teavm.backend.wasm.binary.DataPrimitives; import org.teavm.backend.wasm.binary.DataStructure; import org.teavm.backend.wasm.binary.DataType; import org.teavm.backend.wasm.binary.DataValue; +import org.teavm.backend.wasm.debug.DebugClassLayout; +import org.teavm.backend.wasm.debug.info.FieldType; import org.teavm.common.IntegerArray; import org.teavm.interop.Address; import org.teavm.interop.Function; @@ -415,16 +417,20 @@ public class WasmClassGenerator { if (type instanceof ValueType.Primitive) { return false; } else if (type instanceof ValueType.Object) { - String className = ((ValueType.Object) type).getClassName(); - return !characteristics.isStructure(className) - && !characteristics.isFunction(className) - && !characteristics.isResource(className) - && !className.equals(Address.class.getName()); + var className = ((ValueType.Object) type).getClassName(); + return isManagedClass(className); } else { return true; } } + private boolean isManagedClass(String className) { + return !characteristics.isStructure(className) + && !characteristics.isFunction(className) + && !characteristics.isResource(className) + && !className.equals(Address.class.getName()); + } + private void fillVirtualTable(VirtualTable vtable, DataValue array) { int index = 0; List tables = new ArrayList<>(); @@ -693,6 +699,105 @@ public class WasmClassGenerator { return cls.getMethod(new MethodDescriptor("", ValueType.VOID)) != null; } + public void writeDebug(DebugClassLayout debug) { + var list = new ArrayList<>(binaryDataMap.values()); + var indexes = new ObjectIntHashMap(); + for (var i = 0; i < list.size(); ++i) { + indexes.put(list.get(i).type, i); + } + for (var i = 0; i < list.size(); ++i) { + var data = list.get(i); + if (data.type instanceof ValueType.Primitive) { + debug.writePrimitive(((ValueType.Primitive) data.type).getKind(), data.start); + } else if (data.type instanceof ValueType.Array) { + var itemType = ((ValueType.Array) data.type).getItemType(); + debug.writeArray(indexes.get(itemType), data.start); + } else if (data.type instanceof ValueType.Object) { + var className = ((ValueType.Object) data.type).getClassName(); + if (isManagedClass(className)) { + var parent = data.cls.getParent() != null + ? indexes.get(ValueType.object(data.cls.getParent())) + : -1; + if (data.isInferface) { + debug.writeInterface(className, data.start); + } else { + debug.startClass(className, parent, data.start, data.size); + var fields = getFieldsWithOffset(data); + for (var entry : fields) { + if (entry.field.hasModifier(ElementModifier.STATIC)) { + debug.staticField(entry.field.getName(), entry.offset, + asDebugType(entry.field.getType())); + } + } + for (var entry : fields) { + if (!entry.field.hasModifier(ElementModifier.STATIC)) { + debug.instanceField(entry.field.getName(), entry.offset, + asDebugType(entry.field.getType())); + } + } + debug.endClass(); + } + } else { + debug.writeUnknown(data.start); + } + } else { + debug.writeUnknown(data.start); + } + } + } + + private List getFieldsWithOffset(ClassBinaryData data) { + var result = new ArrayList(); + for (var field : data.fieldLayout) { + var fieldReader = data.cls.getField(field.key); + result.add(new FieldWithOffset(fieldReader, field.value)); + } + return result; + } + + private static class FieldWithOffset { + private FieldReader field; + private int offset; + + FieldWithOffset(FieldReader field, int offset) { + this.field = field; + this.offset = offset; + } + } + + private FieldType asDebugType(ValueType type) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + return FieldType.BOOLEAN; + case BYTE: + return FieldType.BYTE; + case SHORT: + return FieldType.SHORT; + case CHARACTER: + return FieldType.CHAR; + case INTEGER: + return FieldType.INT; + case LONG: + return FieldType.LONG; + case FLOAT: + return FieldType.FLOAT; + case DOUBLE: + return FieldType.DOUBLE; + default: + return FieldType.UNDEFINED; + } + } else if (type instanceof ValueType.Object) { + if (isManagedClass(((ValueType.Object) type).getClassName())) { + return FieldType.OBJECT; + } else { + return FieldType.ADDRESS; + } + } else { + return FieldType.OBJECT; + } + } + static class ClassBinaryData { ValueType type; int size;