Wasm: add parser for own debug information

This commit is contained in:
Alexey Andreev 2022-11-25 21:29:46 +01:00
parent 56929b2085
commit d1feec7ae6
39 changed files with 1780 additions and 55 deletions

View File

@ -24,6 +24,7 @@ public class DebugClassesBuilder extends DebugSectionBuilder implements DebugCla
private ObjectIntMap<String> classes = new ObjectIntHashMap<>();
public DebugClassesBuilder(DebugPackages packages, DebugStrings strings) {
super(DebugConstants.SECTION_CLASSES);
this.packages = packages;
this.strings = strings;
}

View File

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

View File

@ -24,6 +24,7 @@ public class DebugFilesBuilder extends DebugSectionBuilder implements DebugFiles
private ObjectIntMap<FileData> fileMap = new ObjectIntHashMap<>();
public DebugFilesBuilder(DebugStrings strings) {
super(DebugConstants.SECTION_FILES);
this.strings = strings;
}

View File

@ -62,19 +62,19 @@ public class DebugInfoBuilder {
public List<WasmCustomSection> build() {
var result = new ArrayList<WasmCustomSection>();
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<WasmCustomSection> sections, String name, DebugSectionBuilder builder) {
private void addSection(List<WasmCustomSection> sections, DebugSectionBuilder builder) {
if (builder.isEmpty()) {
return;
}
sections.add(new WasmCustomSection(name, builder.build()));
sections.add(new WasmCustomSection(builder.name(), builder.build()));
}
}

View File

@ -30,6 +30,7 @@ public class DebugLinesBuilder extends DebugSectionBuilder implements DebugLines
private Deque<State> 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;

View File

@ -25,6 +25,7 @@ public class DebugMethodsBuilder extends DebugSectionBuilder implements DebugMe
private ObjectIntMap<MethodReference> methods = new ObjectIntHashMap<>();
public DebugMethodsBuilder(DebugClasses classes, DebugStrings strings) {
super(DebugConstants.SECTION_METHODS);
this.classes = classes;
this.strings = strings;
}

View File

@ -24,6 +24,7 @@ public class DebugPackagesBuilder extends DebugSectionBuilder implements DebugPa
private ObjectIntMap<PackageInfo> packages = new ObjectIntHashMap<>();
public DebugPackagesBuilder(DebugStrings strings) {
super(DebugConstants.SECTION_PACKAGES);
this.strings = strings;
}

View File

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

View File

@ -22,6 +22,10 @@ import java.nio.charset.StandardCharsets;
public class DebugStringsBuilder extends DebugSectionBuilder implements DebugStrings {
private ObjectIntMap<String> strings = new ObjectIntHashMap<>();
public DebugStringsBuilder() {
super(DebugConstants.SECTION_STRINGS);
}
@Override
public int stringPtr(String str) {
var result = strings.getOrDefault(str, -1);

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

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

View File

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

View File

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

View File

@ -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<? extends LineInfoSequence> sequenceList;
public LineInfo(LineInfoSequence[] sequences) {
this.sequences = sequences.clone();
sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences));
}
public List<? extends LineInfoSequence> 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());
}
}
}
}

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 abstract class LineInfoCommand {
private int address;
public LineInfoCommand(int address) {
this.address = address;
}
public int address() {
return address;
}
public abstract void acceptVisitor(LineInfoCommandVisitor visitor);
}

View File

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

View File

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

View File

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

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;
public class LineInfoExitCommand extends LineInfoCommand {
public LineInfoExitCommand(int address) {
super(address);
}
@Override
public void acceptVisitor(LineInfoCommandVisitor visitor) {
visitor.visit(this);
}
}

View File

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

View File

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

View File

@ -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<LineInfoCommand> 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<? extends LineInfoCommand> commands() {
return commandList;
}
public LineInfoUnpackedSequence unpack() {
var commandExecutor = new LineInfoCommandExecutor();
var locations = new ArrayList<InstructionLocation>();
for (var command : commands) {
command.acceptVisitor(commandExecutor);
locations.add(commandExecutor.createLocation());
}
return new LineInfoUnpackedSequence(startAddress, endAddress, method, locations);
}
}

View File

@ -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<? extends InstructionLocation> locations;
LineInfoUnpackedSequence(int startAddress, int endAddress, MethodInfo method,
List<? extends InstructionLocation> 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<? extends InstructionLocation> 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));
}
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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<byte[]> chunks = new ArrayList<>();
private Map<String, DebugSectionParser> 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 extends DebugSectionParser> T addSection(T section) {
sectionParsers.put(section.name(), section);
return section;
}
public LineInfo getLineInfo() {
return lines.getLineInfo();
}
public Promise<Void> parse() {
return parseHeader().thenAsync(v -> parseSections());
}
private Promise<Void> 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<Void> parseSections() {
return readLEB().thenAsync(n -> {
if (n == null) {
return Promise.VOID;
}
sectionCode = n;
return continueParsingSection().thenAsync(v -> parseSections());
});
}
private Promise<Void> 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<Void> 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<String> readString(String error) {
return readBytes(error).then(b -> new String(b, StandardCharsets.UTF_8));
}
private Promise<byte[]> readBytes(String error) {
return requireLEB(error + ": error parsing size")
.thenAsync(size -> readBytes(size, error + ": error reading content"));
}
private Promise<byte[]> 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<Void> 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<Integer> requireLEB(String errorMessage) {
return readLEB().then(n -> {
if (n == null) {
error(errorMessage);
}
return n;
});
}
private Promise<Integer> readLEB() {
posBefore = pos;
currentLEB = 0;
bytesInLEB = 0;
currentLEBShift = 0;
return continueLEB();
}
private Promise<Integer> 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<Void> 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<Void> 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<Void> fillAtLeast(int least) {
return fill().thenAsync(x -> fillAtLeastImpl(least));
}
private Promise<Void> 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<Void> fill() {
return readNext(buffer.length - remaining());
}
private Promise<Void> 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);
}
}

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.parser;
import org.teavm.common.Promise;
public interface DebugInfoReader {
Promise<Integer> skip(int amount);
Promise<Integer> read(byte[] buffer, int offset, int count);
}

View File

@ -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<LineInfoSequence> sequences = new ArrayList<>();
private List<LineInfoCommand> commands = new ArrayList<>();
private Deque<State> 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;
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<String>();
while (ptr < data.length) {
strings.add(readString());
}
this.strings = strings.toArray(new String[0]);
}
public String getString(int ref) {
return strings[ref];
}
}

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

View File

@ -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 <T, K extends Comparable<K>> int binarySearch(List<? extends T> list, K key,
Function<T, K> keyExtractor) {
return binarySearch(list, key, keyExtractor, Comparable::compareTo);
}
public static <T, K> int binarySearch(List<? extends T> list, K key, Function<T, K> keyExtractor,
Comparator<K> 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;
}
}
}
}
}

View File

@ -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<T> {
private static ThreadLocal<Queue<Runnable>> processing = new ThreadLocal<>();
public static final Promise<Void> VOID = Promise.of(null);
private T value;
@ -187,6 +190,7 @@ public class Promise<T> {
}
<S> void passValue(Function<? super T, S> f, Promise<? super S> target) {
runAction(() -> {
if (state == State.COMPLETED) {
S next;
try {
@ -199,17 +203,28 @@ public class Promise<T> {
} else {
target.completeWithError(error);
}
});
}
<S> void passValueAsync(Function<T, Promise<S>> f, Promise<S> target) {
runAction(() -> {
if (state == State.COMPLETED) {
target.completeAsync(f.apply(value));
Promise<S> next;
try {
next = f.apply(value);
} catch (Throwable e) {
target.completeWithError(e);
return;
}
target.completeAsync(next);
} else {
target.completeWithError(error);
}
});
}
<S> void passError(Function<Throwable, S> f, Promise<? super S> target) {
runAction(() -> {
S next;
try {
next = f.apply(error);
@ -218,6 +233,7 @@ public class Promise<T> {
return;
}
target.complete(next);
});
}
void complete(T value) {
@ -245,6 +261,7 @@ public class Promise<T> {
}
private void completeImpl(T value) {
runAction(() -> {
state = State.COMPLETED;
this.value = value;
@ -260,6 +277,7 @@ public class Promise<T> {
}
}
catchList = null;
});
}
void completeWithError(Throwable e) {
@ -270,6 +288,7 @@ public class Promise<T> {
}
void completeWithErrorImpl(Throwable e) {
runAction(() -> {
state = State.ERRORED;
this.error = e;
@ -283,6 +302,22 @@ public class Promise<T> {
e.printStackTrace();
}
thenList = null;
});
}
private void runAction(Runnable action) {
var queue = processing.get();
if (queue != null) {
queue.add(action);
} else {
queue = new ArrayDeque<>();
queue.add(action);
processing.set(queue);
while (!queue.isEmpty()) {
queue.remove().run();
}
processing.remove();
}
}
enum State {