rework FlowGraph

This commit is contained in:
ChUrl
2021-01-31 17:53:52 +01:00
parent 91e26d0a9d
commit f275d5f4a6
3 changed files with 211 additions and 153 deletions

View File

@ -1,21 +1,36 @@
package codegen.flowgraph; package codegen.flowgraph;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class FlowBasicBlock implements Iterable<FlowInstruction> { public class FlowBasicBlock implements Iterable<FlowInstruction> {
// Graph structure information
private final String id; private final String id;
private final String label;
private final List<FlowInstruction> instructions;
private final Set<FlowBasicBlock> predecessors; private final Set<FlowBasicBlock> predecessors;
private final Set<FlowBasicBlock> successors; private final Set<FlowBasicBlock> 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<FlowInstruction> instructions;
/**
* Wird intern benutzt um die ID für eine hinzugefügte {@link FlowInstruction} zu ermitteln.
*/
private int instNr; private int instNr;
public FlowBasicBlock(String label) { public FlowBasicBlock(String label) {
@ -38,6 +53,8 @@ public class FlowBasicBlock implements Iterable<FlowInstruction> {
return this.instructions.isEmpty() && this.label.isBlank(); return this.instructions.isEmpty() && this.label.isBlank();
} }
// Geteter, Setter
public String getId() { public String getId() {
return this.id; return this.id;
} }
@ -48,84 +65,105 @@ public class FlowBasicBlock implements Iterable<FlowInstruction> {
public void addInstruction(String instruction, String... args) { public void addInstruction(String instruction, String... args) {
this.instNr++; 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)); instruction, args));
} }
public void addSuccessor(FlowBasicBlock block) { public Set<FlowBasicBlock> getBlockSuccessorSet() {
this.successors.add(block); return Collections.unmodifiableSet(this.successors);
} }
public void addPredecessor(FlowBasicBlock block) { /**
this.predecessors.add(block); * 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<FlowBasicBlock> getSuccessorSet() { public Set<FlowInstruction> getInstructionSuccessorSet(FlowInstruction inst) {
return this.successors;
}
public Set<FlowInstruction> getPredecessors(FlowInstruction inst) {
final int index = this.instructions.indexOf(inst); final int index = this.instructions.indexOf(inst);
if (index == -1) { if (index == -1) {
return null; return Collections.emptySet();
}
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<FlowInstruction> getSuccessors(FlowInstruction inst) {
final int index = this.instructions.indexOf(inst);
if (index == -1) {
return null;
} }
if (index >= 0 && index < this.instructions.size() - 1) { 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)); return Set.of(this.instructions.get(index + 1));
} }
// Instruction is at the end // Instruction is at the end
return this.successors.stream() return this.successors.stream()
.map(FlowBasicBlock::getFirstInst) .map(FlowBasicBlock::getFirstInstruction)
.filter(Objects::nonNull) .filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toUnmodifiableSet()); .collect(Collectors.toUnmodifiableSet());
} }
public Set<FlowBasicBlock> getPredecessorSet() { public boolean addSuccessorBlock(FlowBasicBlock successor) {
return this.predecessors; return this.successors.add(successor);
} }
public FlowInstruction getFirstInst() { public boolean removeSuccessorBlock(FlowBasicBlock successor) {
if (!this.instructions.isEmpty()) { return this.successors.remove(successor);
return this.instructions.get(0);
}
return null;
} }
public FlowInstruction getLastInst() { public Set<FlowBasicBlock> getBlockPredecessorSet() {
if (!this.instructions.isEmpty()) { return Collections.unmodifiableSet(this.predecessors);
return this.instructions.get(this.instructions.size() - 1); }
/**
* 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<FlowInstruction> 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<FlowInstruction> getFirstInstruction() {
if (this.instructions.isEmpty()) {
return Optional.empty();
}
return Optional.of(this.instructions.get(0));
}
public Optional<FlowInstruction> getLastInstruction() {
if (this.instructions.isEmpty()) {
return Optional.empty();
}
return Optional.of(this.instructions.get(this.instructions.size() - 1));
} }
// Printing // Printing
/**
* Diese Methode ist für das Printen mit Graphviz, {@link #toString()} für den Rest.
*/
public String printInst() { public String printInst() {
return this.instructions.stream() return this.instructions.stream()
.map(inst -> inst.toString().trim() + "\\n") .map(inst -> inst.toString().trim() + "\\n")
@ -139,16 +177,19 @@ public class FlowBasicBlock implements Iterable<FlowInstruction> {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.id); return Objects.hash(this.id, this.label);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object o) {
if (obj instanceof FlowBasicBlock) { if (this == o) {
return this.id.equals(((FlowBasicBlock) obj).id); return true;
} }
if (o == null || this.getClass() != o.getClass()) {
return false; return false;
}
final FlowBasicBlock that = (FlowBasicBlock) o;
return this.id.equals(that.id) && this.label.equals(that.label);
} }
@Override @Override

View File

@ -1,53 +1,79 @@
package codegen.flowgraph; package codegen.flowgraph;
import parser.ast.AST;
import util.GraphvizCaller;
import util.Logger; 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.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Optional;
import java.util.stream.Collectors; 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<FlowBasicBlock> { public class FlowGraph implements Iterable<FlowBasicBlock> {
private final FlowGraphHead head;
private final List<FlowBasicBlock> basicBlocks; private final List<FlowBasicBlock> 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.
* <p>
* 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<String, FlowBasicBlock> predecessorMap; private final Map<String, FlowBasicBlock> predecessorMap;
public FlowGraph(String bytecodeVersion, String source, String clazz, int stackSize, int localCount) { 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.basicBlocks = new ArrayList<>();
this.tail = new FlowGraphTail(); this.exportTail = new FlowGraphTail();
this.predecessorMap = new HashMap<>(); 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) { public void addLabel(String label) {
final FlowBasicBlock newBlock = new FlowBasicBlock(label); final FlowBasicBlock newBlock = new FlowBasicBlock(label);
// Resolve missing successors/predecessors from jumps // Resolve missing successors/predecessors from jumps
if (this.predecessorMap.containsKey(label)) { if (this.predecessorMap.containsKey(label)) {
this.predecessorMap.get(label).addSuccessor(newBlock); this.predecessorMap.get(label).addSuccessorBlock(newBlock);
newBlock.addPredecessor(this.predecessorMap.get(label)); newBlock.addPredecessorBlock(this.predecessorMap.get(label));
} }
newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block final Optional<FlowBasicBlock> currentBlock = this.getCurrentBlock();
this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block 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); 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.
* <p>
* Da GoTo immer springt, wird diese Sprunganweisung gesondert betrachtet.
*
* @param jumpInstruction Der verwendete Sprungbefehl.
*/
public void addJump(String jumpInstruction, String label) { public void addJump(String jumpInstruction, String label) {
this.addInstruction(jumpInstruction, label); this.addInstruction(jumpInstruction, label);
@ -56,21 +82,30 @@ public class FlowGraph implements Iterable<FlowBasicBlock> {
if (!"goto".equals(jumpInstruction)) { if (!"goto".equals(jumpInstruction)) {
// Goto always jumps, so we don't have a direct relation in order of the code // 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 final Optional<FlowBasicBlock> currentBlock = this.getCurrentBlock();
this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block if (currentBlock.isPresent()) {
newBlock.addPredecessorBlock(currentBlock.get()); // Obvious predecessor of new block
currentBlock.get().addSuccessorBlock(newBlock); // Obvious successor of current block
}
} }
// Jumped successor // Jumped successor
final FlowBasicBlock labelBlock = this.getBlockByLabel(label); final Optional<FlowBasicBlock> labelBlock = this.getBlockByLabel(label);
if (labelBlock != null) { final Optional<FlowBasicBlock> currentBlock = this.getCurrentBlock();
if (labelBlock.isPresent()) {
// Successor exists // Successor exists
this.getCurrentBlock().addSuccessor(labelBlock); if (currentBlock.isPresent()) {
labelBlock.addPredecessor(this.getCurrentBlock()); currentBlock.get().addSuccessorBlock(labelBlock.get());
labelBlock.get().addPredecessorBlock(currentBlock.get());
}
} else { } else {
// Successor doesn't exist, so wait until it does // 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); this.basicBlocks.add(newBlock);
@ -81,16 +116,20 @@ public class FlowGraph implements Iterable<FlowBasicBlock> {
this.basicBlocks.add(new FlowBasicBlock("START")); // First block doesn't exist this.basicBlocks.add(new FlowBasicBlock("START")); // First block doesn't exist
} }
this.getCurrentBlock().addInstruction(instruction, args); // Add to last block final Optional<FlowBasicBlock> currentBlock = this.getCurrentBlock();
// Add to last block
currentBlock.ifPresent(flowBasicBlock -> flowBasicBlock.addInstruction(instruction, args));
} }
/** /**
* Entfernt leere Blöcke. * Entfernt leere Blöcke.
* Ein Block ist "leer", wenn er kein Label und keine Instructions hat.
*/ */
public void purgeEmptyBlocks() { public void purgeEmptyBlocks() {
Logger.log("\nPurging empty blocks: "); Logger.log("\nPurging empty blocks: ");
final Set<FlowBasicBlock> toRemove = new HashSet<>(); final Collection<FlowBasicBlock> toRemove = new HashSet<>();
// Collect removable blocks // Collect removable blocks
for (FlowBasicBlock block : this.basicBlocks) { for (FlowBasicBlock block : this.basicBlocks) {
@ -103,52 +142,53 @@ public class FlowGraph implements Iterable<FlowBasicBlock> {
// Remove blocks + reroute predecessors/successors // Remove blocks + reroute predecessors/successors
for (FlowBasicBlock block : toRemove) { 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(predecessor)
+ " to Block " + this.basicBlocks.indexOf(successor));
Logger.log("Rerouting Block " + this.basicBlocks.indexOf(pred) + " to Block " + this.basicBlocks.indexOf(succ)); predecessor.addSuccessorBlock(successor);
pred.addSuccessor(succ); successor.addPredecessorBlock(predecessor);
succ.addPredecessor(pred);
} }
pred.getSuccessorSet().remove(block);
} }
for (FlowBasicBlock succ : block.getSuccessorSet()) { // Remove references
succ.getPredecessorSet().remove(block); for (FlowBasicBlock predecessor : block.getBlockPredecessorSet()) {
predecessor.removeSuccessorBlock(block);
}
for (FlowBasicBlock successor : block.getBlockSuccessorSet()) {
successor.removePredecessorBlock(block);
} }
} }
this.basicBlocks.removeAll(toRemove); this.basicBlocks.removeAll(toRemove);
} }
private FlowBasicBlock getBlockByLabel(String label) { private Optional<FlowBasicBlock> getBlockByLabel(String label) {
return this.basicBlocks.stream() return this.basicBlocks.stream()
.filter(block -> block.getLabel().equals(label)) .filter(block -> block.getLabel().equals(label))
.findFirst() .findFirst();
.orElse(null);
} }
private FlowBasicBlock getCurrentBlock() { /**
return this.basicBlocks.get(this.basicBlocks.size() - 1); * Der aktuelle Block ist immer der letzte Block.
*/
private Optional<FlowBasicBlock> getCurrentBlock() {
if (this.basicBlocks.isEmpty()) {
return Optional.empty();
}
return Optional.of(this.basicBlocks.get(this.basicBlocks.size() - 1));
} }
// Printing // 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() { public String printToImage() {
if (this.basicBlocks.isEmpty()) { final Optional<FlowBasicBlock> currentBlock = this.getCurrentBlock();
if (this.basicBlocks.isEmpty() || currentBlock.isEmpty()) {
return "Empty Graph"; return "Empty Graph";
} }
@ -174,45 +214,19 @@ public class FlowGraph implements Iterable<FlowBasicBlock> {
.append("END[label=\"END\"];\n"); .append("END[label=\"END\"];\n");
dot.append("START -> ").append(this.basicBlocks.get(0).getId()).append(";\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) { for (FlowBasicBlock block : this.basicBlocks) {
// Successors // 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"); for (FlowBasicBlock successor : block.getBlockSuccessorSet()) {
} dot.append(block.getId()).append(" -> ").append(successor.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");
}
} }
} }
dot.append("}"); dot.append("}");
final String dotOut = dot.toString(); GraphvizCaller.callGraphviz(dot, "FlowGraph");
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();
}
return "Finished."; return "Finished.";
} }
@ -225,9 +239,9 @@ public class FlowGraph implements Iterable<FlowBasicBlock> {
.map(FlowBasicBlock::toString) .map(FlowBasicBlock::toString)
.collect(Collectors.joining()); .collect(Collectors.joining());
return this.head return this.exportHead
+ blocksString + blocksString
+ this.tail; + this.exportTail;
} }
@Override @Override

View File

@ -1,24 +1,26 @@
package codegen.flowgraph; package codegen.flowgraph;
/**
* Repräsentiert eine Instruction im {@link FlowGraph}.
*/
public class FlowInstruction { public class FlowInstruction {
private final String id; private final String id;
private final String blockId;
/**
* Die Instruction ist der Jasmin-Assembler Befehl.
*/
private final String instruction; private final String instruction;
private final String[] args; 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.id = id;
this.blockId = blockId;
this.instruction = instruction; this.instruction = instruction;
this.args = args; this.args = args;
} }
@Override public String getId() {
public String toString() { return this.id;
final String argsString = String.join(" ", this.args);
return "\t\t" + this.instruction + " " + argsString;
} }
public String getInstruction() { public String getInstruction() {
@ -29,11 +31,12 @@ public class FlowInstruction {
return this.args; return this.args;
} }
public String getBlockId() { // Overrides
return this.blockId;
}
public String getId() { @Override
return this.id; public String toString() {
final String argsString = String.join(" ", this.args);
return "\t\t" + this.instruction + " " + argsString;
} }
} }