Introduce yComp printer

This commit is contained in:
SirYwell 2025-05-10 07:14:07 +02:00
parent 6b7584b3cc
commit b9d4e06dfc
No known key found for this signature in database
4 changed files with 361 additions and 2 deletions

View file

@ -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 subgraphs, or https://www.yworks.com/yed-live/, which is relatively good at
neighbourhoods and larger layouts) that can visualize that output. neighbourhoods and larger layouts) that can visualize that output.
It allows debugging anything related to the IR. 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. 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. 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 ## Miscellaneous
### Nullability ### Nullability

View file

@ -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.IrGraph;
import edu.kit.kastel.vads.compiler.ir.SsaTranslation; import edu.kit.kastel.vads.compiler.ir.SsaTranslation;
import edu.kit.kastel.vads.compiler.ir.optimize.LocalValueNumbering; 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.lexer.Lexer;
import edu.kit.kastel.vads.compiler.parser.ParseException; import edu.kit.kastel.vads.compiler.parser.ParseException;
import edu.kit.kastel.vads.compiler.parser.Parser; 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.parser.ast.ProgramTree;
import edu.kit.kastel.vads.compiler.semantic.SemanticAnalysis; import edu.kit.kastel.vads.compiler.semantic.SemanticAnalysis;
import edu.kit.kastel.vads.compiler.semantic.SemanticException; import edu.kit.kastel.vads.compiler.semantic.SemanticException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -41,6 +41,14 @@ public class Main {
graphs.add(translation.translate()); 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 // TODO: generate assembly and invoke gcc instead of generating abstract assembly
String s = new CodeGenerator().generateCode(graphs); String s = new CodeGenerator().generateCode(graphs);
Files.writeString(output, s); Files.writeString(output, s);
@ -58,4 +66,11 @@ public class Main {
throw new AssertionError("unreachable"); throw new AssertionError("unreachable");
} }
} }
private static void dumpGraph(IrGraph graph, Path path, String key) throws IOException {
Files.writeString(
path.resolve(graph.name() + "-" + key + ".vcg"),
YCompPrinter.print(graph)
);
}
} }

View file

@ -14,6 +14,10 @@ public final class ProjNode extends Node {
return this.projectionInfo.toString(); return this.projectionInfo.toString();
} }
public ProjectionInfo projectionInfo() {
return projectionInfo;
}
public sealed interface ProjectionInfo { public sealed interface ProjectionInfo {
} }

View file

@ -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<Block, Set<Node>> clusters = new HashMap<>();
private final Map<Node, Integer> 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<Node> 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<Block, Set<Node>> 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<Node> 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<VcgColor> 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<? extends Node> 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<Edge> 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<VcgColor> 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();
}
}
}