diff --git a/src/main/java/codegen/flowgraph/FlowBasicBlock.java b/src/main/java/codegen/flowgraph/FlowBasicBlock.java index afe73cf..441d58b 100644 --- a/src/main/java/codegen/flowgraph/FlowBasicBlock.java +++ b/src/main/java/codegen/flowgraph/FlowBasicBlock.java @@ -1,21 +1,36 @@ package codegen.flowgraph; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; public class FlowBasicBlock implements Iterable { + // Graph structure information private final String id; - private final String label; - private final List instructions; private final Set predecessors; private final Set successors; + /** + * Das Label ist das Jump-Label, über welches ein Block angesprungen werden kann. + */ + private final String label; + + /** + * Alle Instructions, welche zu einem Block gehören. + * Diese werden immer sequentiell ohne Verzweigungen ausgeführt. + */ + private final List instructions; + + /** + * Wird intern benutzt um die ID für eine hinzugefügte {@link FlowInstruction} zu ermitteln. + */ private int instNr; public FlowBasicBlock(String label) { @@ -38,6 +53,8 @@ public class FlowBasicBlock implements Iterable { return this.instructions.isEmpty() && this.label.isBlank(); } + // Geteter, Setter + public String getId() { return this.id; } @@ -48,84 +65,105 @@ public class FlowBasicBlock implements Iterable { public void addInstruction(String instruction, String... args) { this.instNr++; - this.instructions.add(new FlowInstruction(String.valueOf(Long.parseLong(this.id) + this.instNr), this.id, + this.instructions.add(new FlowInstruction(String.valueOf(Long.parseLong(this.id) + this.instNr), instruction, args)); } - public void addSuccessor(FlowBasicBlock block) { - this.successors.add(block); + public Set getBlockSuccessorSet() { + return Collections.unmodifiableSet(this.successors); } - public void addPredecessor(FlowBasicBlock block) { - this.predecessors.add(block); - } - - public Set getSuccessorSet() { - return this.successors; - } - - public Set getPredecessors(FlowInstruction inst) { + /** + * Ermittelt alle Instructions, welche auf eine {@link FlowInstruction} folgen können. + * Befindet sich die Instruction am Ende des Blockes, werden Instructions aus Successor-Blöcken gesucht. + */ + public Set getInstructionSuccessorSet(FlowInstruction inst) { final int index = this.instructions.indexOf(inst); if (index == -1) { - return null; - } - - if (index > 0 && index <= this.instructions.size() - 1) { - // Instruction is in the middle or end - - return Set.of(this.instructions.get(index - 1)); - } - - // Instruction is at the beginning - return this.predecessors.stream() - .map(FlowBasicBlock::getLastInst) - .filter(Objects::nonNull) - .collect(Collectors.toUnmodifiableSet()); - } - - public Set getSuccessors(FlowInstruction inst) { - final int index = this.instructions.indexOf(inst); - - if (index == -1) { - return null; + return Collections.emptySet(); } if (index >= 0 && index < this.instructions.size() - 1) { - // Instruction is in the beginning or middle + // Instruction is in the beginning or in the middle return Set.of(this.instructions.get(index + 1)); } // Instruction is at the end return this.successors.stream() - .map(FlowBasicBlock::getFirstInst) - .filter(Objects::nonNull) + .map(FlowBasicBlock::getFirstInstruction) + .filter(Optional::isPresent) + .map(Optional::get) .collect(Collectors.toUnmodifiableSet()); } - public Set getPredecessorSet() { - return this.predecessors; + public boolean addSuccessorBlock(FlowBasicBlock successor) { + return this.successors.add(successor); } - public FlowInstruction getFirstInst() { - if (!this.instructions.isEmpty()) { - return this.instructions.get(0); - } - - return null; + public boolean removeSuccessorBlock(FlowBasicBlock successor) { + return this.successors.remove(successor); } - public FlowInstruction getLastInst() { - if (!this.instructions.isEmpty()) { - return this.instructions.get(this.instructions.size() - 1); + public Set getBlockPredecessorSet() { + return Collections.unmodifiableSet(this.predecessors); + } + + /** + * Ermittelt alle Instructions, welche Predecessor einer {@link FlowInstruction} sein können. + * Befindet sich die Instruction am Anfang des Blockes, werden Instructions aus Predecessor-Blöcken gesucht. + */ + public Set getInstructionPredecessorSet(FlowInstruction inst) { + final int index = this.instructions.indexOf(inst); + + if (index == -1) { + return Collections.emptySet(); } - return null; + if (index > 0 && index <= this.instructions.size() - 1) { + // Instruction is in the middle or at the end + + return Set.of(this.instructions.get(index - 1)); + } + + // Instruction is at the beginning + return this.predecessors.stream() + .map(FlowBasicBlock::getLastInstruction) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toUnmodifiableSet()); + } + + public boolean addPredecessorBlock(FlowBasicBlock predecessor) { + return this.predecessors.add(predecessor); + } + + public boolean removePredecessorBlock(FlowBasicBlock predecessor) { + return this.predecessors.remove(predecessor); + } + + public Optional getFirstInstruction() { + if (this.instructions.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(this.instructions.get(0)); + } + + public Optional getLastInstruction() { + if (this.instructions.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(this.instructions.get(this.instructions.size() - 1)); } // Printing + /** + * Diese Methode ist für das Printen mit Graphviz, {@link #toString()} für den Rest. + */ public String printInst() { return this.instructions.stream() .map(inst -> inst.toString().trim() + "\\n") @@ -139,16 +177,19 @@ public class FlowBasicBlock implements Iterable { @Override public int hashCode() { - return Objects.hash(this.id); + return Objects.hash(this.id, this.label); } @Override - public boolean equals(Object obj) { - if (obj instanceof FlowBasicBlock) { - return this.id.equals(((FlowBasicBlock) obj).id); + public boolean equals(Object o) { + if (this == o) { + return true; } - - return false; + if (o == null || this.getClass() != o.getClass()) { + return false; + } + final FlowBasicBlock that = (FlowBasicBlock) o; + return this.id.equals(that.id) && this.label.equals(that.label); } @Override diff --git a/src/main/java/codegen/flowgraph/FlowGraph.java b/src/main/java/codegen/flowgraph/FlowGraph.java index 28120f3..e8a4b72 100644 --- a/src/main/java/codegen/flowgraph/FlowGraph.java +++ b/src/main/java/codegen/flowgraph/FlowGraph.java @@ -1,53 +1,79 @@ package codegen.flowgraph; +import parser.ast.AST; +import util.GraphvizCaller; import util.Logger; -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.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Optional; import java.util.stream.Collectors; +/** + * Die Graph-Repräsentation des Programm, erzeugt aus einem {@link AST}. + * Der Grundbaustein ist {@link FlowBasicBlock}, diese enthalten wiederum {@link FlowInstruction}. + */ public class FlowGraph implements Iterable { - private final FlowGraphHead head; private final List basicBlocks; - private final FlowGraphTail tail; - // If a new block has this label, the value in this map is a predecessor + // Only for Export to Jasmin-Assembler + private final FlowGraphHead exportHead; + private final FlowGraphTail exportTail; + + /** + * Wenn ein neuer Block ein Label bekommt, welches in der Predecessor-Map vorhanden ist, + * dann ist der hier gespeicherte Block ein Predecessor des neuen Blockes. + *

+ * Einträge werden hier hinzugefügt, wenn ein Jump nach vorne passiert. + * In diesem Fall ist der Jump-Successor noch nicht im Graph präsent. + */ private final Map predecessorMap; public FlowGraph(String bytecodeVersion, String source, String clazz, int stackSize, int localCount) { - this.head = new FlowGraphHead(bytecodeVersion, source, clazz, stackSize, localCount); + this.exportHead = new FlowGraphHead(bytecodeVersion, source, clazz, stackSize, localCount); this.basicBlocks = new ArrayList<>(); - this.tail = new FlowGraphTail(); + this.exportTail = new FlowGraphTail(); this.predecessorMap = new HashMap<>(); } - // Label marks beginning of block + /** + * Ein Label markiert den Beginn eines neuen Blockes. + * Es werden Predecessor/Successor-Verbindungen zum letzten Block + * und zu Blöcken aus der {@link #predecessorMap} hergestellt. + */ public void addLabel(String label) { final FlowBasicBlock newBlock = new FlowBasicBlock(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.get(label).addSuccessorBlock(newBlock); + newBlock.addPredecessorBlock(this.predecessorMap.get(label)); } - newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block - this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block + final Optional currentBlock = this.getCurrentBlock(); + if (currentBlock.isPresent()) { + newBlock.addPredecessorBlock(currentBlock.get()); // Obvious predecessor of new block + currentBlock.get().addSuccessorBlock(newBlock); // Obvious successor of current block + } this.basicBlocks.add(newBlock); } - // Jump means end of block + /** + * Ein Jump markiert das Ende eines Blockes. + * Es werden Predecessor/Successor-Verbindungen zum letzten Block hergestellt + * und wenn nötig Einträge in der {@link #predecessorMap} angelegt. + *

+ * Da GoTo immer springt, wird diese Sprunganweisung gesondert betrachtet. + * + * @param jumpInstruction Der verwendete Sprungbefehl. + */ public void addJump(String jumpInstruction, String label) { this.addInstruction(jumpInstruction, label); @@ -56,21 +82,30 @@ public class FlowGraph implements Iterable { if (!"goto".equals(jumpInstruction)) { // Goto always jumps, so we don't have a direct relation in order of the code - newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block - this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block + final Optional currentBlock = this.getCurrentBlock(); + if (currentBlock.isPresent()) { + + newBlock.addPredecessorBlock(currentBlock.get()); // Obvious predecessor of new block + currentBlock.get().addSuccessorBlock(newBlock); // Obvious successor of current block + } } // Jumped successor - final FlowBasicBlock labelBlock = this.getBlockByLabel(label); - if (labelBlock != null) { + final Optional labelBlock = this.getBlockByLabel(label); + final Optional currentBlock = this.getCurrentBlock(); + + if (labelBlock.isPresent()) { // Successor exists - this.getCurrentBlock().addSuccessor(labelBlock); - labelBlock.addPredecessor(this.getCurrentBlock()); + if (currentBlock.isPresent()) { + currentBlock.get().addSuccessorBlock(labelBlock.get()); + labelBlock.get().addPredecessorBlock(currentBlock.get()); + } } else { // Successor doesn't exist, so wait until it does - this.predecessorMap.put(label, this.getCurrentBlock()); // Current node is predecessor of label-block + // Current node is predecessor of label-block + currentBlock.ifPresent(flowBasicBlock -> this.predecessorMap.put(label, flowBasicBlock)); } this.basicBlocks.add(newBlock); @@ -81,16 +116,20 @@ public class FlowGraph implements Iterable { this.basicBlocks.add(new FlowBasicBlock("START")); // First block doesn't exist } - this.getCurrentBlock().addInstruction(instruction, args); // Add to last block + final Optional currentBlock = this.getCurrentBlock(); + + // Add to last block + currentBlock.ifPresent(flowBasicBlock -> flowBasicBlock.addInstruction(instruction, args)); } /** * Entfernt leere Blöcke. + * Ein Block ist "leer", wenn er kein Label und keine Instructions hat. */ public void purgeEmptyBlocks() { Logger.log("\nPurging empty blocks: "); - final Set toRemove = new HashSet<>(); + final Collection toRemove = new HashSet<>(); // Collect removable blocks for (FlowBasicBlock block : this.basicBlocks) { @@ -103,52 +142,53 @@ public class FlowGraph implements Iterable { // Remove blocks + reroute predecessors/successors for (FlowBasicBlock block : toRemove) { - for (FlowBasicBlock pred : block.getPredecessorSet()) { + // Reroute + for (FlowBasicBlock predecessor : block.getBlockPredecessorSet()) { + for (FlowBasicBlock successor : block.getBlockSuccessorSet()) { - for (FlowBasicBlock succ : block.getSuccessorSet()) { - - Logger.log("Rerouting Block " + this.basicBlocks.indexOf(pred) + " to Block " + this.basicBlocks.indexOf(succ)); - pred.addSuccessor(succ); - succ.addPredecessor(pred); + Logger.log("Rerouting Block " + this.basicBlocks.indexOf(predecessor) + + " to Block " + this.basicBlocks.indexOf(successor)); + predecessor.addSuccessorBlock(successor); + successor.addPredecessorBlock(predecessor); } - - pred.getSuccessorSet().remove(block); } - for (FlowBasicBlock succ : block.getSuccessorSet()) { - succ.getPredecessorSet().remove(block); + // Remove references + for (FlowBasicBlock predecessor : block.getBlockPredecessorSet()) { + predecessor.removeSuccessorBlock(block); + } + + for (FlowBasicBlock successor : block.getBlockSuccessorSet()) { + successor.removePredecessorBlock(block); } } this.basicBlocks.removeAll(toRemove); } - private FlowBasicBlock getBlockByLabel(String label) { + private Optional getBlockByLabel(String label) { return this.basicBlocks.stream() .filter(block -> block.getLabel().equals(label)) - .findFirst() - .orElse(null); + .findFirst(); } - private FlowBasicBlock getCurrentBlock() { - return this.basicBlocks.get(this.basicBlocks.size() - 1); + /** + * Der aktuelle Block ist immer der letzte Block. + */ + private Optional getCurrentBlock() { + if (this.basicBlocks.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(this.basicBlocks.get(this.basicBlocks.size() - 1)); } // Printing - public String print() { - final String blocksString = this.basicBlocks.stream() - .map(FlowBasicBlock::toString) - .map(string -> string + "-".repeat(50) + "\n") - .collect(Collectors.joining()); - - return this.head + "-".repeat(100) + "\n" - + "-".repeat(50) + "\n" + blocksString + "-".repeat(100) + "\n" - + this.tail; - } - public String printToImage() { - if (this.basicBlocks.isEmpty()) { + final Optional currentBlock = this.getCurrentBlock(); + + if (this.basicBlocks.isEmpty() || currentBlock.isEmpty()) { return "Empty Graph"; } @@ -174,45 +214,19 @@ public class FlowGraph implements Iterable { .append("END[label=\"END\"];\n"); dot.append("START -> ").append(this.basicBlocks.get(0).getId()).append(";\n"); - dot.append(this.getCurrentBlock().getId()).append(" -> END;\n"); + dot.append(currentBlock.get().getId()).append(" -> END;\n"); for (FlowBasicBlock block : this.basicBlocks) { // Successors - for (FlowBasicBlock succ : block.getSuccessorSet()) { - if (!dot.toString().contains(block.getId() + " -> " + succ.getId())) { - // No duplicate arrows - dot.append(block.getId()).append(" -> ").append(succ.getId()).append(";\n"); - } - } - - // Predecessors - for (FlowBasicBlock pred : block.getPredecessorSet()) { - if (!dot.toString().contains(pred.getId() + " -> " + block.getId())) { - // No duplicate arrows - - dot.append(pred.getId()).append(" -> ").append(block.getId()).append(";\n"); - } + for (FlowBasicBlock successor : block.getBlockSuccessorSet()) { + dot.append(block.getId()).append(" -> ").append(successor.getId()).append(";\n"); } } dot.append("}"); - final String dotOut = dot.toString(); - - final Path dotFile = Paths.get(System.getProperty("user.dir") + "/FlowGraph.dot"); - try { - Files.writeString(dotFile, dotOut); - } catch (IOException e) { - e.printStackTrace(); - } - - final ProcessBuilder dotCompile = new ProcessBuilder("dot", "-Tsvg", "-oFlowGraph.svg", "FlowGraph.dot"); - try { - dotCompile.start(); - } catch (IOException e) { - e.printStackTrace(); - } + GraphvizCaller.callGraphviz(dot, "FlowGraph"); return "Finished."; } @@ -225,9 +239,9 @@ public class FlowGraph implements Iterable { .map(FlowBasicBlock::toString) .collect(Collectors.joining()); - return this.head + return this.exportHead + blocksString - + this.tail; + + this.exportTail; } @Override diff --git a/src/main/java/codegen/flowgraph/FlowInstruction.java b/src/main/java/codegen/flowgraph/FlowInstruction.java index 2394c5e..52bbc01 100644 --- a/src/main/java/codegen/flowgraph/FlowInstruction.java +++ b/src/main/java/codegen/flowgraph/FlowInstruction.java @@ -1,24 +1,26 @@ package codegen.flowgraph; +/** + * Repräsentiert eine Instruction im {@link FlowGraph}. + */ public class FlowInstruction { private final String id; - private final String blockId; + + /** + * Die Instruction ist der Jasmin-Assembler Befehl. + */ private final String instruction; private final String[] args; - public FlowInstruction(String id, String blockId, String instruction, String... args) { + public FlowInstruction(String id, String instruction, String... args) { this.id = id; - this.blockId = blockId; this.instruction = instruction; this.args = args; } - @Override - public String toString() { - final String argsString = String.join(" ", this.args); - - return "\t\t" + this.instruction + " " + argsString; + public String getId() { + return this.id; } public String getInstruction() { @@ -29,11 +31,12 @@ public class FlowInstruction { return this.args; } - public String getBlockId() { - return this.blockId; - } + // Overrides - public String getId() { - return this.id; + @Override + public String toString() { + final String argsString = String.join(" ", this.args); + + return "\t\t" + this.instruction + " " + argsString; } }