diff --git a/README.md b/README.md index ce67e47..cde3aaa 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,48 @@ To figure out the cause, we provide utilities that ease debugging. subgraphs, or https://www.yworks.com/yed-live/, which is relatively good at neighbourhoods and larger layouts) that can visualize that output. It allows debugging anything related to the IR. +- `edu.kit.kastel.vads.compiler.ir.util.YCompPrinter` can generate output for [yComp](https://pp.ipd.kit.edu/firm/yComp.html). + This tool is more sophisticated than GraphViz. See below for further information. We also try to keep track of source positions as much as possible through the compiler. You can get rid of all that, but it can be helpful to track down where something comes from. +### yComp + +To use yComp, you need to patch the provided start script to make it work with modern Java versions. +You can copy-paste the following script: +```sh +#!/bin/sh +set -e + +YCOMP="$0" +while [ -L "$YCOMP" ]; do + LINK="$(readlink "$YCOMP")" + case "$LINK" in + /*) YCOMP="$LINK";; + *) YCOMP="${YCOMP%/*}/$LINK";; + esac +done + +# 1.5.0_22, 1.8, 9, 11.0.2... +# We only match on the first field +JAVA_VERSION="$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)" + +ADDITIONAL_ARGUMENTS="" + +if [ "$JAVA_VERSION" -gt 9 ]; then + echo "Java 9+ detected, opening potentially required packages" + ADDITIONAL_ARGUMENTS='--add-opens java.desktop/sun.swing=ALL-UNNAMED' +fi + +echo "Commandline is: 'java -Xmx512m $ADDITIONAL_ARGUMENTS -jar "${YCOMP%/*}/yComp.jar" "$@"'" +java -Xmx512m $ADDITIONAL_ARGUMENTS -jar "${YCOMP%/*}/yComp.jar" "$@" +``` + +You can directly dump graphs by setting the `DUMP_GRAPHS` environment variable to `vcg` or by passing `-DdumpGraphs=vcg` +to the compiler as a JVM argument (not as a program argument!). +The graphs will be dumped to the `graphs` directory relative to the output file. + ## Miscellaneous ### Nullability diff --git a/src/main/java/edu/kit/kastel/vads/compiler/Main.java b/src/main/java/edu/kit/kastel/vads/compiler/Main.java index 606cf74..d6034b4 100644 --- a/src/main/java/edu/kit/kastel/vads/compiler/Main.java +++ b/src/main/java/edu/kit/kastel/vads/compiler/Main.java @@ -4,6 +4,7 @@ import edu.kit.kastel.vads.compiler.backend.aasm.CodeGenerator; import edu.kit.kastel.vads.compiler.ir.IrGraph; import edu.kit.kastel.vads.compiler.ir.SsaTranslation; import edu.kit.kastel.vads.compiler.ir.optimize.LocalValueNumbering; +import edu.kit.kastel.vads.compiler.ir.util.YCompPrinter; import edu.kit.kastel.vads.compiler.lexer.Lexer; import edu.kit.kastel.vads.compiler.parser.ParseException; import edu.kit.kastel.vads.compiler.parser.Parser; @@ -12,7 +13,6 @@ import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree; import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree; import edu.kit.kastel.vads.compiler.semantic.SemanticAnalysis; import edu.kit.kastel.vads.compiler.semantic.SemanticException; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -41,6 +41,14 @@ public class Main { graphs.add(translation.translate()); } + if ("vcg".equals(System.getenv("DUMP_GRAPHS")) || "vcg".equals(System.getProperty("dumpGraphs"))) { + Path tmp = output.toAbsolutePath().resolveSibling("graphs"); + Files.createDirectory(tmp); + for (IrGraph graph : graphs) { + dumpGraph(graph, tmp, "before-codegen"); + } + } + // TODO: generate assembly and invoke gcc instead of generating abstract assembly String s = new CodeGenerator().generateCode(graphs); Files.writeString(output, s); @@ -58,4 +66,11 @@ public class Main { throw new AssertionError("unreachable"); } } -} \ No newline at end of file + + private static void dumpGraph(IrGraph graph, Path path, String key) throws IOException { + Files.writeString( + path.resolve(graph.name() + "-" + key + ".vcg"), + YCompPrinter.print(graph) + ); + } +} diff --git a/src/main/java/edu/kit/kastel/vads/compiler/ir/node/ProjNode.java b/src/main/java/edu/kit/kastel/vads/compiler/ir/node/ProjNode.java index 041c2b5..9c62674 100644 --- a/src/main/java/edu/kit/kastel/vads/compiler/ir/node/ProjNode.java +++ b/src/main/java/edu/kit/kastel/vads/compiler/ir/node/ProjNode.java @@ -14,6 +14,10 @@ public final class ProjNode extends Node { return this.projectionInfo.toString(); } + public ProjectionInfo projectionInfo() { + return projectionInfo; + } + public sealed interface ProjectionInfo { } diff --git a/src/main/java/edu/kit/kastel/vads/compiler/ir/util/YCompPrinter.java b/src/main/java/edu/kit/kastel/vads/compiler/ir/util/YCompPrinter.java new file mode 100644 index 0000000..a62434b --- /dev/null +++ b/src/main/java/edu/kit/kastel/vads/compiler/ir/util/YCompPrinter.java @@ -0,0 +1,302 @@ +package edu.kit.kastel.vads.compiler.ir.util; + +import edu.kit.kastel.vads.compiler.ir.IrGraph; +import edu.kit.kastel.vads.compiler.ir.node.BinaryOperationNode; +import edu.kit.kastel.vads.compiler.ir.node.Block; +import edu.kit.kastel.vads.compiler.ir.node.ConstIntNode; +import edu.kit.kastel.vads.compiler.ir.node.Node; +import edu.kit.kastel.vads.compiler.ir.node.Phi; +import edu.kit.kastel.vads.compiler.ir.node.ProjNode; +import edu.kit.kastel.vads.compiler.ir.node.ProjNode.SimpleProjectionInfo; +import edu.kit.kastel.vads.compiler.ir.node.ReturnNode; +import edu.kit.kastel.vads.compiler.ir.node.StartNode; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.IntStream; + +public class YCompPrinter { + + private final Map> clusters = new HashMap<>(); + private final Map ids = new HashMap<>(); + private final IrGraph graph; + private int nodeCounter = 0; + private int blockCounter = 0; + + public YCompPrinter(IrGraph graph) { + this.graph = graph; + } + + private void prepare(Node node, Set seen) { + if (!seen.add(node)) { + return; + } + + if (!(node instanceof Block)) { + this.clusters.computeIfAbsent( + node.block(), + _ -> Collections.newSetFromMap(new IdentityHashMap<>()) + ) + .add(node); + } + for (Node predecessor : node.predecessors()) { + prepare(predecessor, seen); + } + if (node == this.graph.endBlock()) { + this.clusters.put(this.graph.endBlock(), Set.of()); + } + } + + public static String print(IrGraph graph) { + YCompPrinter printer = new YCompPrinter(graph); + printer.prepare(graph.endBlock(), new HashSet<>()); + return printer.dumpGraphAsString(); + } + + private String dumpGraphAsString() { + StringBuilder result = new StringBuilder(); + + result.append("graph: {"); + String graphName = this.graph.name(); + result.append("\n title: ").append('"').append(graphName).append('"').append("\n"); + + result.append(""" + display_edge_labels: yes + layoutalgorithm: mindepth //$ "Compilergraph" + manhattan_edges: yes + port_sharing: no + orientation: top_to_bottom + """.indent(2)); + + for (VcgColor color : VcgColor.values()) { + result.append("\n colorentry ").append(color.id()).append(": ").append(color.getRgb()); + } + + result.append("\n"); + + result.append(formatMethod(graphName).indent(2)); + + result.append("}"); + + return result.toString(); + } + + private String formatMethod(String name) { + StringBuilder result = new StringBuilder(); + + result.append("graph: {"); + result.append("\n title: ").append('"').append("method").append('"'); + result.append("\n label: ").append('"').append(name).append('"'); + result.append("\n color: ").append(VcgColor.ROOT_BLOCK.id()); + + for (Entry> entry : this.clusters.entrySet()) { + result.append("\n").append(formatBlock(entry.getKey(), entry.getValue()).indent(2)); + } + + result.append("}"); + + return result.toString(); + } + + private String formatBlock(Block block, Set nodes) { + StringBuilder result = new StringBuilder("graph: {"); + result.append("\n title: " + '"').append(nodeTitle(block)).append('"'); + result.append("\n label: " + '"').append(nodeLabel(block)).append('"'); + result.append("\n status: clustered"); + result.append("\n color: ").append(VcgColor.BLOCK.id()); + result.append("\n"); + + for (Node node : nodes) { + result.append(formatNode(node).indent(2)); + result.append(formatInputEdges(node).indent(2)); + } + result.append(formatControlflowEdges(block)); + + result.append(formatSchedule(block)); + + result.append("\n}"); + + return result.toString(); + } + + private String formatNode(Node node) { + String infoText = "I am an info text for " + node; + + String result = "node: {"; + result += "\n title: " + '"' + nodeTitle(node) + '"' + "\n"; + result += "\n label: " + '"' + nodeLabel(node) + '"' + "\n"; + result += "\n color: " + nodeColor(node).id(); + result += "\n info1: " + '"' + infoText + '"'; + result += "\n}"; + + return result; + } + + private String formatInputEdges(Node node) { + var edges = IntStream.range(0, node.predecessors().size()) + .mapToObj( + idx -> new Edge( + node.predecessor(idx), node, idx, edgeColor(node.predecessor(idx), node) + ) + ) + .toList(); + return formatEdges(edges, "\n priority: 50"); + } + + private Optional edgeColor(Node src, Node dst) { + if (nodeColor(src) != VcgColor.NORMAL) { + return Optional.of(nodeColor(src)); + } + if (nodeColor(dst) != VcgColor.NORMAL) { + return Optional.of(nodeColor(dst)); + } + return Optional.empty(); + } + + private String formatControlflowEdges(Block block) { + StringJoiner result = new StringJoiner("\n"); + List parents = block.predecessors(); + for (Node parent : parents) { + if (parent instanceof ReturnNode) { + // Return needs no label + result.add(formatControlflowEdge(parent, block, "")); + } else { + throw new RuntimeException("Unknown paren type: " + parent); + } + } + + return result.toString(); + } + + private String formatControlflowEdge(Node source, Block dst, String label) { + String result = "edge: {"; + result += "\n sourcename: " + '"' + nodeTitle(source) + '"'; + result += "\n targetname: " + '"' + nodeTitle(dst) + '"'; + result += "\n label: " + '"' + label + '"'; + result += "\n color: " + VcgColor.CONTROL_FLOW.id(); + result += "\n}"; + return result; + } + + private String formatEdges(Collection edges, String additionalProps) { + StringJoiner result = new StringJoiner("\n"); + for (Edge edge : edges) { + StringBuilder inner = new StringBuilder(); + // edge: {sourcename: "n74" targetname: "n71" label: "0" class:14 priority:50 color:blue} + inner.append("edge: {"); + inner.append("\n sourcename: ").append('"').append(nodeTitle(edge.src())).append('"'); + inner.append("\n targetname: ").append('"').append(nodeTitle(edge.dst())).append('"'); + inner.append("\n label: ").append('"').append(edge.index()).append('"'); + edge.color.ifPresent(color -> inner.append("\n color: ").append(color.id())); + inner.append(additionalProps); + inner.append("\n}"); + result.add(inner); + } + + return result.toString(); + } + + private String formatSchedule(Block block) { + // Once you have a schedule, you might want to also emit it :) + return formatEdges(List.of(), "\n color: " + VcgColor.SCHEDULE.id()); + } + + @SuppressWarnings("DuplicateBranchesInSwitch") + private VcgColor nodeColor(Node node) { + return switch (node) { + case BinaryOperationNode _ -> VcgColor.NORMAL; + case Block _ -> VcgColor.NORMAL; + case ConstIntNode _ -> VcgColor.NORMAL; + case Phi _ -> VcgColor.PHI; + case ProjNode proj -> { + if (proj.projectionInfo() == SimpleProjectionInfo.SIDE_EFFECT) { + yield VcgColor.MEMORY; + } else if (proj.projectionInfo() == SimpleProjectionInfo.RESULT) { + yield VcgColor.NORMAL; + } else { + yield VcgColor.NORMAL; + } + } + case ReturnNode _ -> VcgColor.CONTROL_FLOW; + case StartNode _ -> VcgColor.CONTROL_FLOW; + }; + } + + private String nodeTitle(Node node) { + if (node instanceof Block block) { + if (block == this.graph.startBlock()) { + return "start-block"; + } else if (block == this.graph.endBlock()) { + return "end-block"; + } + return "block-" + idFor(block); + } + return "node-" + idFor(node); + } + + private String nodeLabel(Node node) { + if (node == this.graph.startBlock()) { + return "start-block"; + } else if (node == this.graph.endBlock()) { + return "end-block"; + } + return node.toString(); + } + + private int idFor(Node node) { + if (node instanceof Block block) { + return this.ids.computeIfAbsent(block, _ -> this.blockCounter++); + } + return this.ids.computeIfAbsent(node, _ -> this.nodeCounter++); + } + + private record Edge(Node src, Node dst, int index, Optional color) { + + } + + private enum VcgColor { + // colorentry 100: 204 204 204 gray + // colorentry 101: 222 239 234 faint green + // colorentry 103: 242 242 242 white-ish + // colorentry 104: 153 255 153 light green + // colorentry 105: 153 153 255 blue + // colorentry 106: 255 153 153 red + // colorentry 107: 255 255 153 yellow + // colorentry 108: 255 153 255 pink + // colorentry 110: 127 127 127 dark gray + // colorentry 111: 153 255 153 light green + // colorentry 114: 153 153 255 blue + CONTROL_FLOW("255 153 153"), + MEMORY("153 153 255"), + NORMAL("242 242 242"), + SPECIAL("255 153 255"), + CONST("255 255 153"), + PHI("153 255 153"), + ROOT_BLOCK("204 204 204"), + BLOCK("222 239 234"), + SCHEDULE("255 153 255"); + + private final String rgb; + + VcgColor(String rgb) { + this.rgb = rgb; + } + + public String getRgb() { + return rgb; + } + + public int id() { + return 100 + ordinal(); + } + } +}