diff --git a/.gitignore b/.gitignore index 97291c7..4953aef 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ jasmin.jar *.j TestClass.stups +/DotOut.dot.svg +/DotOut.dot diff --git a/src/main/java/codegen/sourcegraph/SourceBlock.java b/src/main/java/codegen/sourcegraph/SourceBlock.java new file mode 100644 index 0000000..9cfaae3 --- /dev/null +++ b/src/main/java/codegen/sourcegraph/SourceBlock.java @@ -0,0 +1,109 @@ +package codegen.sourcegraph; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class SourceBlock { + + private final String id; + private final String label; + private final List instructions; + private final Set pred; + private final Set succ; + + public SourceBlock(String label) { + this.label = label; + this.id = String.valueOf(System.nanoTime()); + this.instructions = new ArrayList<>(); + this.pred = new HashSet<>(); + this.succ = new HashSet<>(); + } + + public SourceBlock() { + this(""); + } + + public boolean isEmpty() { + return this.label.isBlank() && this.instructions.isEmpty(); + } + + public void addLine(String instruction, String... args) { + this.instructions.add(new SourceInst(instruction, args)); + } + + public List getInstructions() { + return this.instructions; + } + + public String getLabel() { + return this.label; + } + + public void addSuccessor(SourceBlock block) { + this.succ.add(block); + } + + public void addPredecessor(SourceBlock block) { + this.pred.add(block); + } + + public Set getSuccessors() { + return this.succ; + } + + public Set getPredecessors() { + return this.pred; + } + + public String getId() { + return this.id; + } + + public String printInst() { + return this.instructions.stream() + .map(inst -> inst.toString().trim() + "\\n") + .map(inst -> inst.replace("\"", "\\\"")) + .collect(Collectors.joining()); + } + + public void addSuccessors(Set successors) { + this.succ.addAll(successors); + } + + public void addPredecessors(Set predecessors) { + this.pred.addAll(predecessors); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SourceBlock) { + return this.id.equals(((SourceBlock) obj).id); + } + + return false; + } + + @Override + public String toString() { + final String linesString = this.instructions.stream() + .map(SourceInst::toString) + .map(line -> line + "\n") + .collect(Collectors.joining()); + + if (this.label.isBlank()) { + return linesString; + } + + return this.label + ":\n" + + linesString; + } +} diff --git a/src/main/java/codegen/sourcegraph/SourceGraph.java b/src/main/java/codegen/sourcegraph/SourceGraph.java index a9b6dc6..221ce8a 100644 --- a/src/main/java/codegen/sourcegraph/SourceGraph.java +++ b/src/main/java/codegen/sourcegraph/SourceGraph.java @@ -1,47 +1,107 @@ package codegen.sourcegraph; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class SourceGraph { private final SourceGraphHead head; - private final List blocks; + private final List blocks; private final SourceGraphTail tail; + // If a new block has this label, the value in this map is a predecessor + private final Map predecessorMap; + public SourceGraph(String bytecodeVersion, String source, String clazz, int stackSize, int localCount) { this.head = new SourceGraphHead(bytecodeVersion, source, clazz, stackSize, localCount); this.blocks = new ArrayList<>(); this.tail = new SourceGraphTail(); + this.predecessorMap = new HashMap<>(); } + // Label marks beginning of block public void addLabel(String label) { - if (this.blocks.get(this.blocks.size() - 1).isEmpty()) { + final SourceBlock newBlock = new SourceBlock(label); + + + // Resolve missing successors/predecessors from jumps + if (this.predecessorMap.containsKey(label)) { + this.predecessorMap.get(label).addSuccessor(newBlock); + newBlock.addPredecessor(this.predecessorMap.get(label)); + + // this.predecessorMap.remove(label); // Problematic if multiple gotos to same label + } + + if (this.getCurrentBlock().isEmpty()) { // Replace empty blocks, we don't need them - this.blocks.set(this.blocks.size() - 1, new SourceGraphBlock(label)); + if (this.blocks.size() >= 2) { + // This empty blocks successors become the previous blocks successors after replacment + + this.blocks.get(this.blocks.size() - 2).addSuccessors(this.getCurrentBlock().getSuccessors()); + } + + // Previous blocks predecessors are also the new blocks predecessors + newBlock.addPredecessors(this.getCurrentBlock().getPredecessors()); + + this.blocks.set(this.blocks.size() - 1, newBlock); // Replace } else { - this.blocks.add(new SourceGraphBlock(label)); + // Append block if last one isn't empty + + newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block + this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block + + this.blocks.add(newBlock); } } + // Jump means end of block + public void addJump(String jumpInstruction, String label) { + this.addInst(jumpInstruction, label); + + final SourceBlock newBlock = new SourceBlock(); + newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block + + if (!"goto".equals(jumpInstruction)) { + // Goto always jumps + + this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block + } + + // Jumped successor + final SourceBlock labelBlock = this.getBlockByLabel(label); + if (labelBlock != null) { + // Successor exists + + this.getCurrentBlock().addSuccessor(labelBlock); + labelBlock.addPredecessor(this.getCurrentBlock()); + } else { + // Successor doesn't exist, so wait until it does + + this.predecessorMap.put(label, this.getCurrentBlock()); // Current node is predecessor of label-block + } + + this.blocks.add(newBlock); + } + public void addInst(String instruction, String... args) { if (this.blocks.isEmpty()) { - this.blocks.add(new SourceGraphBlock()); + this.blocks.add(new SourceBlock()); // First block doesn't exist } - this.blocks.get(this.blocks.size() - 1).addLine(instruction, args); - } - - public void addJump(String instruction, String label) { - this.addInst(instruction, label); - this.blocks.add(new SourceGraphBlock()); + this.getCurrentBlock().addLine(instruction, args); // Add to last block } public String print() { final String blocksString = this.blocks.stream() - .map(SourceGraphBlock::toString) + .map(SourceBlock::toString) .map(string -> string + "-".repeat(50) + "\n") .collect(Collectors.joining()); @@ -50,14 +110,73 @@ public class SourceGraph { + this.tail; } + public String printToImage() { + final StringBuilder dot = new StringBuilder(); + + dot.append("digraph dfd {\n") + .append("node[shape=Mrecord]\n"); + + for (SourceBlock block : this.blocks) { + dot.append(block.getId()) + .append(" [label=\"{ ") + .append(block.getLabel()) + .append("| ") + .append(block.printInst()) + .append("}\"];\n"); + } + + dot.append("START[label=\"START\"];\n") + .append("END[label=\"END\"];\n"); + + dot.append("START -> ").append(this.blocks.get(0).getId()).append(";\n"); + dot.append(this.getCurrentBlock().getId()).append(" -> END;\n"); + + for (SourceBlock block : this.blocks) { + for (SourceBlock succ : block.getSuccessors()) { + dot.append(block.getId()).append(" -> ").append(succ.getId()).append(";\n"); + } + } + + dot.append("}"); + + final String dotOut = dot.toString(); + + final Path dotFile = Paths.get(System.getProperty("user.dir") + "/DotOut.dot"); + try { + Files.writeString(dotFile, dotOut); + } catch (IOException e) { + e.printStackTrace(); + } + + final ProcessBuilder dotCompile = new ProcessBuilder("dot", "-Tsvg", "-O", "DotOut.dot"); + try { + dotCompile.start(); + } catch (IOException e) { + e.printStackTrace(); + } + + return "Finished."; + } + @Override public String toString() { final String blocksString = this.blocks.stream() - .map(SourceGraphBlock::toString) + .map(SourceBlock::toString) .collect(Collectors.joining()); return this.head + blocksString + this.tail; } + + private SourceBlock getBlockByLabel(String label) { + return this.blocks.stream() + .filter(block -> block.getLabel().equals(label)) + .findFirst() + .orElse(null); + } + + private SourceBlock getCurrentBlock() { + return this.blocks.get(this.blocks.size() - 1); + } } diff --git a/src/main/java/codegen/sourcegraph/SourceGraphBlock.java b/src/main/java/codegen/sourcegraph/SourceGraphBlock.java deleted file mode 100644 index d2788c4..0000000 --- a/src/main/java/codegen/sourcegraph/SourceGraphBlock.java +++ /dev/null @@ -1,44 +0,0 @@ -package codegen.sourcegraph; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class SourceGraphBlock { - - private final String label; - private final List lines; - - public SourceGraphBlock(String label) { - this.label = label; - this.lines = new ArrayList<>(); - } - - public SourceGraphBlock() { - this.label = ""; - this.lines = new ArrayList<>(); - } - - public boolean isEmpty() { - return this.label.isEmpty() && this.lines.isEmpty(); - } - - @Override - public String toString() { - final String linesString = this.lines.stream() - .map(SourceGraphInst::toString) - .map(line -> line + "\n") - .collect(Collectors.joining()); - - if (this.label.isBlank()) { - return linesString; - } - - return this.label + ":\n" - + linesString; - } - - public void addLine(String instruction, String... args) { - this.lines.add(new SourceGraphInst(instruction, args)); - } -} diff --git a/src/main/java/codegen/sourcegraph/SourceGraphGenerator.java b/src/main/java/codegen/sourcegraph/SourceGraphGenerator.java index 8817a34..ec0deca 100644 --- a/src/main/java/codegen/sourcegraph/SourceGraphGenerator.java +++ b/src/main/java/codegen/sourcegraph/SourceGraphGenerator.java @@ -4,6 +4,7 @@ import codegen.CodeGenerationException; import codegen.analysis.StackSizeAnalyzer; import parser.ast.AST; import parser.ast.ASTNode; +import util.Logger; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -101,6 +102,7 @@ public final class SourceGraphGenerator { System.out.println(" - Generating Source Graph..."); this.generateNode(this.tree.getRoot().getChildren().get(3).getChildren().get(11)); + Logger.call(this.graph::printToImage); log("\n\nSourceGraph print:\n" + "-".repeat(100) + "\n" + this.graph.print() + "-".repeat(100)); System.out.println("Graph-generation successful."); diff --git a/src/main/java/codegen/sourcegraph/SourceGraphInst.java b/src/main/java/codegen/sourcegraph/SourceInst.java similarity index 60% rename from src/main/java/codegen/sourcegraph/SourceGraphInst.java rename to src/main/java/codegen/sourcegraph/SourceInst.java index 3d5de63..4c1a22a 100644 --- a/src/main/java/codegen/sourcegraph/SourceGraphInst.java +++ b/src/main/java/codegen/sourcegraph/SourceInst.java @@ -1,11 +1,11 @@ package codegen.sourcegraph; -public class SourceGraphInst { +public class SourceInst { private final String instruction; private final String[] args; - public SourceGraphInst(String instruction, String... args) { + public SourceInst(String instruction, String... args) { this.instruction = instruction; this.args = args; } @@ -16,4 +16,12 @@ public class SourceGraphInst { return "\t\t" + this.instruction + " " + argsString; } + + public String getInstruction() { + return this.instruction; + } + + public String[] getArgs() { + return this.args; + } }