diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassesBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassesBuilder.java index 5060f9893..595a72ed0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassesBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugClassesBuilder.java @@ -24,6 +24,7 @@ public class DebugClassesBuilder extends DebugSectionBuilder implements DebugCla private ObjectIntMap classes = new ObjectIntHashMap<>(); public DebugClassesBuilder(DebugPackages packages, DebugStrings strings) { + super(DebugConstants.SECTION_CLASSES); this.packages = packages; this.strings = strings; } 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 06975610a..c23d42d22 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 @@ -25,4 +25,11 @@ public final class DebugConstants { public static final int LOC_FILE = 3; public static final int LOC_PTR = 4; public static final int LOC_USER = 10; + + 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_METHODS = "teavm_mtd"; + public static final String SECTION_LINES = "teavm_line"; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugFilesBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugFilesBuilder.java index f404dd840..89f475ef5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugFilesBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugFilesBuilder.java @@ -24,6 +24,7 @@ public class DebugFilesBuilder extends DebugSectionBuilder implements DebugFiles private ObjectIntMap fileMap = new ObjectIntHashMap<>(); public DebugFilesBuilder(DebugStrings strings) { + super(DebugConstants.SECTION_FILES); this.strings = strings; } 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 9d96f2601..5057b2a1a 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 @@ -62,19 +62,19 @@ public class DebugInfoBuilder { public List build() { var result = new ArrayList(); - addSection(result, "teavm_str", strings); - addSection(result, "teavm_file", files); - addSection(result, "teavm_pkg", packages); - addSection(result, "teavm_classes", classes); - addSection(result, "teavm_methods", methods); - addSection(result, "teavm_line", lines); + addSection(result, strings); + addSection(result, files); + addSection(result, packages); + addSection(result, classes); + addSection(result, methods); + addSection(result, lines); return result; } - private void addSection(List sections, String name, DebugSectionBuilder builder) { + private void addSection(List sections, DebugSectionBuilder builder) { if (builder.isEmpty()) { return; } - sections.add(new WasmCustomSection(name, builder.build())); + sections.add(new WasmCustomSection(builder.name(), builder.build())); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugLinesBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugLinesBuilder.java index f16995ecc..50dd4f809 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugLinesBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugLinesBuilder.java @@ -30,6 +30,7 @@ public class DebugLinesBuilder extends DebugSectionBuilder implements DebugLines private Deque states = new ArrayDeque<>(); public DebugLinesBuilder(DebugFiles files, DebugMethods methods) { + super(DebugConstants.SECTION_LINES); this.files = files; this.methods = methods; } @@ -45,8 +46,8 @@ public class DebugLinesBuilder extends DebugSectionBuilder implements DebugLines @Override public void location(String file, int line) { if (Objects.equals(file, this.file) && this.ptr != lastWrittenPtr && this.line != line) { - if (this.ptr - lastWrittenPtr < 32 && Math.abs(this.line - line) <= 3) { - blob.writeByte(DebugConstants.LOC_USER + 32 * (this.ptr - lastWrittenPtr) + (line - this.line) + 3); + if (this.ptr - lastWrittenPtr < 32 && Math.abs(line - this.line) <= 3) { + blob.writeByte(DebugConstants.LOC_USER + (this.ptr - lastWrittenPtr) + 32 * (line - this.line + 3)); this.line = line; lastWrittenPtr = ptr; return; diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java index 6b46d2d0a..c1c0b814f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugMethodsBuilder.java @@ -25,6 +25,7 @@ public class DebugMethodsBuilder extends DebugSectionBuilder implements DebugMe private ObjectIntMap methods = new ObjectIntHashMap<>(); public DebugMethodsBuilder(DebugClasses classes, DebugStrings strings) { + super(DebugConstants.SECTION_METHODS); this.classes = classes; this.strings = strings; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugPackagesBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugPackagesBuilder.java index 910b5e99d..ba84b87e6 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugPackagesBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugPackagesBuilder.java @@ -24,6 +24,7 @@ public class DebugPackagesBuilder extends DebugSectionBuilder implements DebugPa private ObjectIntMap packages = new ObjectIntHashMap<>(); public DebugPackagesBuilder(DebugStrings strings) { + super(DebugConstants.SECTION_PACKAGES); this.strings = strings; } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugSectionBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugSectionBuilder.java index 941fa4326..2e8e3b4af 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugSectionBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugSectionBuilder.java @@ -19,12 +19,21 @@ import org.teavm.backend.wasm.blob.BinaryDataConsumer; import org.teavm.backend.wasm.blob.Blob; public class DebugSectionBuilder { + private String name; protected Blob blob = new Blob(); + protected DebugSectionBuilder(String name) { + this.name = name; + } + public void read(BinaryDataConsumer consumer) { blob.newReader(consumer).readRemaining(); } + public String name() { + return name; + } + public byte[] build() { return blob.toArray(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/DebugStringsBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/DebugStringsBuilder.java index e97b18ce6..df927d59e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/debug/DebugStringsBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/debug/DebugStringsBuilder.java @@ -22,6 +22,10 @@ import java.nio.charset.StandardCharsets; public class DebugStringsBuilder extends DebugSectionBuilder implements DebugStrings { private ObjectIntMap strings = new ObjectIntHashMap<>(); + public DebugStringsBuilder() { + super(DebugConstants.SECTION_STRINGS); + } + @Override public int stringPtr(String str) { var result = strings.getOrDefault(str, -1); diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassInfo.java new file mode 100644 index 000000000..b25a9e6e2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/ClassInfo.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; + +public abstract class ClassInfo { + public abstract PackageInfo pkg(); + + public abstract String name(); + + public String fullName() { + var pkg = pkg(); + return pkg != null ? pkg.fullName() + "." + name() : name(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/FileInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/FileInfo.java new file mode 100644 index 000000000..4c6420288 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/FileInfo.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; + +public abstract class FileInfo { + public abstract FileInfo parent(); + + public abstract String name(); + + public String fullName() { + var parent = this.parent(); + return parent != null ? parent.fullName() + "/" + name() : name(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/InliningLocation.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/InliningLocation.java new file mode 100644 index 000000000..197c8756c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/InliningLocation.java @@ -0,0 +1,34 @@ +/* + * 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 class InliningLocation { + private Location location; + private MethodInfo method; + + public InliningLocation(Location location, MethodInfo method) { + this.location = location; + this.method = method; + } + + public Location location() { + return location; + } + + public MethodInfo method() { + return method; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/InstructionLocation.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/InstructionLocation.java new file mode 100644 index 000000000..8eeb445ca --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/InstructionLocation.java @@ -0,0 +1,34 @@ +/* + * 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 class InstructionLocation { + private int address; + private Location location; + + public InstructionLocation(int address, Location location) { + this.address = address; + this.location = location; + } + + public int address() { + return address; + } + + public Location location() { + return location; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfo.java new file mode 100644 index 000000000..3ad0975b6 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfo.java @@ -0,0 +1,60 @@ +/* + * 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.io.PrintStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.teavm.common.CollectionUtil; + +public class LineInfo { + private LineInfoSequence[] sequences; + private List sequenceList; + + public LineInfo(LineInfoSequence[] sequences) { + this.sequences = sequences.clone(); + sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences)); + } + + public List sequences() { + return sequenceList; + } + + public LineInfoSequence find(int address) { + var index = CollectionUtil.binarySearch(sequenceList, address, LineInfoSequence::endAddress); + if (index < 0) { + index = -index - 1; + } + if (index >= sequenceList.size()) { + return null; + } + var sequence = sequenceList.get(index); + return address >= sequence.startAddress() ? sequence : null; + } + + public void dump(PrintStream out) { + for (var i = 0; i < sequences.length; ++i) { + var sequence = sequences[i]; + out.println("Sequence " + i + ": " + sequence.method().fullName() + " [" + sequence.startAddress() + ".." + + sequence.endAddress() + ")"); + for (var location : sequence.unpack().locations()) { + out.println(" at " + location.address() + " " + location.location().file().fullName() + ":" + + location.location().line()); + } + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommand.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommand.java new file mode 100644 index 000000000..655347f13 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommand.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 abstract class LineInfoCommand { + private int address; + + public LineInfoCommand(int address) { + this.address = address; + } + + public int address() { + return address; + } + + public abstract void acceptVisitor(LineInfoCommandVisitor visitor); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandExecutor.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandExecutor.java new file mode 100644 index 000000000..791c370ea --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandExecutor.java @@ -0,0 +1,54 @@ +/* + * 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 class LineInfoCommandExecutor implements LineInfoCommandVisitor { + private FileInfo file; + private int line = 1; + private int address; + private InliningLocation inliningLocation; + + public InstructionLocation createLocation() { + return file != null ? new InstructionLocation(address, new Location(file, line, inliningLocation)) : null; + } + + @Override + public void visit(LineInfoEnterCommand command) { + address = command.address(); + inliningLocation = new InliningLocation(new Location(file, line, inliningLocation), command.method()); + file = null; + line = 1; + } + + @Override + public void visit(LineInfoExitCommand command) { + address = command.address(); + inliningLocation = inliningLocation.location().inlining(); + } + + @Override + public void visit(LineInfoFileCommand command) { + address = command.address(); + file = command.file(); + line = command.line(); + } + + @Override + public void visit(LineInfoLineCommand command) { + address = command.address(); + line = command.line(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandVisitor.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandVisitor.java new file mode 100644 index 000000000..a8406eef1 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoCommandVisitor.java @@ -0,0 +1,26 @@ +/* + * 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 LineInfoCommandVisitor { + void visit(LineInfoEnterCommand command); + + void visit(LineInfoExitCommand command); + + void visit(LineInfoFileCommand command); + + void visit(LineInfoLineCommand command); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoEnterCommand.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoEnterCommand.java new file mode 100644 index 000000000..4d8403372 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoEnterCommand.java @@ -0,0 +1,34 @@ +/* + * 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 class LineInfoEnterCommand extends LineInfoCommand { + private MethodInfo method; + + public LineInfoEnterCommand(int address, MethodInfo method) { + super(address); + this.method = method; + } + + public MethodInfo method() { + return method; + } + + @Override + public void acceptVisitor(LineInfoCommandVisitor visitor) { + visitor.visit(this); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoExitCommand.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoExitCommand.java new file mode 100644 index 000000000..f011a37bd --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoExitCommand.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; + +public class LineInfoExitCommand extends LineInfoCommand { + public LineInfoExitCommand(int address) { + super(address); + } + + @Override + public void acceptVisitor(LineInfoCommandVisitor visitor) { + visitor.visit(this); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoFileCommand.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoFileCommand.java new file mode 100644 index 000000000..8338b1627 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoFileCommand.java @@ -0,0 +1,40 @@ +/* + * 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 class LineInfoFileCommand extends LineInfoCommand { + private FileInfo file; + private int line; + + public LineInfoFileCommand(int address, FileInfo file, int line) { + super(address); + this.file = file; + this.line = line; + } + + public FileInfo file() { + return file; + } + + public int line() { + return line; + } + + @Override + public void acceptVisitor(LineInfoCommandVisitor visitor) { + visitor.visit(this); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoLineCommand.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoLineCommand.java new file mode 100644 index 000000000..85e701704 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoLineCommand.java @@ -0,0 +1,34 @@ +/* + * 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 class LineInfoLineCommand extends LineInfoCommand { + private int line; + + public LineInfoLineCommand(int address, int line) { + super(address); + this.line = line; + } + + public int line() { + return line; + } + + @Override + public void acceptVisitor(LineInfoCommandVisitor visitor) { + visitor.visit(this); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoSequence.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoSequence.java new file mode 100644 index 000000000..889472c48 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoSequence.java @@ -0,0 +1,63 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class LineInfoSequence { + private int startAddress; + private int endAddress; + private MethodInfo method; + private LineInfoCommand[] commands; + private List commandList; + + public LineInfoSequence(int startAddress, int endAddress, MethodInfo method, LineInfoCommand[] commands) { + this.startAddress = startAddress; + this.endAddress = endAddress; + this.method = method; + this.commands = commands.clone(); + commandList = Collections.unmodifiableList(Arrays.asList(this.commands)); + } + + public int startAddress() { + return startAddress; + } + + public int endAddress() { + return endAddress; + } + + public MethodInfo method() { + return method; + } + + public List commands() { + return commandList; + } + + public LineInfoUnpackedSequence unpack() { + var commandExecutor = new LineInfoCommandExecutor(); + var locations = new ArrayList(); + for (var command : commands) { + command.acceptVisitor(commandExecutor); + locations.add(commandExecutor.createLocation()); + } + return new LineInfoUnpackedSequence(startAddress, endAddress, method, locations); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoUnpackedSequence.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoUnpackedSequence.java new file mode 100644 index 000000000..b610af55d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/LineInfoUnpackedSequence.java @@ -0,0 +1,61 @@ +/* + * 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.List; +import org.teavm.common.CollectionUtil; + +public class LineInfoUnpackedSequence { + private int startAddress; + private int endAddress; + private MethodInfo method; + private List locations; + + LineInfoUnpackedSequence(int startAddress, int endAddress, MethodInfo method, + List locations) { + this.startAddress = startAddress; + this.endAddress = endAddress; + this.method = method; + this.locations = locations; + } + + public int startAddress() { + return startAddress; + } + + public int endAddress() { + return endAddress; + } + + public MethodInfo method() { + return method; + } + + public List locations() { + return locations; + } + + public InstructionLocation find(int address) { + if (address < startAddress || address >= endAddress) { + return null; + } + var index = CollectionUtil.binarySearch(locations, address, InstructionLocation::address); + if (index < 0) { + index = -index; + } + return locations.get(Math.min(locations.size() - 1, index)); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/Location.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/Location.java new file mode 100644 index 000000000..a0cb2d008 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/Location.java @@ -0,0 +1,40 @@ +/* + * 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 class Location { + private FileInfo file; + private int line; + private InliningLocation inlining; + + public Location(FileInfo file, int line, InliningLocation inlining) { + this.file = file; + this.line = line; + this.inlining = inlining; + } + + public FileInfo file() { + return file; + } + + public int line() { + return line; + } + + public InliningLocation inlining() { + return inlining; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/MethodInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/MethodInfo.java new file mode 100644 index 000000000..b962092ff --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/MethodInfo.java @@ -0,0 +1,26 @@ +/* + * 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 MethodInfo { + public abstract ClassInfo cls(); + + public abstract String name(); + + public String fullName() { + return cls().fullName() + "." + name(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/info/PackageInfo.java b/core/src/main/java/org/teavm/backend/wasm/debug/info/PackageInfo.java new file mode 100644 index 000000000..a6672d8fe --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/info/PackageInfo.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; + +public abstract class PackageInfo { + public abstract PackageInfo parent(); + + public abstract String name(); + + public final String fullName() { + var parent = this.parent(); + return parent != null ? parent.fullName() + "." + name() : name(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/BlobReader.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/BlobReader.java new file mode 100644 index 000000000..a64ccc30d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/BlobReader.java @@ -0,0 +1,26 @@ +/* + * 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; + +class BlobReader { + private byte[] data; + + public BlobReader(byte[] data) { + this.data = data; + } + + +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassParser.java new file mode 100644 index 000000000..24055c8de --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugClassParser.java @@ -0,0 +1,68 @@ +/* + * 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 org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.ClassInfo; +import org.teavm.backend.wasm.debug.info.PackageInfo; + +public class DebugClassParser extends DebugSectionParser { + private DebugStringParser strings; + private DebugPackageParser packages; + private ClassInfoImpl[] classes; + + public DebugClassParser(DebugStringParser strings, DebugPackageParser packages) { + super(DebugConstants.SECTION_CLASSES, strings, packages); + this.strings = strings; + this.packages = packages; + } + + public ClassInfo getClass(int ptr) { + return classes[ptr]; + } + + @Override + protected void doParse() { + var classes = new ArrayList(); + while (ptr < data.length) { + var pkg = packages.getPackage(readLEB()); + var name = strings.getString(readLEB()); + classes.add(new ClassInfoImpl(pkg, name)); + } + this.classes = classes.toArray(new ClassInfoImpl[0]); + } + + private static class ClassInfoImpl extends ClassInfo { + private PackageInfo pkg; + private String name; + + ClassInfoImpl(PackageInfo pkg, String name) { + this.pkg = pkg; + this.name = name; + } + + @Override + public PackageInfo pkg() { + return pkg; + } + + @Override + public String name() { + return name; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugFileParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugFileParser.java new file mode 100644 index 000000000..952610fc4 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugFileParser.java @@ -0,0 +1,83 @@ +/* + * 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 org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.FileInfo; + +public class DebugFileParser extends DebugSectionParser { + private DebugStringParser strings; + private FileInfoImpl[] files; + + public DebugFileParser(DebugStringParser strings) { + super(DebugConstants.SECTION_FILES, strings); + this.strings = strings; + } + + public FileInfo getFile(int index) { + return index > 0 ? files[index - 1] : null; + } + + @Override + protected void doParse() { + var builders = new ArrayList(); + while (ptr < data.length) { + var parent = readLEB(); + var nameIndex = readLEB(); + var name = (nameIndex & 1) == 0 + ? strings.getString(nameIndex >>> 1) + : strings.getString(nameIndex >>> 1) + "." + strings.getString(readLEB()); + builders.add(new FileBuilder(parent, name)); + } + this.files = new FileInfoImpl[builders.size()]; + for (var i = 0; i < files.length; ++i) { + files[i] = new FileInfoImpl(builders.get(i).name); + } + for (var i = 0; i < files.length; ++i) { + files[i].parent = getFile(builders.get(i).parent); + } + } + + private static class FileInfoImpl extends FileInfo { + private FileInfo parent; + private final String name; + + private FileInfoImpl(String name) { + this.name = name; + } + + @Override + public FileInfo parent() { + return parent; + } + + @Override + public String name() { + return name; + } + } + + private static class FileBuilder { + int parent; + String name; + + FileBuilder(int parent, String name) { + this.parent = parent; + this.name = name; + } + } +} 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 new file mode 100644 index 000000000..7643d773d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoParser.java @@ -0,0 +1,282 @@ +/* + * 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.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.backend.wasm.debug.info.LineInfo; +import org.teavm.common.Promise; + +public class DebugInfoParser { + private static final int WASM_HEADER_SIZE = 8; + private DebugInfoReader reader; + private int pos; + private int posBefore; + private byte[] buffer = new byte[256]; + private int posInBuffer; + private int bufferLimit; + private int currentLEB; + private int currentLEBShift; + private boolean eof; + private int bytesInLEB; + private int sectionCode; + private List chunks = new ArrayList<>(); + private Map sectionParsers = new HashMap<>(); + private DebugLinesParser lines; + + public DebugInfoParser(DebugInfoReader reader) { + this.reader = reader; + + var strings = addSection(new DebugStringParser()); + var files = addSection(new DebugFileParser(strings)); + var packages = addSection(new DebugPackageParser(strings)); + var classes = addSection(new DebugClassParser(strings, packages)); + var methods = addSection(new DebugMethodParser(strings, classes)); + lines = addSection(new DebugLinesParser(files, methods)); + } + + private T addSection(T section) { + sectionParsers.put(section.name(), section); + return section; + } + + public LineInfo getLineInfo() { + return lines.getLineInfo(); + } + + public Promise parse() { + return parseHeader().thenAsync(v -> parseSections()); + } + + private Promise parseHeader() { + return fillAtLeast(16).thenVoid(v -> { + if (remaining() < WASM_HEADER_SIZE) { + error("Invalid WebAssembly header"); + } + if (readInt32() != 0x6d736100) { + error("Invalid WebAssembly magic number"); + } + if (readInt32() != 1) { + error("Unsupported WebAssembly version"); + } + }); + } + + private Promise parseSections() { + return readLEB().thenAsync(n -> { + if (n == null) { + return Promise.VOID; + } + sectionCode = n; + return continueParsingSection().thenAsync(v -> parseSections()); + }); + } + + private Promise continueParsingSection() { + return requireLEB("Unexpected end of file reading section length").thenAsync(sectionSize -> { + if (sectionCode == 0) { + return parseSection(pos + sectionSize); + } else { + return skip(sectionSize, "Error skipping section " + sectionCode + " of size " + sectionSize); + } + }); + } + + private Promise parseSection(int limit) { + return readString("Error reading custom section name").thenAsync(name -> { + var sectionParser = sectionParsers.get(name); + System.out.println("Section '" + name + "': " + (sectionParser != null ? "parsed" : "unknown")); + if (sectionParser != null) { + return readBytes(limit - pos, "Error reading section '" + name + "' content") + .thenVoid(sectionParser::parse); + } else { + return skip(limit - pos, "Error skipping section '" + name + "'"); + } + }); + } + + private Promise readString(String error) { + return readBytes(error).then(b -> new String(b, StandardCharsets.UTF_8)); + } + + private Promise readBytes(String error) { + return requireLEB(error + ": error parsing size") + .thenAsync(size -> readBytes(size, error + ": error reading content")); + } + + private Promise readBytes(int count, String error) { + return readBytesImpl(count, error).then(v -> { + var result = new byte[count]; + var i = 0; + for (var chunk : chunks) { + System.arraycopy(chunk, 0, result, i, chunk.length); + i += chunk.length; + } + chunks.clear(); + return result; + }); + } + + private Promise readBytesImpl(int count, String error) { + posBefore = pos; + var min = Math.min(count, remaining()); + var chunk = new byte[min]; + System.arraycopy(buffer, posInBuffer, chunk, 0, min); + chunks.add(chunk); + pos += min; + posInBuffer += min; + if (count > min) { + if (eof) { + error(error); + } + return fill().thenAsync(x -> readBytesImpl(count - min, error)); + } else { + return Promise.VOID; + } + } + + private Promise requireLEB(String errorMessage) { + return readLEB().then(n -> { + if (n == null) { + error(errorMessage); + } + return n; + }); + } + + private Promise readLEB() { + posBefore = pos; + currentLEB = 0; + bytesInLEB = 0; + currentLEBShift = 0; + return continueLEB(); + } + + private Promise continueLEB() { + while (posInBuffer < bufferLimit) { + var b = buffer[posInBuffer++]; + ++pos; + ++bytesInLEB; + var digit = b & 0x7F; + if (((digit << currentLEBShift) >> currentLEBShift) != digit) { + error("LEB represents too big number"); + } + currentLEB |= digit << currentLEBShift; + if ((b & 0x80) == 0) { + return Promise.of(currentLEB); + } + currentLEBShift += 7; + } + return fill().thenAsync(bytesRead -> { + if (eof) { + if (bytesInLEB > 0) { + error("Unexpected end of file reached reading LEB"); + } + return Promise.of(null); + } else { + return continueLEB(); + } + }); + } + + private int readInt32() { + posBefore = pos; + var result = (buffer[posInBuffer] & 255) + | ((buffer[posInBuffer + 1] & 255) << 8) + | ((buffer[posInBuffer + 2] & 255) << 16) + | ((buffer[posInBuffer + 3] & 255) << 24); + posInBuffer += 4; + pos += 4; + return result; + } + + private int remaining() { + return bufferLimit - posInBuffer; + } + + private Promise skip(int bytes, String error) { + if (bytes <= remaining()) { + posInBuffer += bytes; + pos += bytes; + return Promise.VOID; + } else { + posBefore = pos; + pos += remaining(); + bytes -= remaining(); + posInBuffer = 0; + bufferLimit = 0; + return skipImpl(bytes, error); + } + } + + private Promise skipImpl(int bytes, String error) { + return reader.skip(bytes).thenAsync(bytesSkipped -> { + if (bytesSkipped < 0) { + error(error); + } + pos += bytesSkipped; + return bytes > bytesSkipped ? skipImpl(bytes - bytesSkipped, error) : Promise.VOID; + }); + } + + private Promise fillAtLeast(int least) { + return fill().thenAsync(x -> fillAtLeastImpl(least)); + } + + private Promise fillAtLeastImpl(int least) { + if (eof || least <= remaining()) { + return Promise.VOID; + } + return reader.read(buffer, bufferLimit, Math.min(least, buffer.length - bufferLimit)) + .thenAsync(bytesRead -> { + if (bytesRead < 0) { + eof = true; + } else { + bufferLimit += bytesRead; + } + return fillAtLeastImpl(least); + }); + } + + private Promise fill() { + return readNext(buffer.length - remaining()); + } + + private Promise readNext(int bytes) { + if (eof) { + return Promise.VOID; + } + if (posInBuffer > 0 && posInBuffer < bufferLimit) { + System.arraycopy(buffer, posInBuffer, buffer, 0, bufferLimit - posInBuffer); + } + bufferLimit -= posInBuffer; + posInBuffer = 0; + return reader.read(buffer, bufferLimit, bytes).thenVoid(bytesRead -> { + if (bytesRead < 0) { + eof = true; + } else { + bufferLimit += bytesRead; + } + }); + } + + private void error(String message) { + throw new ParseException(message, posBefore); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoReader.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoReader.java new file mode 100644 index 000000000..cd094aa95 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugInfoReader.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.parser; + +import org.teavm.common.Promise; + +public interface DebugInfoReader { + Promise skip(int amount); + + Promise read(byte[] buffer, int offset, int count); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugLinesParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugLinesParser.java new file mode 100644 index 000000000..6b525364b --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugLinesParser.java @@ -0,0 +1,168 @@ +/* + * 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.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.FileInfo; +import org.teavm.backend.wasm.debug.info.LineInfo; +import org.teavm.backend.wasm.debug.info.LineInfoCommand; +import org.teavm.backend.wasm.debug.info.LineInfoEnterCommand; +import org.teavm.backend.wasm.debug.info.LineInfoExitCommand; +import org.teavm.backend.wasm.debug.info.LineInfoFileCommand; +import org.teavm.backend.wasm.debug.info.LineInfoLineCommand; +import org.teavm.backend.wasm.debug.info.LineInfoSequence; +import org.teavm.backend.wasm.debug.info.MethodInfo; + +public class DebugLinesParser extends DebugSectionParser { + private DebugFileParser files; + private DebugMethodParser methods; + private LineInfo lineInfo; + private List sequences = new ArrayList<>(); + private List commands = new ArrayList<>(); + private Deque stateStack = new ArrayDeque<>(); + private FileInfo file; + private int line = 1; + private int address; + private MethodInfo currentMethod; + private int sequenceStartAddress; + + public DebugLinesParser( + DebugFileParser files, + DebugMethodParser methods + ) { + super(DebugConstants.SECTION_LINES, files, methods); + this.files = files; + this.methods = methods; + } + + public LineInfo getLineInfo() { + return lineInfo; + } + + @Override + protected void doParse() { + while (ptr < data.length) { + var cmd = data[ptr++] & 0xFF; + switch (cmd) { + case DebugConstants.LOC_START: + start(); + break; + case DebugConstants.LOC_END: + end(); + break; + case DebugConstants.LOC_LINE: + moveToLine(); + break; + case DebugConstants.LOC_FILE: + moveToFile(); + break; + case DebugConstants.LOC_PTR: + advanceAddress(); + break; + default: + if (cmd >= DebugConstants.LOC_USER) { + specialCommand(cmd); + } else { + throw new IllegalStateException("Invalid command at " + ptr + ": " + cmd); + } + break; + } + } + lineInfo = new LineInfo(sequences.toArray(new LineInfoSequence[0])); + sequences = null; + commands = null; + stateStack = null; + currentMethod = null; + } + + private void start() { + var method = methods.getMethod(readLEB()); + if (currentMethod == null) { + currentMethod = method; + sequenceStartAddress = address; + } else { + stateStack.push(new State(file, line)); + commands.add(new LineInfoEnterCommand(address, method)); + } + file = null; + line = 1; + } + + private void end() { + if (stateStack.isEmpty()) { + if (currentMethod != null) { + sequences.add(new LineInfoSequence(sequenceStartAddress, address, currentMethod, + commands.toArray(new LineInfoCommand[0]))); + } + commands.clear(); + currentMethod = null; + file = null; + line = 1; + } else { + var state = stateStack.pop(); + file = state.file; + line = state.line; + commands.add(new LineInfoExitCommand(address)); + } + } + + private void moveToLine() { + line = line + readSignedLEB(); + if (currentMethod != null) { + commands.add(new LineInfoLineCommand(address, line)); + } + } + + private void moveToFile() { + file = files.getFile(readLEB()); + line = 1; + if (ptr < data.length && data[ptr] == DebugConstants.LOC_LINE) { + ++ptr; + line = line + readSignedLEB(); + } + if (currentMethod != null) { + commands.add(new LineInfoFileCommand(address, file, line)); + } + } + + private void advanceAddress() { + address += readLEB(); + } + + private void specialCommand(int cmd) { + cmd -= DebugConstants.LOC_USER; + address += cmd % 32; + cmd /= 32; + line += cmd - 3; + if (currentMethod != null) { + commands.add(new LineInfoFileCommand(address, file, line)); + } + } + + private static class State { + FileInfo file; + int line; + + State(FileInfo file, int line) { + this.file = file; + this.line = line; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugMethodParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugMethodParser.java new file mode 100644 index 000000000..f11ce504a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugMethodParser.java @@ -0,0 +1,68 @@ +/* + * 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 org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.ClassInfo; +import org.teavm.backend.wasm.debug.info.MethodInfo; + +public class DebugMethodParser extends DebugSectionParser { + private DebugStringParser strings; + private DebugClassParser classes; + private MethodInfoImpl[] methods; + + public DebugMethodParser(DebugStringParser strings, DebugClassParser classes) { + super(DebugConstants.SECTION_METHODS); + this.strings = strings; + this.classes = classes; + } + + public MethodInfo getMethod(int ptr) { + return methods[ptr]; + } + + @Override + protected void doParse() { + var methods = new ArrayList(); + while (ptr < data.length) { + var cls = classes.getClass(readLEB()); + var name = strings.getString(readLEB()); + methods.add(new MethodInfoImpl(cls, name)); + } + this.methods = methods.toArray(new MethodInfoImpl[0]); + } + + private static class MethodInfoImpl extends MethodInfo { + private ClassInfo cls; + private String name; + + MethodInfoImpl(ClassInfo cls, String name) { + this.cls = cls; + this.name = name; + } + + @Override + public ClassInfo cls() { + return cls; + } + + @Override + public String name() { + return name; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugPackageParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugPackageParser.java new file mode 100644 index 000000000..2616dbdfe --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugPackageParser.java @@ -0,0 +1,78 @@ +/* + * 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 org.teavm.backend.wasm.debug.DebugConstants; +import org.teavm.backend.wasm.debug.info.PackageInfo; + +public class DebugPackageParser extends DebugSectionParser { + private DebugStringParser strings; + private PackageInfoImpl[] packages; + + public DebugPackageParser(DebugStringParser strings) { + super(DebugConstants.SECTION_PACKAGES, strings); + this.strings = strings; + } + + public PackageInfo getPackage(int index) { + return index == 0 ? null : packages[index - 1]; + } + + @Override + protected void doParse() { + var packageBuilders = new ArrayList(); + while (ptr < data.length) { + packageBuilders.add(new PackageBuilder(readLEB(), strings.getString(readLEB()))); + } + packages = new PackageInfoImpl[packageBuilders.size()]; + for (var i = 0; i < packages.length; ++i) { + packages[i] = new PackageInfoImpl(packageBuilders.get(i).name); + } + for (var i = 0; i < packages.length; ++i) { + packages[i].parent = getPackage(packageBuilders.get(i).parent); + } + } + + private static class PackageInfoImpl extends PackageInfo { + private PackageInfo parent; + private String name; + + PackageInfoImpl(String name) { + this.name = name; + } + + @Override + public PackageInfo parent() { + return parent; + } + + @Override + public String name() { + return name; + } + } + + private static class PackageBuilder { + int parent; + String name; + + PackageBuilder(int parent, String name) { + this.parent = parent; + this.name = name; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugSectionParser.java new file mode 100644 index 000000000..1d514487f --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugSectionParser.java @@ -0,0 +1,103 @@ +/* + * 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.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public abstract class DebugSectionParser { + private String name; + private List dependantSections = new ArrayList<>(); + byte[] data; + int ptr; + private boolean ready; + private int unsatisfiedDependencies; + + protected DebugSectionParser(String name, DebugSectionParser... dependencies) { + this.name = name; + for (var dependency : dependencies) { + dependency.dependantSections.add(this); + } + unsatisfiedDependencies = dependencies.length; + } + + public String name() { + return name; + } + + public boolean ready() { + return ready; + } + + public void parse(byte[] data) { + this.data = data; + if (unsatisfiedDependencies == 0) { + parse(); + } + } + + private void parse() { + doParse(); + ready = true; + data = null; + for (var dependant : dependantSections) { + if (--dependant.unsatisfiedDependencies == 0 && dependant.data != null) { + dependant.parse(); + } + } + } + + protected abstract void doParse(); + + protected int readLEB() { + var result = 0; + var shift = 0; + while (true) { + var b = data[ptr++]; + result |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + break; + } + shift += 7; + } + return result; + } + + protected int readSignedLEB() { + var result = 0; + var shift = 0; + while (true) { + var b = data[ptr++]; + result |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + if ((b & 0x40) != 0) { + result |= -1 << (shift + 7); + } + break; + } + shift += 7; + } + return result; + } + + protected String readString() { + var length = readLEB(); + var result = new String(data, ptr, length, StandardCharsets.UTF_8); + ptr += length; + return result; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugStringParser.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugStringParser.java new file mode 100644 index 000000000..e6a023796 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/DebugStringParser.java @@ -0,0 +1,40 @@ +/* + * 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 org.teavm.backend.wasm.debug.DebugConstants; + +public class DebugStringParser extends DebugSectionParser { + private String[] strings; + + public DebugStringParser() { + super(DebugConstants.SECTION_STRINGS); + } + + @Override + protected void doParse() { + var strings = new ArrayList(); + while (ptr < data.length) { + strings.add(readString()); + } + this.strings = strings.toArray(new String[0]); + } + + public String getString(int ref) { + return strings[ref]; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/parser/ParseException.java b/core/src/main/java/org/teavm/backend/wasm/debug/parser/ParseException.java new file mode 100644 index 000000000..4d9554f11 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/parser/ParseException.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.parser; + +class ParseException extends RuntimeException { + final String message; + final int pos; + + ParseException(String message, int pos) { + super("Error parsing at " + pos + ": " + message); + this.message = message; + this.pos = pos; + } +} diff --git a/core/src/main/java/org/teavm/common/CollectionUtil.java b/core/src/main/java/org/teavm/common/CollectionUtil.java new file mode 100644 index 000000000..1c1691f5d --- /dev/null +++ b/core/src/main/java/org/teavm/common/CollectionUtil.java @@ -0,0 +1,54 @@ +/* + * 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.common; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +public final class CollectionUtil { + private CollectionUtil() { + } + + public static > int binarySearch(List list, K key, + Function keyExtractor) { + return binarySearch(list, key, keyExtractor, Comparable::compareTo); + } + + public static int binarySearch(List list, K key, Function keyExtractor, + Comparator comparator) { + var l = 0; + var u = list.size() - 1; + while (true) { + var i = (l + u) / 2; + var t = keyExtractor.apply(list.get(i)); + var cmp = comparator.compare(t, key); + if (cmp == 0) { + return i; + } else if (cmp > 0) { + l = i + i; + if (l > u) { + return -i - 1; + } + } else { + u = i - 1; + if (u < l) { + return -i; + } + } + } + } +} diff --git a/core/src/main/java/org/teavm/common/Promise.java b/core/src/main/java/org/teavm/common/Promise.java index 3900dca13..6eb9191d8 100644 --- a/core/src/main/java/org/teavm/common/Promise.java +++ b/core/src/main/java/org/teavm/common/Promise.java @@ -15,14 +15,17 @@ */ package org.teavm.common; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.function.Consumer; import java.util.function.Function; public class Promise { + private static ThreadLocal> processing = new ThreadLocal<>(); public static final Promise VOID = Promise.of(null); private T value; @@ -187,37 +190,50 @@ public class Promise { } void passValue(Function f, Promise target) { - if (state == State.COMPLETED) { + runAction(() -> { + if (state == State.COMPLETED) { + S next; + try { + next = f.apply(value); + } catch (Throwable e) { + target.completeWithError(e); + return; + } + target.complete(next); + } else { + target.completeWithError(error); + } + }); + } + + void passValueAsync(Function> f, Promise target) { + runAction(() -> { + if (state == State.COMPLETED) { + Promise next; + try { + next = f.apply(value); + } catch (Throwable e) { + target.completeWithError(e); + return; + } + target.completeAsync(next); + } else { + target.completeWithError(error); + } + }); + } + + void passError(Function f, Promise target) { + runAction(() -> { S next; try { - next = f.apply(value); + next = f.apply(error); } catch (Throwable e) { target.completeWithError(e); return; } target.complete(next); - } else { - target.completeWithError(error); - } - } - - void passValueAsync(Function> f, Promise target) { - if (state == State.COMPLETED) { - target.completeAsync(f.apply(value)); - } else { - target.completeWithError(error); - } - } - - void passError(Function f, Promise target) { - S next; - try { - next = f.apply(error); - } catch (Throwable e) { - target.completeWithError(e); - return; - } - target.complete(next); + }); } void complete(T value) { @@ -245,21 +261,23 @@ public class Promise { } private void completeImpl(T value) { - state = State.COMPLETED; - this.value = value; + runAction(() -> { + state = State.COMPLETED; + this.value = value; - if (thenList != null) { - List> list = thenList; - thenList = null; - for (Then then : list) { - if (then.promise) { - passValueAsync((Function>) then.f, (Promise) then.target); - } else { - passValue(then.f, (Promise) then.target); + if (thenList != null) { + List> list = thenList; + thenList = null; + for (Then then : list) { + if (then.promise) { + passValueAsync((Function>) then.f, (Promise) then.target); + } else { + passValue(then.f, (Promise) then.target); + } } } - } - catchList = null; + catchList = null; + }); } void completeWithError(Throwable e) { @@ -270,19 +288,36 @@ public class Promise { } void completeWithErrorImpl(Throwable e) { - state = State.ERRORED; - this.error = e; + runAction(() -> { + state = State.ERRORED; + this.error = e; - if (catchList != null) { - List list = catchList; - thenList = null; - for (Catch c : list) { - passError(c.f, (Promise) c.target); + if (catchList != null) { + List list = catchList; + thenList = null; + for (Catch c : list) { + passError(c.f, (Promise) c.target); + } + } else { + e.printStackTrace(); } + thenList = null; + }); + } + + private void runAction(Runnable action) { + var queue = processing.get(); + if (queue != null) { + queue.add(action); } else { - e.printStackTrace(); + queue = new ArrayDeque<>(); + queue.add(action); + processing.set(queue); + while (!queue.isEmpty()) { + queue.remove().run(); + } + processing.remove(); } - thenList = null; } enum State {