From 6e7208e6d3658243747e63b6ce95fd9f7cc4c10f Mon Sep 17 00:00:00 2001 From: ChUrl Date: Sat, 30 Jan 2021 15:30:24 +0100 Subject: [PATCH] bufixes with goto-successors, empty block removal --- .../codegen/flowgraph/FlowBasicBlock.java | 6 +- .../java/codegen/flowgraph/FlowGraph.java | 159 +++++++++------- .../codegen/flowgraph/FlowGraphGenerator.java | 174 ++++++++++++------ 3 files changed, 212 insertions(+), 127 deletions(-) diff --git a/src/main/java/codegen/flowgraph/FlowBasicBlock.java b/src/main/java/codegen/flowgraph/FlowBasicBlock.java index 2357a5f..286ed87 100644 --- a/src/main/java/codegen/flowgraph/FlowBasicBlock.java +++ b/src/main/java/codegen/flowgraph/FlowBasicBlock.java @@ -29,8 +29,12 @@ public class FlowBasicBlock { this(""); } + /** + * Ermittelt ob ein BasicBlock ohne weiteres entfernbar ist. + * Der Block darf kein Label haben, damit keine Sprünge ins Leere passieren. + */ public boolean isEmpty() { - return this.label.isBlank() && this.instructions.isEmpty(); + return this.instructions.isEmpty() && this.label.isBlank(); } public String getId() { diff --git a/src/main/java/codegen/flowgraph/FlowGraph.java b/src/main/java/codegen/flowgraph/FlowGraph.java index ace10b1..947db97 100644 --- a/src/main/java/codegen/flowgraph/FlowGraph.java +++ b/src/main/java/codegen/flowgraph/FlowGraph.java @@ -1,19 +1,23 @@ package codegen.flowgraph; +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.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class FlowGraph { private final FlowGraphHead head; - private final List blocks; + private final List basicBlocks; private final FlowGraphTail tail; // If a new block has this label, the value in this map is a predecessor @@ -21,7 +25,7 @@ public class FlowGraph { public FlowGraph(String bytecodeVersion, String source, String clazz, int stackSize, int localCount) { this.head = new FlowGraphHead(bytecodeVersion, source, clazz, stackSize, localCount); - this.blocks = new ArrayList<>(); + this.basicBlocks = new ArrayList<>(); this.tail = new FlowGraphTail(); this.predecessorMap = new HashMap<>(); } @@ -35,46 +39,24 @@ public class FlowGraph { 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 } - /* - TODO: Hier ist ein Bug, welcher im Datenflussgraph zu gotos mit 2 successors führt - if (this.getCurrentBlock().isEmpty()) { - // Replace empty blocks, we don't need them + newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block + this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block - 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().getSuccessorSet()); - } - - // Previous blocks predecessors are also the new blocks predecessors - newBlock.addPredecessors(this.getCurrentBlock().getPredecessorSet()); - - this.blocks.set(this.blocks.size() - 1, newBlock); // Replace - } else { - */ - // 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); - // } + this.basicBlocks.add(newBlock); } // Jump means end of block public void addJump(String jumpInstruction, String label) { - this.addInst(jumpInstruction, label); + this.addInstruction(jumpInstruction, label); final FlowBasicBlock newBlock = new FlowBasicBlock(); - newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block if (!"goto".equals(jumpInstruction)) { // Goto always jumps + newBlock.addPredecessor(this.getCurrentBlock()); // Obvious predecessor of new block this.getCurrentBlock().addSuccessor(newBlock); // Obvious successor of current block } @@ -91,22 +73,80 @@ public class FlowGraph { this.predecessorMap.put(label, this.getCurrentBlock()); // Current node is predecessor of label-block } - this.blocks.add(newBlock); + this.basicBlocks.add(newBlock); } - public void addInst(String instruction, String... args) { - if (this.blocks.isEmpty()) { - this.blocks.add(new FlowBasicBlock()); // First block doesn't exist + public void addInstruction(String instruction, String... args) { + if (this.basicBlocks.isEmpty()) { + this.basicBlocks.add(new FlowBasicBlock("START")); // First block doesn't exist } this.getCurrentBlock().addInstruction(instruction, args); // Add to last block } + /** + * Entfernt leere Blöcke. + */ + public void purgeEmptyBlocks() { + Logger.log("\nPurging empty blocks: "); + + final Set toRemove = new HashSet<>(); + + // Collect removable blocks + for (FlowBasicBlock block : this.basicBlocks) { + if (block.isEmpty()) { + Logger.log("Marking Block " + this.basicBlocks.indexOf(block) + " as removable."); + toRemove.add(block); + } + } + + // Remove blocks + reroute predecessors/successors + for (FlowBasicBlock block : toRemove) { + + for (FlowBasicBlock pred : block.getPredecessorSet()) { + + 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); + } + + pred.getSuccessorSet().remove(block); + } + + for (FlowBasicBlock succ : block.getSuccessorSet()) { + succ.getPredecessorSet().remove(block); + } + } + + this.basicBlocks.removeAll(toRemove); + } + + // Getters, Setters + + private FlowBasicBlock getBlockByLabel(String label) { + return this.basicBlocks.stream() + .filter(block -> block.getLabel().equals(label)) + .findFirst() + .orElse(null); + } + + private FlowBasicBlock getCurrentBlock() { + return this.basicBlocks.get(this.basicBlocks.size() - 1); + } + + public List getBasicBlocks() { + return this.basicBlocks; + } + + // Print + Overrides + public String print() { - final String blocksString = this.blocks.stream() - .map(FlowBasicBlock::toString) - .map(string -> string + "-".repeat(50) + "\n") - .collect(Collectors.joining()); + 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" @@ -119,10 +159,10 @@ public class FlowGraph { dot.append("digraph dfd {\n") .append("node[shape=Mrecord]\n"); - for (FlowBasicBlock block : this.blocks) { + for (FlowBasicBlock block : this.basicBlocks) { dot.append(block.getId()) .append(" [label=\"{ ") - .append(this.blocks.indexOf(block)) + .append(this.basicBlocks.indexOf(block)) .append(": ") .append(block.getLabel()) .append("| ") @@ -133,13 +173,23 @@ public class FlowGraph { dot.append("START[label=\"START\"];\n") .append("END[label=\"END\"];\n"); - dot.append("START -> ").append(this.blocks.get(0).getId()).append(";\n"); + dot.append("START -> ").append(this.basicBlocks.get(0).getId()).append(";\n"); dot.append(this.getCurrentBlock().getId()).append(" -> END;\n"); - for (FlowBasicBlock block : this.blocks) { + for (FlowBasicBlock block : this.basicBlocks) { + // Successors for (FlowBasicBlock succ : block.getSuccessorSet()) { 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"); + } + } } dot.append("}"); @@ -165,34 +215,13 @@ public class FlowGraph { @Override public String toString() { - final String blocksString = this.blocks.stream() - .map(FlowBasicBlock::toString) - .collect(Collectors.joining()); + final String blocksString = this.basicBlocks.stream() + .map(FlowBasicBlock::toString) + .collect(Collectors.joining()); return this.head + blocksString + this.tail; } - private FlowBasicBlock getBlockByLabel(String label) { - return this.blocks.stream() - .filter(block -> block.getLabel().equals(label)) - .findFirst() - .orElse(null); - } - - private FlowBasicBlock getCurrentBlock() { - return this.blocks.get(this.blocks.size() - 1); - } - - public List getBlocks() { - return this.blocks; - } - - public FlowBasicBlock getBlockById(String id) { - return this.blocks.stream() - .filter(block -> block.getId().equals(id)) - .findFirst() - .orElse(null); - } } diff --git a/src/main/java/codegen/flowgraph/FlowGraphGenerator.java b/src/main/java/codegen/flowgraph/FlowGraphGenerator.java index c4e3445..9e03f2a 100644 --- a/src/main/java/codegen/flowgraph/FlowGraphGenerator.java +++ b/src/main/java/codegen/flowgraph/FlowGraphGenerator.java @@ -17,11 +17,15 @@ import java.util.Map; import static java.util.Map.entry; import static util.Logger.log; +/** + * Erzeugt den SourceCode in FlussGraph-Darstellung. + */ public final class FlowGraphGenerator { private static final Map methodMap; static { + // Init the method mappings: ASTNode.getName() -> FlowGraphGenerator.method Map map; try { final Class gen = FlowGraphGenerator.class; @@ -76,7 +80,7 @@ public final class FlowGraphGenerator { private static Map initVarMap(AST tree) { final Map varMap = new HashMap<>(); - // Assign variables to map + // Assign variables to map: Symbol -> jasminLocalVarNr. int varCount = 0; final Deque stack = new ArrayDeque<>(); stack.push(tree.getRoot()); @@ -97,14 +101,17 @@ public final class FlowGraphGenerator { return Collections.unmodifiableMap(varMap); } - public Map getVarMap() { - return this.varMap; - } - + /** + * Erzeugt den Flussgraphen für den gespeicherten AST. + * Der Flussgraph ist dabei die Graphenform des generierten SourceCodes: + * Die Instruktionen sind unterteilt in BasicBlocks, welche über Kanten verbunden sind. + */ public FlowGraph generateGraph() { System.out.println(" - Generating Source Graph..."); + // Skip the first 2 identifiers: ClassName, MainArgs this.generateNode(this.tree.getRoot().getChildren().get(3).getChildren().get(11)); + this.graph.purgeEmptyBlocks(); Logger.call(this.graph::printToImage); log("\n\nSourceGraph print:\n" + "-".repeat(100) + "\n" + this.graph.print() + "-".repeat(100)); @@ -113,137 +120,168 @@ public final class FlowGraphGenerator { return this.graph; } - private void generateNode(ASTNode node) { - if (methodMap.containsKey(node.getName())) { + /** + * Erzeugt den FlussGraphen für die angegebene Wurzel. + * Der Wurzelname wird über die methodMap einer Methode zugewiesen. + * Diese wird aufgerufen und erzeugt den entsprechenden Teilbaum. + */ + private void generateNode(ASTNode root) { + if (methodMap.containsKey(root.getName())) { try { - methodMap.get(node.getName()).invoke(this, node); + methodMap.get(root.getName()).invoke(this, root); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } else { - node.getChildren().forEach(this::generateNode); + root.getChildren().forEach(this::generateNode); } } // ifeq - if value is 0 // ifne - if value is not 0 - private void condNode(ASTNode node) { + + /** + * Erzeugt den Teilbaum für einen If-Knoten. + */ + private void condNode(ASTNode root) { final int currentLabel = this.labelCounter; this.labelCounter++; - // Condition - this.generateNode(node.getChildren().get(0)); + // Condition If ( ... ) { + this.generateNode(root.getChildren().get(0)); - // Jump + // Jump if condition false this.graph.addJump("ifeq", "IFfalse" + currentLabel); - // IFtrue branch - this.generateNode(node.getChildren().get(1)); - this.graph.addJump("goto", "IFend" + currentLabel); + // IFtrue branch (gets executed without jump) + this.generateNode(root.getChildren().get(1)); + this.graph.addJump("goto", "IFend" + currentLabel); // Skip IFfalse branch - // IFfalse branch + // IFfalse branch (gets executed after jump) this.graph.addLabel("IFfalse" + currentLabel); - if (node.getChildren().size() == 3) { + if (root.getChildren().size() == 3) { // Else exists - this.generateNode(node.getChildren().get(2)); + this.generateNode(root.getChildren().get(2)); } // IFend branch this.graph.addLabel("IFend" + currentLabel); } - private void loopNode(ASTNode node) { + /** + * Erzeugt den Teilbaum für einen While-Knoten. + */ + private void loopNode(ASTNode root) { final int currentLabel = this.labelCounter; this.labelCounter++; + // LOOPstart label for loop repetition this.graph.addLabel("LOOPstart" + currentLabel); - // Condition - this.generateNode(node.getChildren().get(0).getChildren().get(1)); + // Condition while ( ... ) { + this.generateNode(root.getChildren().get(0).getChildren().get(1)); - // Jump + // Jump out of loop if condition is false this.graph.addJump("ifeq", "LOOPend" + currentLabel); - // Loop body - this.generateNode(node.getChildren().get(1)); - this.graph.addJump("goto", "LOOPstart" + currentLabel); + // Loop body (gets executed without jump) + this.generateNode(root.getChildren().get(1)); + this.graph.addJump("goto", "LOOPstart" + currentLabel); // Repeat loop // Loop end this.graph.addLabel("LOOPend" + currentLabel); } - private void assignNode(ASTNode node) { //! Stack - 1 - this.generateNode(node.getChildren().get(0)); + /** + * Erzeugt den Teilbaum für Assignment-Knoten. + * Die JVM-Stacksize wird dabei um 1 verringert, da istore/astore 1 Argument konsumieren. + */ + private void assignNode(ASTNode root) { //! Stack - 1 + this.generateNode(root.getChildren().get(0)); - final String type = this.nodeTypeMap.get(node.getChildren().get(0)); + final String type = this.nodeTypeMap.get(root.getChildren().get(0)); final String inst = switch (type) { case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "istore"; case "STRING_TYPE" -> "astore"; default -> throw new IllegalStateException("Unexpected value: " + type); }; - log("assign(): " + node.getName() + ": " + node.getValue() + " => " + inst); + log("assign(): " + root.getName() + ": " + root.getValue() + " => " + inst); - this.graph.addInst(inst, this.varMap.get(node.getValue()).toString()); + this.graph.addInstruction(inst, this.varMap.get(root.getValue()).toString()); } - private void exprNode(ASTNode node) { - if ("INTEGER_TYPE".equals(this.nodeTypeMap.get(node))) { - this.intExpr(node); - } else if ("BOOLEAN_TYPE".equals(this.nodeTypeMap.get(node))) { - this.boolExpr(node); + /** + * Wählt die entsprechende Methode für mathematische oder logische Ausdrücke. + */ + private void exprNode(ASTNode root) { + if ("INTEGER_TYPE".equals(this.nodeTypeMap.get(root))) { + this.intExpr(root); + } else if ("BOOLEAN_TYPE".equals(this.nodeTypeMap.get(root))) { + this.boolExpr(root); } } - private void intExpr(ASTNode node) { + /** + * Erzeugt den Teilbaum für mathematische Ausdrücke. + * Bei unären Operatoren bleibt die Stackgröße konstant (1 konsumiert, 1 Ergebnis), + * bei binären Operatoren sinkt die Stackgröße um 1 (2 konsumiert, 1 Ergebnis). + */ + private void intExpr(ASTNode root) { String inst = ""; - if (node.getChildren().size() == 1) { //! Stack + 0 + if (root.getChildren().size() == 1) { //! Stack + 0 // Unary operator - this.generateNode(node.getChildren().get(0)); + this.generateNode(root.getChildren().get(0)); - inst = switch (node.getValue()) { + inst = switch (root.getValue()) { case "ADD" -> ""; case "SUB" -> "ineg"; - default -> throw new IllegalStateException("Unexpected value: " + node.getValue()); + default -> throw new IllegalStateException("Unexpected value: " + root.getValue()); }; - } else if (node.getChildren().size() == 2) { //! Stack - 1 + } else if (root.getChildren().size() == 2) { //! Stack - 1 // Binary operator - this.generateNode(node.getChildren().get(0)); - this.generateNode(node.getChildren().get(1)); + this.generateNode(root.getChildren().get(0)); + this.generateNode(root.getChildren().get(1)); - inst = switch (node.getValue()) { + inst = switch (root.getValue()) { case "ADD" -> "iadd"; // Integer case "SUB" -> "isub"; case "MUL" -> "imul"; case "DIV" -> "idiv"; case "MOD" -> "irem"; // Remainder operator - default -> throw new IllegalStateException("Unexpected value: " + node.getValue()); + default -> throw new IllegalStateException("Unexpected value: " + root.getValue()); }; } - log("intExpr(): " + node.getName() + ": " + node.getValue() + " => " + inst); + log("intExpr(): " + root.getName() + ": " + root.getValue() + " => " + inst); - this.graph.addInst(inst); + this.graph.addInstruction(inst); } + /** + * Erzeugt den Teilbaum für logische Ausdrücke. + * Bei unären Operatoren wächst der Stack temporär um 1 (NOT pusht eine 1 für xor), + * bei binären Operatoren sinkt die Stackgröße um 1 (2 konsumiert, 1 Ergebnis). + */ private void boolExpr(ASTNode node) { if (node.getChildren().size() == 1) { //! Stack + 1 // Unary operator if (!"NOT".equals(node.getValue())) { - // Diese Möglichkeit gibts eigentlich nicht + // Possibility doesn't exist, would be frontend-error + throw new IllegalStateException("Unexpected value: " + node.getValue()); } this.generateNode(node.getChildren().get(0)); - // 0 ^1 = 1, 1 ^1 = 0 - this.graph.addInst("ldc", "1"); - this.graph.addInst("ixor"); + // 0 xor 1 = 1, 1 xor 1 = 0 => not + this.graph.addInstruction("ldc", "1"); + this.graph.addInstruction("ixor"); } else if (node.getChildren().size() == 2) { //! Stack - 1 // Binary operator @@ -266,9 +304,10 @@ public final class FlowGraphGenerator { default -> throw new IllegalStateException("Unexpected value: " + type); }; + // The comparison operations need to jump switch (node.getValue()) { - case "AND" -> this.graph.addInst("iand"); // Boolean - case "OR" -> this.graph.addInst("ior"); + case "AND" -> this.graph.addInstruction("iand"); // Boolean + case "OR" -> this.graph.addInstruction("ior"); case "EQUAL" -> this.genComparisonInst(cmpeq, "EQ", currentLabel); case "NOT_EQUAL" -> this.genComparisonInst(cmpne, "NE", currentLabel); case "LESS" -> this.genComparisonInst("if_icmplt", "LT", currentLabel); @@ -280,12 +319,19 @@ public final class FlowGraphGenerator { } } + /** + * Erzeugt die Instruktionen für eine Vergleichsoperation. + * + * @param cmpInst Die Vergleichsanweisung + * @param labelPre Das Labelpräfix, abhängig von der Art des Vergleichs + * @param currentLabel Der aktuelle Labelcounter + */ private void genComparisonInst(String cmpInst, String labelPre, int currentLabel) { this.graph.addJump(cmpInst, labelPre + "true" + currentLabel); // If not equal jump to NEtrue - this.graph.addInst("ldc", "0"); // If false load 0 + this.graph.addInstruction("ldc", "0"); // If false load 0 this.graph.addJump("goto", labelPre + "end" + currentLabel); // If false skip to true this.graph.addLabel(labelPre + "true" + currentLabel); - this.graph.addInst("ldc", "1"); // If true load 1 + this.graph.addInstruction("ldc", "1"); // If true load 1 this.graph.addLabel(labelPre + "end" + currentLabel); } @@ -295,7 +341,7 @@ public final class FlowGraphGenerator { log("intStringLiteral(): " + node.getName() + ": " + node.getValue() + " => ldc"); // bipush only pushes 1 byte as int - this.graph.addInst("ldc", node.getValue()); + this.graph.addInstruction("ldc", node.getValue()); } private void boolLiteralNode(ASTNode node) { //! Stack + 1 @@ -303,7 +349,7 @@ public final class FlowGraphGenerator { final String val = "true".equals(node.getValue()) ? "1" : "0"; - this.graph.addInst("ldc", val); + this.graph.addInstruction("ldc", val); } private void identifierNode(ASTNode node) { //! Stack + 1 @@ -316,11 +362,11 @@ public final class FlowGraphGenerator { log("identifier(): " + node.getName() + ": " + node.getValue() + " => " + inst); - this.graph.addInst(inst, this.varMap.get(node.getValue()).toString()); + this.graph.addInstruction(inst, this.varMap.get(node.getValue()).toString()); } private void printlnNode(ASTNode node) { //! Stack + 1 - this.graph.addInst("getstatic", "java/lang/System/out", "Ljava/io/PrintStream;"); + this.graph.addInstruction("getstatic", "java/lang/System/out", "Ljava/io/PrintStream;"); final ASTNode expr = node.getChildren().get(1).getChildren().get(1); final String type = switch (this.nodeTypeMap.get(expr)) { @@ -334,6 +380,12 @@ public final class FlowGraphGenerator { log("println(): " + expr.getName() + ": " + expr.getValue() + " => " + type); - this.graph.addInst("invokevirtual", "java/io/PrintStream/println(" + type + ")V"); + this.graph.addInstruction("invokevirtual", "java/io/PrintStream/println(" + type + ")V"); + } + + // Getters, Setters + + public Map getVarMap() { + return this.varMap; } }