Wasm: support class layout in debug information

This commit is contained in:
Alexey Andreev 2022-12-16 17:13:17 +01:00
parent 506a9bd8c5
commit 7b3905246b
19 changed files with 1125 additions and 7 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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<WasmCustomSection> build() {
var result = new ArrayList<WasmCustomSection>();
addSection(result, strings);
@ -75,6 +81,7 @@ public class DebugInfoBuilder {
addSection(result, methods);
addSection(result, variables);
addSection(result, lines);
addSection(result, classLayout);
return result;
}

View File

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

View File

@ -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<? extends FieldInfo> instanceFields();
Collection<? extends FieldInfo> staticFields();
int size();
@Override
default TypeLayoutKind kind() {
return TypeLayoutKind.CLASS;
}
}

View File

@ -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<TypeLayout> layoutByAddress;
public abstract List<? extends TypeLayout> 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<TypeLayout>();
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<TypeLayout> 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<? extends FieldInfo> 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<TypeLayout> 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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TypeLayoutImpl> types = new ArrayList<>();
private List<List<Consumer<TypeLayoutImpl>>> 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<FieldInfoImpl>();
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<FieldInfoImpl>();
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<TypeLayoutImpl> 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<? extends FieldInfoImpl> instanceFields;
private List<? extends FieldInfoImpl> 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<? extends FieldInfo> instanceFields() {
return instanceFields;
}
@Override
public Collection<? extends FieldInfo> 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<TypeLayoutImpl> types;
ClassLayoutInfoImpl(List<TypeLayoutImpl> types) {
this.types = types;
}
@Override
public List<? extends TypeLayout> types() {
return types;
}
}
}

View File

@ -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 extends DebugSectionParser> 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

View File

@ -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<VirtualTable> tables = new ArrayList<>();
@ -693,6 +699,105 @@ public class WasmClassGenerator {
return cls.getMethod(new MethodDescriptor("<clinit>", ValueType.VOID)) != null;
}
public void writeDebug(DebugClassLayout debug) {
var list = new ArrayList<>(binaryDataMap.values());
var indexes = new ObjectIntHashMap<ValueType>();
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<FieldWithOffset> getFieldsWithOffset(ClassBinaryData data) {
var result = new ArrayList<FieldWithOffset>();
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;