mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-11-21 01:00:54 +08:00
Wasm: working on control flow analyzer for debugger
This commit is contained in:
parent
44204b952d
commit
87d63168d2
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 ControlFlowInfo {
|
||||
private List<? extends FunctionControlFlow> functions;
|
||||
|
||||
public ControlFlowInfo(FunctionControlFlow[] functions) {
|
||||
this.functions = Collections.unmodifiableList(Arrays.asList(functions));
|
||||
}
|
||||
|
||||
public List<? extends FunctionControlFlow> functions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
public FunctionControlFlow find(int address) {
|
||||
var index = CollectionUtil.binarySearch(functions, address, FunctionControlFlow::end);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
}
|
||||
if (index > functions.size()) {
|
||||
return null;
|
||||
}
|
||||
var fn = functions.get(index);
|
||||
if (fn.start() > address) {
|
||||
return fn;
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
for (int i = 0; i < functions.size(); ++i) {
|
||||
var range = functions.get(i);
|
||||
out.println("Range #" + i + ": [" + range.start() + ".." + range.end() + ")");
|
||||
for (var iter = range.iterator(); iter.hasNext(); iter.next()) {
|
||||
out.print(" " + Integer.toHexString(iter.address()));
|
||||
if (iter.isCall()) {
|
||||
out.print(" (call)");
|
||||
}
|
||||
out.print(" -> ");
|
||||
var followers = iter.targets();
|
||||
for (var j = 0; j < followers.length; ++j) {
|
||||
if (j > 0) {
|
||||
out.print(", ");
|
||||
}
|
||||
out.print(Integer.toHexString(followers[j]));
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class DebugInfo {
|
||||
private LineInfo lines;
|
||||
private ControlFlowInfo controlFlow;
|
||||
private int offset;
|
||||
|
||||
public DebugInfo(LineInfo lines, ControlFlowInfo controlFlow, int offset) {
|
||||
this.lines = lines;
|
||||
this.controlFlow = controlFlow;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public LineInfo lines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public ControlFlowInfo controlFlow() {
|
||||
return controlFlow;
|
||||
}
|
||||
|
||||
public int offset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
if (offset != 0) {
|
||||
out.println("Code section offset: " + Integer.toHexString(offset));
|
||||
}
|
||||
if (lines != null) {
|
||||
out.println("LINES");
|
||||
lines.dump(out);
|
||||
}
|
||||
if (controlFlow != null) {
|
||||
out.println("CONTROL FLOW");
|
||||
controlFlow.dump(out);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 FunctionControlFlow {
|
||||
private int start;
|
||||
private int end;
|
||||
int[] offsets;
|
||||
int[] data;
|
||||
|
||||
FunctionControlFlow(int[] offsets, int[] data) {
|
||||
this.offsets = offsets;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int start() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int end() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public FunctionControlFlowIterator iterator() {
|
||||
return new FunctionControlFlowIterator(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.debug.info;
|
||||
|
||||
import com.carrotsearch.hppc.IntArrayList;
|
||||
|
||||
public class FunctionControlFlowBuilder {
|
||||
private IntArrayList offsets = new IntArrayList();
|
||||
private IntArrayList data = new IntArrayList();
|
||||
|
||||
public void addBranch(int position, int[] targets) {
|
||||
offsets.add(data.size() << 1);
|
||||
data.add(position);
|
||||
data.add(targets);
|
||||
}
|
||||
|
||||
public void addCall(int position, int[] targets) {
|
||||
offsets.add((data.size() << 1) | 1);
|
||||
data.add(position);
|
||||
data.add(targets);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return offsets.isEmpty();
|
||||
}
|
||||
|
||||
public FunctionControlFlow build() {
|
||||
return new FunctionControlFlow(offsets.toArray(), data.toArray());
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.Arrays;
|
||||
|
||||
public class FunctionControlFlowIterator {
|
||||
private FunctionControlFlow controlFlow;
|
||||
private int index;
|
||||
private boolean valid;
|
||||
private int offset;
|
||||
private boolean isCall;
|
||||
|
||||
FunctionControlFlowIterator(FunctionControlFlow controlFlow) {
|
||||
this.controlFlow = controlFlow;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return index < controlFlow.offsets.length;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
++index;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
private void fill() {
|
||||
if (!valid) {
|
||||
valid = true;
|
||||
var n = controlFlow.offsets[index];
|
||||
offset = n >>> 1;
|
||||
isCall = (n & 1) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int address() {
|
||||
fill();
|
||||
return controlFlow.data[offset];
|
||||
}
|
||||
|
||||
public int[] targets() {
|
||||
fill();
|
||||
var nextOffset = index < controlFlow.offsets.length - 1
|
||||
? controlFlow.offsets[index + 1] >>> 1
|
||||
: controlFlow.data.length;
|
||||
return Arrays.copyOfRange(controlFlow.data, offset + 1, nextOffset);
|
||||
}
|
||||
|
||||
public boolean isCall() {
|
||||
fill();
|
||||
return isCall;
|
||||
}
|
||||
}
|
@ -22,20 +22,14 @@ import java.util.List;
|
||||
import org.teavm.common.CollectionUtil;
|
||||
|
||||
public class LineInfo {
|
||||
private int offset;
|
||||
private LineInfoSequence[] sequences;
|
||||
private List<? extends LineInfoSequence> sequenceList;
|
||||
|
||||
public LineInfo(int offset, LineInfoSequence[] sequences) {
|
||||
this.offset = offset;
|
||||
public LineInfo(LineInfoSequence[] sequences) {
|
||||
this.sequences = sequences.clone();
|
||||
sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences));
|
||||
}
|
||||
|
||||
public int offset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public List<? extends LineInfoSequence> sequences() {
|
||||
return sequenceList;
|
||||
}
|
||||
|
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 com.carrotsearch.hppc.IntArrayList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.teavm.backend.wasm.debug.info.ControlFlowInfo;
|
||||
import org.teavm.backend.wasm.debug.info.FunctionControlFlow;
|
||||
import org.teavm.backend.wasm.debug.info.FunctionControlFlowBuilder;
|
||||
import org.teavm.backend.wasm.model.WasmType;
|
||||
import org.teavm.backend.wasm.parser.AddressListener;
|
||||
import org.teavm.backend.wasm.parser.BranchOpcode;
|
||||
import org.teavm.backend.wasm.parser.CodeListener;
|
||||
import org.teavm.backend.wasm.parser.CodeSectionListener;
|
||||
import org.teavm.backend.wasm.parser.Opcode;
|
||||
|
||||
public class ControlFlowParser implements CodeSectionListener, CodeListener, AddressListener {
|
||||
private int previousAddress;
|
||||
private int address;
|
||||
private FunctionControlFlowBuilder cfb;
|
||||
private List<Branch> branches = new ArrayList<>();
|
||||
private List<FunctionControlFlow> ranges = new ArrayList<>();
|
||||
private List<Branch> pendingBranches = new ArrayList<>();
|
||||
private List<Block> blocks = new ArrayList<>();
|
||||
|
||||
public ControlFlowInfo build() {
|
||||
return new ControlFlowInfo(ranges.toArray(new FunctionControlFlow[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void address(int address) {
|
||||
previousAddress = this.address;
|
||||
this.address = address;
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean functionStart(int index, int size) {
|
||||
cfb = new FunctionControlFlowBuilder();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeListener code() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startBlock(boolean loop, WasmType type) {
|
||||
return startBlock(loop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startConditionalBlock(WasmType type) {
|
||||
return startBlock(false);
|
||||
}
|
||||
|
||||
private int startBlock(boolean loop) {
|
||||
var token = blocks.size();
|
||||
var branch = !loop ? newBranch(false) : null;
|
||||
var block = new Block(branch, address);
|
||||
blocks.add(block);
|
||||
if (branch != null) {
|
||||
block.pendingBranches.add(branch);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElseSection(int token) {
|
||||
var block = blocks.get(blocks.size() - 1);
|
||||
var lastBranch = branches.get(branches.size() - 1);
|
||||
if (lastBranch.address != previousAddress) {
|
||||
lastBranch = new Branch(previousAddress, false);
|
||||
branches.add(lastBranch);
|
||||
}
|
||||
block.pendingBranches.add(lastBranch);
|
||||
block.branch.targets.add(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endBlock(int token, boolean loop) {
|
||||
var block = blocks.remove(blocks.size() - 1);
|
||||
pendingBranches.addAll(block.pendingBranches);
|
||||
if (loop) {
|
||||
var branch = newBranch(false);
|
||||
branch.targets.add(block.address);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(int functionIndex) {
|
||||
call();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indirectCall(int typeIndex, int tableIndex) {
|
||||
call();
|
||||
}
|
||||
|
||||
private void call() {
|
||||
newPendingBranch(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void opcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case RETURN:
|
||||
case UNREACHABLE: {
|
||||
newBranch(false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void branch(BranchOpcode opcode, int depth, int target) {
|
||||
var branch = newBranch(false);
|
||||
if (opcode == BranchOpcode.BR_IF) {
|
||||
pendingBranches.add(branch);
|
||||
}
|
||||
var block = blocks.get(target);
|
||||
block.pendingBranches.add(branch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableBranch(int[] depths, int[] targets, int defaultDepth, int defaultTarget) {
|
||||
var branch = newPendingBranch(false);
|
||||
for (var target : targets) {
|
||||
blocks.get(target).pendingBranches.add(branch);
|
||||
}
|
||||
blocks.get(defaultTarget).pendingBranches.add(branch);
|
||||
}
|
||||
|
||||
private Branch newPendingBranch(boolean isCall) {
|
||||
var branch = newBranch(isCall);
|
||||
pendingBranches.add(branch);
|
||||
return branch;
|
||||
}
|
||||
|
||||
private Branch newBranch(boolean isCall) {
|
||||
var branch = new Branch(address, isCall);
|
||||
branches.add(branch);
|
||||
return branch;
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
for (var branch : pendingBranches) {
|
||||
branch.targets.add(address);
|
||||
}
|
||||
pendingBranches.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void functionEnd() {
|
||||
for (var branch : branches) {
|
||||
if (branch.isCall) {
|
||||
cfb.addCall(branch.address, branch.targets.toArray());
|
||||
} else {
|
||||
cfb.addBranch(branch.address, branch.targets.toArray());
|
||||
}
|
||||
}
|
||||
ranges.add(cfb.build());
|
||||
branches.clear();
|
||||
pendingBranches.clear();
|
||||
blocks.clear();
|
||||
}
|
||||
|
||||
private static class Block {
|
||||
Branch branch;
|
||||
final int address;
|
||||
List<Branch> pendingBranches = new ArrayList<>();
|
||||
|
||||
Block(Branch branch, int address) {
|
||||
this.branch = branch;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Branch {
|
||||
final int address;
|
||||
final IntArrayList targets = new IntArrayList();
|
||||
final boolean isCall;
|
||||
|
||||
Branch(int address, boolean isCall) {
|
||||
this.address = address;
|
||||
this.isCall = isCall;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@ import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
||||
import org.teavm.backend.wasm.debug.info.ControlFlowInfo;
|
||||
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||
import org.teavm.backend.wasm.parser.CodeSectionParser;
|
||||
import org.teavm.backend.wasm.parser.ModuleParser;
|
||||
import org.teavm.common.AsyncInputStream;
|
||||
import org.teavm.common.ByteArrayAsyncInputStream;
|
||||
@ -29,6 +31,8 @@ import org.teavm.common.ByteArrayAsyncInputStream;
|
||||
public class DebugInfoParser extends ModuleParser {
|
||||
private Map<String, DebugSectionParser> sectionParsers = new HashMap<>();
|
||||
private DebugLinesParser lines;
|
||||
private ControlFlowInfo controlFlow;
|
||||
private int offset;
|
||||
|
||||
public DebugInfoParser(AsyncInputStream reader) {
|
||||
super(reader);
|
||||
@ -45,8 +49,8 @@ public class DebugInfoParser extends ModuleParser {
|
||||
return section;
|
||||
}
|
||||
|
||||
public LineInfo getLineInfo() {
|
||||
return lines.getLineInfo();
|
||||
public DebugInfo getDebugInfo() {
|
||||
return new DebugInfo(lines.getLineInfo(), controlFlow, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -55,11 +59,19 @@ public class DebugInfoParser extends ModuleParser {
|
||||
var parser = sectionParsers.get(name);
|
||||
return parser != null ? parser::parse : null;
|
||||
} else if (code == 10) {
|
||||
lines.setOffset(pos);
|
||||
this.offset = pos;
|
||||
return this::parseCode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseCode(byte[] data) {
|
||||
var builder = new ControlFlowParser();
|
||||
var codeParser = new CodeSectionParser(builder, builder);
|
||||
codeParser.parse(data);
|
||||
controlFlow = builder.build();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length != 1) {
|
||||
System.err.println("Pass single argument - path to wasm file");
|
||||
@ -69,9 +81,9 @@ public class DebugInfoParser extends ModuleParser {
|
||||
var input = new ByteArrayAsyncInputStream(Files.readAllBytes(file.toPath()));
|
||||
var parser = new DebugInfoParser(input);
|
||||
input.readFully(parser::parse);
|
||||
var lineInfo = parser.getLineInfo();
|
||||
if (lineInfo != null) {
|
||||
lineInfo.dump(System.out);
|
||||
var debugInfo = parser.getDebugInfo();
|
||||
if (debugInfo != null) {
|
||||
debugInfo.dump(System.out);
|
||||
} else {
|
||||
System.out.println("No debug information found");
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||
private int address;
|
||||
private MethodInfo currentMethod;
|
||||
private int sequenceStartAddress;
|
||||
private int offset;
|
||||
|
||||
public DebugLinesParser(
|
||||
DebugFileParser files,
|
||||
@ -57,13 +56,8 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||
return lineInfo;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse() {
|
||||
address = offset;
|
||||
while (ptr < data.length) {
|
||||
var cmd = data[ptr++] & 0xFF;
|
||||
switch (cmd) {
|
||||
@ -91,7 +85,7 @@ public class DebugLinesParser extends DebugSectionParser {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lineInfo = new LineInfo(offset, sequences.toArray(new LineInfoSequence[0]));
|
||||
lineInfo = new LineInfo(sequences.toArray(new LineInfoSequence[0]));
|
||||
sequences = null;
|
||||
commands = null;
|
||||
stateStack = null;
|
||||
|
@ -152,7 +152,7 @@ public class DisassemblyCodeSectionListener implements AddressListener, CodeSect
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endBlock(int token) {
|
||||
public void endBlock(int token, boolean loop) {
|
||||
writer.address(address).outdent().write("end (; $label_" + token + " ;)").eol();
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public interface CodeListener {
|
||||
default void startElseSection(int token) {
|
||||
}
|
||||
|
||||
default void endBlock(int token) {
|
||||
default void endBlock(int token, boolean loop) {
|
||||
}
|
||||
|
||||
default void branch(BranchOpcode opcode, int depth, int target) {
|
||||
|
@ -18,17 +18,26 @@ package org.teavm.backend.wasm.parser;
|
||||
import org.teavm.backend.wasm.model.WasmType;
|
||||
|
||||
public interface CodeSectionListener {
|
||||
void sectionStart(int functionCount);
|
||||
default void sectionStart(int functionCount) {
|
||||
}
|
||||
|
||||
boolean functionStart(int index, int size);
|
||||
default boolean functionStart(int index, int size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void localsStart(int count);
|
||||
default void localsStart(int count) {
|
||||
}
|
||||
|
||||
void local(int start, int count, WasmType type);
|
||||
default void local(int start, int count, WasmType type) {
|
||||
}
|
||||
|
||||
CodeListener code();
|
||||
default CodeListener code() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void functionEnd();
|
||||
default void functionEnd() {
|
||||
}
|
||||
|
||||
void sectionEnd();
|
||||
default void sectionEnd() {
|
||||
}
|
||||
}
|
||||
|
@ -68,10 +68,10 @@ public class CodeSectionParser {
|
||||
var end = ptr + functionSize;
|
||||
if (listener.functionStart(index, functionSize)) {
|
||||
parseLocals();
|
||||
}
|
||||
codeListener = listener.code();
|
||||
if (codeListener != null) {
|
||||
parseCode();
|
||||
codeListener = listener.code();
|
||||
if (codeListener != null) {
|
||||
parseCode();
|
||||
}
|
||||
}
|
||||
ptr = end;
|
||||
reportAddress();
|
||||
@ -625,7 +625,7 @@ public class CodeSectionParser {
|
||||
}
|
||||
blockStack.remove(blockStack.size() - 1);
|
||||
reportAddress();
|
||||
codeListener.endBlock(token);
|
||||
codeListener.endBlock(token, isLoop);
|
||||
++ptr;
|
||||
return true;
|
||||
}
|
||||
@ -656,7 +656,7 @@ public class CodeSectionParser {
|
||||
}
|
||||
blockStack.remove(blockStack.size() - 1);
|
||||
reportAddress();
|
||||
codeListener.endBlock(token);
|
||||
codeListener.endBlock(token, false);
|
||||
++ptr;
|
||||
return true;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
||||
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
||||
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
||||
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||
@ -55,9 +55,9 @@ public class Debugger {
|
||||
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
||||
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
||||
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
||||
private Map<JavaScriptScript, LineInfo> wasmLineInfoMap = new HashMap<>();
|
||||
private Map<LineInfo, JavaScriptScript> wasmScriptMap = new HashMap<>();
|
||||
private Map<String, Set<LineInfo>> wasmInfoFileMap = new HashMap<>();
|
||||
private Map<JavaScriptScript, DebugInfo> wasmDebugInfoMap = new HashMap<>();
|
||||
private Map<DebugInfo, JavaScriptScript> wasmScriptMap = new HashMap<>();
|
||||
private Map<String, Set<DebugInfo>> wasmInfoFileMap = new HashMap<>();
|
||||
private Map<DebugInformation, JavaScriptScript> scriptMap = new HashMap<>();
|
||||
private Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
|
||||
private Set<Breakpoint> breakpoints = new LinkedHashSet<>();
|
||||
@ -130,12 +130,14 @@ public class Debugger {
|
||||
}
|
||||
break;
|
||||
case WASM: {
|
||||
var info = wasmLineInfoMap.get(script);
|
||||
var info = wasmDebugInfoMap.get(script);
|
||||
if (info != null) {
|
||||
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UNKNOWN:
|
||||
break;
|
||||
}
|
||||
enterMethod = false;
|
||||
first = false;
|
||||
@ -240,7 +242,7 @@ public class Debugger {
|
||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<LineInfo> wasmLineInfoBySource(String sourceFile) {
|
||||
private List<DebugInfo> wasmLineInfoBySource(String sourceFile) {
|
||||
var list = wasmInfoFileMap.get(sourceFile);
|
||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||
}
|
||||
@ -315,16 +317,19 @@ public class Debugger {
|
||||
}));
|
||||
}
|
||||
}
|
||||
for (var wasmLineInfo : wasmLineInfoBySource(location.getFileName())) {
|
||||
for (var sequence : wasmLineInfo.sequences()) {
|
||||
for (var wasmDebugInfo : wasmLineInfoBySource(location.getFileName())) {
|
||||
if (wasmDebugInfo.lines() == null) {
|
||||
continue;
|
||||
}
|
||||
for (var sequence : wasmDebugInfo.lines().sequences()) {
|
||||
for (var loc : sequence.unpack().locations()) {
|
||||
if (loc.location() == null) {
|
||||
continue;
|
||||
}
|
||||
if (loc.location().line() == location.getLine()
|
||||
&& loc.location().file().fullName().equals(location.getFileName())) {
|
||||
var jsLocation = new JavaScriptLocation(wasmScriptMap.get(wasmLineInfo),
|
||||
0, loc.address());
|
||||
var jsLocation = new JavaScriptLocation(wasmScriptMap.get(wasmDebugInfo),
|
||||
0, loc.address() + wasmDebugInfo.offset());
|
||||
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
||||
jsBreakpoints.add(jsBreakpoint);
|
||||
breakpointMap.put(jsBreakpoint, breakpoint);
|
||||
@ -427,15 +432,20 @@ public class Debugger {
|
||||
}
|
||||
|
||||
private List<SourceLocationWithMethod> mapWasmFrames(JavaScriptCallFrame frame) {
|
||||
var lineInfo = wasmLineInfoMap.get(frame.getLocation().getScript());
|
||||
var debugInfo = wasmDebugInfoMap.get(frame.getLocation().getScript());
|
||||
if (debugInfo == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var lineInfo = debugInfo.lines();
|
||||
if (lineInfo == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var sequence = lineInfo.find(frame.getLocation().getColumn());
|
||||
var address = frame.getLocation().getColumn() - debugInfo.offset();
|
||||
var sequence = lineInfo.find(address);
|
||||
if (sequence == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var instructionLocation = sequence.unpack().find(frame.getLocation().getColumn());
|
||||
var instructionLocation = sequence.unpack().find(address);
|
||||
if (instructionLocation == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@ -525,15 +535,18 @@ public class Debugger {
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (parser.getLineInfo() != null) {
|
||||
wasmLineInfoMap.put(script, parser.getLineInfo());
|
||||
wasmScriptMap.put(parser.getLineInfo(), script);
|
||||
for (var sequence : parser.getLineInfo().sequences()) {
|
||||
for (var command : sequence.commands()) {
|
||||
if (command instanceof LineInfoFileCommand) {
|
||||
var file = ((LineInfoFileCommand) command).file();
|
||||
if (file != null) {
|
||||
addWasmInfoFile(file.fullName(), parser.getLineInfo());
|
||||
var debugInfo = parser.getDebugInfo();
|
||||
if (debugInfo != null) {
|
||||
wasmDebugInfoMap.put(script, debugInfo);
|
||||
wasmScriptMap.put(debugInfo, script);
|
||||
if (debugInfo.lines() != null) {
|
||||
for (var sequence : debugInfo.lines().sequences()) {
|
||||
for (var command : sequence.commands()) {
|
||||
if (command instanceof LineInfoFileCommand) {
|
||||
var file = ((LineInfoFileCommand) command).file();
|
||||
if (file != null) {
|
||||
addWasmInfoFile(file.fullName(), debugInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -542,13 +555,13 @@ public class Debugger {
|
||||
});
|
||||
}
|
||||
|
||||
private void addWasmInfoFile(String sourceFile, LineInfo wasmLineInfo) {
|
||||
private void addWasmInfoFile(String sourceFile, DebugInfo debugInfo) {
|
||||
var list = wasmInfoFileMap.get(sourceFile);
|
||||
if (list == null) {
|
||||
list = new HashSet<>();
|
||||
wasmInfoFileMap.put(sourceFile, list);
|
||||
}
|
||||
list.add(wasmLineInfo);
|
||||
list.add(debugInfo);
|
||||
allSourceFiles.add(sourceFile);
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,11 @@
|
||||
<groupId>javax.websocket</groupId>
|
||||
<artifactId>javax.websocket-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.carrotsearch</groupId>
|
||||
<artifactId>hppc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
Loading…
Reference in New Issue
Block a user