1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-06-02 00:41:42 +00:00

- Visualization of the Control Flow Graph in Mermaid syntax.

- Added new option -vmermaid, which creates in the output directory .mmd files containing the Mermaid CFGs generated at the different stages of the compilation.
- Use the mermaid plug-in of IntelliJ to view those files.
- Added -vnoasm to stop verbosing asembler output.

Currently in DRAFT, but functional.
This commit is contained in:
Sven Van de Velde 2023-11-28 19:04:28 +01:00
parent ff29404f24
commit 3b571bbe6a
7 changed files with 232 additions and 29 deletions

View File

@ -27,6 +27,12 @@ public class CompileLog {
*/
private boolean verboseAsmOptimize = false;
/**
* Should ASM output be verbose.
*/
private boolean verboseAsm = true;
/**
* Should SSA optimization be verbose.
*/
@ -65,7 +71,8 @@ public class CompileLog {
/**
* Should the creation of the SSA be verbose.
*/
private boolean verboseCreateSsa = false;
private boolean verboseSsaMermaid = false;
/**
* Output information about struct unwinding
@ -234,6 +241,11 @@ public class CompileLog {
this.verboseCreateSsa = verboseCreateSsa;
}
public CompileLog verboseSsaMermaid() {
setVerboseSsaMermaid(true);
return this;
}
public boolean isVerboseUplift() {
return verboseUplift;
}
@ -273,6 +285,10 @@ public class CompileLog {
return this;
}
public boolean isVerboseAsm() { return verboseAsm; }
public void setVerboseAsm(boolean verboseAsm) { this.verboseAsm = verboseAsm; }
public boolean isVerboseAsmOptimize() {
return verboseAsmOptimize;
}
@ -335,4 +351,14 @@ public class CompileLog {
public boolean isVerboseComments() {
return verboseComments;
}
private boolean verboseCreateSsa = false;
public boolean isVerboseSsaMermaid() {
return verboseSsaMermaid;
}
public void setVerboseSsaMermaid(boolean verboseSsaMermaid) {
this.verboseSsaMermaid = verboseSsaMermaid;
}
}

View File

@ -220,6 +220,8 @@ public class Compiler {
new Pass1GenerateControlFlowGraph(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("FIRST CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-after-parsing");
getLog().append(program.prettyControlFlowGraph());
}
new Pass1ResolveForwardReferences(program).execute();
@ -249,13 +251,17 @@ public class Compiler {
new Pass1AssertNoLValueIntermediate(program).execute();
new PassNAddTypeConversionAssignment(program, true).execute();
new Pass1AddressOfHandling(program).execute();
new Pass1AsmUsesHandling(program).execute();
new Pass1AssertProcedureCallParameters(program).execute();
new Pass1ModifiedVarsAnalysis(program).execute();
new Pass1CallStackVarPrepare(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("CONTROL FLOW GRAPH BEFORE SIZEOF FIX");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-before-sizeof-fix");
getLog().append(program.prettyControlFlowGraph());
}
@ -270,6 +276,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("CONTROL FLOW GRAPH AFTER UNWIND");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-after-unwind");
getLog().append(program.prettyControlFlowGraph());
}
@ -283,6 +291,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("CONTROL FLOW GRAPH BEFORE INLINING");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-before-inlining");
getLog().append(program.prettyControlFlowGraph());
}
new Pass1ProcedureInline(program).execute();
@ -293,6 +303,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("INITIAL CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-initial");
getLog().append(program.prettyControlFlowGraph());
}
@ -314,6 +326,8 @@ public class Compiler {
new Pass1CallStackVarConvert(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("PROCEDURE CALLS");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-procedure-calls");
getLog().append(program.prettyControlFlowGraph());
}
new Pass1CallStack(program).execute();
@ -321,6 +335,8 @@ public class Compiler {
new Pass1CallPhiParameters(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("PROCEDURE PARAMETERS");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-procedure-parameters");
getLog().append(program.prettyControlFlowGraph());
}
new PassNUnwindLValueLists(program).execute();
@ -334,6 +350,8 @@ public class Compiler {
getLog().append("\nCONTROL FLOW GRAPH SSA");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass1-cfg-final-ssa");
getLog().append(program.prettyControlFlowGraph());
getLog().append("SYMBOL TABLE SSA");
@ -465,6 +483,8 @@ public class Compiler {
if(getLog().isVerboseLoopUnroll()) {
getLog().append("CONTROL FLOW GRAPH BEFORE UNROLLING");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass2-cfg-before-unrolling");
getLog().append(program.prettyControlFlowGraph());
}
@ -474,6 +494,8 @@ public class Compiler {
if(unrolled) {
if(getLog().isVerboseLoopUnroll()) {
getLog().append("UNROLLED CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass2-cfg-after-unrolling");
getLog().append(program.prettyControlFlowGraph());
}
pass2Optimize();
@ -560,6 +582,8 @@ public class Compiler {
getLog().append("Successful SSA optimization " + optimization.getClass().getSimpleName() + "");
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass-2-cfg-ssa-" + optimization.getClass().getSimpleName());
getLog().append(program.prettyControlFlowGraph());
}
}
@ -604,16 +628,22 @@ public class Compiler {
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass3x-cfg-optimized");
getLog().append(program.prettyControlFlowGraph());
}
new PassNCullEmptyBlocks(program, false).step();
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass3-cfg-empty-blocks-culled");
getLog().append(program.prettyControlFlowGraph());
}
new PassNRenumberLabels(program, false).execute();
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass3-cfg-labels-renumbered");
getLog().append(program.prettyControlFlowGraph());
}
new PassNBlockSequencePlanner(program).step();
@ -637,6 +667,8 @@ public class Compiler {
program.getLiveRangeVariablesEffective();
getLog().append("\nFINAL CONTROL FLOW GRAPH");
if(getLog().isVerboseSsaMermaid())
program.toFileMermaidControlFlowGraph("pass3-cfg-final");
getLog().append(program.prettyControlFlowGraph());
}
@ -747,8 +779,10 @@ public class Compiler {
private void pass5GenerateAndOptimizeAsm() {
getLog().append("\nASSEMBLER BEFORE OPTIMIZATION");
getLog().append(program.getAsm().toString(new AsmProgram.AsmPrintState(true), program));
if(getLog().isVerboseAsm()) {
getLog().append("\nASSEMBLER BEFORE OPTIMIZATION");
getLog().append(program.getAsm().toString(new AsmProgram.AsmPrintState(true), program));
}
getLog().append("ASSEMBLER OPTIMIZATIONS");
List<Pass5AsmOptimization> pass5Optimizations = new ArrayList<>();
@ -769,8 +803,10 @@ public class Compiler {
getLog().append("Succesful ASM optimization " + optimization.getClass().getSimpleName());
asmOptimized = true;
if(getLog().isVerboseAsmOptimize()) {
getLog().append("ASSEMBLER");
getLog().append(program.getAsm().toString());
if(getLog().isVerboseAsm()) {
getLog().append("ASSEMBLER");
getLog().append(program.getAsm().toString());
}
}
}
}
@ -786,9 +822,11 @@ public class Compiler {
getLog().append("\nFINAL SYMBOL TABLE");
getLog().append(program.getScope().toStringVars(program, false));
getLog().append("\nFINAL ASSEMBLER");
getLog().append("Score: " + Pass4RegisterUpliftCombinations.getAsmScore(program) + "\n");
getLog().append(program.getAsm().toString(new AsmProgram.AsmPrintState(false, true, true, false), program));
if(getLog().isVerboseAsm()) {
getLog().append("\nFINAL ASSEMBLER");
getLog().append("Score: " + Pass4RegisterUpliftCombinations.getAsmScore(program) + "\n");
getLog().append(program.getAsm().toString(new AsmProgram.AsmPrintState(false, true, true, false), program));
}
}

View File

@ -114,6 +114,9 @@ public class KickC implements Callable<Integer> {
@CommandLine.Option(names = {"-v"}, description = "Verbose output describing the compilation process")
private boolean verbose = false;
@CommandLine.Option(names = {"-vnoasm"}, description = "Verbosity Option. Don't show any generated assembler during compilation.")
private boolean verboseAsm = false;
@CommandLine.Option(names = {"-vasmout"}, description = "Verbosity Option. Show KickAssembler standard output during compilation.")
private boolean verboseAsmOut = false;
@ -123,6 +126,9 @@ public class KickC implements Callable<Integer> {
@CommandLine.Option(names = {"-vcreate"}, description = "Verbosity Option. Creation of the Single Static Assignment Control Flow Graph.")
private boolean verboseCreateSsa = false;
@CommandLine.Option(names = {"-vmermaid"}, description = "Verbosity Option. Visualize the Single Static Assignment Control Flow Graph in Mermaid.")
private boolean verboseCreateSsaMermaid = false;
@CommandLine.Option(names = {"-voptimize"}, description = "Verbosity Option. Control Flow Graph Optimization.")
private boolean verboseSSAOptimize = false;
@ -499,34 +505,25 @@ public class KickC implements Callable<Integer> {
// Execute the binary file if instructed
if(emulator != null) {
List<String> emulatorCommand = new ArrayList<>();
emulatorCommand.addAll(Arrays.asList(emulator.split(" ")));
// Find commandline options for the emulator
if( emulator.equals("C64Debugger") ) {
String emuOptions = "";
if(emulator.equals("C64Debugger")) {
Path viceSymbolsPath = program.getOutputFileManager().getOutputFile("vs");
emulatorCommand.add("-symbol");
emulatorCommand.add(viceSymbolsPath.toString());
emulatorCommand.add("-autojmp");
emulatorCommand.add("-prg");
emuOptions = "-symbols " + viceSymbolsPath + " -autojmp -prg ";
}
// The program names used by VICE emulators
List<String> viceEmus = Arrays.asList("x64", "x64sc", "x128", "x64dtv", "xcbm2", "xcbm5x0", "xpet", "xplus4", "xscpu64", "xvic");
if(viceEmus.contains(emulator)) {
Path viceSymbolsPath = program.getOutputFileManager().getOutputFile("vs");
emulatorCommand.add("-moncommands");
emulatorCommand.add(viceSymbolsPath.toAbsolutePath().toString());
emuOptions = "-moncommands " + viceSymbolsPath.toAbsolutePath().toString() + " ";
}
emulatorCommand.add(outputBinaryFilePath.toAbsolutePath().toString());
System.out.println("Executing " + outputBinaryFilePath + " using " + emulator);
String executeCommand = emulator + " " + emuOptions + outputBinaryFilePath.toAbsolutePath().toString();
if(verbose) {
System.out.println("Executing command: " + String.join(" ", emulatorCommand));
System.out.println("Executing command: " + executeCommand);
}
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(emulatorCommand);
processBuilder.inheritIO();
Process process = processBuilder.start();
Process process = Runtime.getRuntime().exec(executeCommand);
process.waitFor();
} catch(Throwable e) {
System.err.println("Executing emulator failed! " + e.getMessage());
@ -554,6 +551,10 @@ public class KickC implements Callable<Integer> {
compiler.getLog().setVerboseCreateSsa(true);
compiler.getLog().setSysOut(true);
}
if(verboseCreateSsaMermaid) {
compiler.getLog().setVerboseSsaMermaid(true);
compiler.getLog().setSysOut(true);
}
if(verboseSSAOptimize) {
compiler.getLog().setVerboseSSAOptimize(true);
compiler.getLog().setSysOut(true);
@ -594,6 +595,10 @@ public class KickC implements Callable<Integer> {
compiler.getLog().setVerboseAsmOptimize(true);
compiler.getLog().setSysOut(true);
}
if(verboseAsm) {
compiler.getLog().setVerboseAsm(false);
compiler.getLog().setSysOut(true);
}
if(verboseFixLongBranch) {
compiler.getLog().setVerboseFixLongBranch(true);
compiler.getLog().setSysOut(true);

View File

@ -129,6 +129,20 @@ public class OutputFileManager {
return outputFile.normalize();
}
/**
* Get the extended output file name in a specific directory with a name and specific extension
*
* @return The output file.
*/
public Path getMermaidOutputFile(String name, String extension) {
Path outputDir = getOutputDirectory();
String fileName = getOutputBaseName();
fileName += "-" + name;
if(extension.length() > 0) fileName += "." + extension;
final Path outputFile = outputDir.resolve(fileName);
return outputFile.normalize();
}
/**
* Get the output directory
* <i>Output-path</i> is the directory where the output files are generated. The following is a prioritized list specifying how the compiler finds this folder:

View File

@ -5,10 +5,7 @@ import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.LabelRef;
import dk.camelot64.kickc.model.values.ScopeRef;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.*;
/**
* A named/labelled sequence of SSA statements connected to other basic blocks.
@ -191,6 +188,67 @@ public class ControlFlowBlock implements Graph.Block {
return out.toString();
}
private String mermaidString(String label) {
return label.replace(":","_").replace("@","_");
}
public String toMermaid(Program program, Graph graph, HashSet<String> mermaidFlow) {
StringBuilder out = new StringBuilder();
StringBuilder outStatements = new StringBuilder();
if(graph != null) {
List<Graph.Block> predecessors = program.getGraph().getPredecessors(this);
if(predecessors.size() > 0) {
for(Graph.Block predecessor : predecessors) {
String flow = mermaidString(predecessor.getLabel().getFullName()) +
"-->" +
mermaidString(this.getLabel().getFullName()) +
"\n";
mermaidFlow.add(flow);
if (program.isProcedureEntry(this)) {
flow = mermaidString(this.getLabel().getLocalName() + "___return") +
"-->" +
mermaidString(predecessor.getLabel().getFullName()) +
"\n";
mermaidFlow.add(flow);
}
}
}
} else {
mermaidFlow.add(" @UNKNOWN");
}
outStatements.append("\n");
outStatements.append(mermaidString(this.getLabel().getFullName())).append("[\"`");
outStatements.append("**" + this.getLabel().getFullName() + "**");
outStatements.append("\n");
int index = 0;
if(!statements.isEmpty()) {
for (Statement statement : statements) {
// String statementString = statement.toString(program, program.getLog().isVerboseLiveRanges());
if(statement instanceof StatementCall) {
// outStatements.append("[").append(index).append("] ");
outStatements.append(statement.toString(program, program.getLog().isVerboseLiveRanges()));
outStatements.append("\n");
}
index++;
}
} else {
outStatements.append(" <-> ");
}
outStatements.append("`\"]\n");
// if(defaultSuccessor != null) {
// String flow = mermaidString(this.getLabel().getFullName()) +
// "-->" +
// mermaidString(defaultSuccessor.getFullName()) +
// " \n";
// mermaidFlow.add(flow);
// }
out.append(outStatements);
return out.toString();
}
@Override
public boolean equals(Object o) {
if(this == o) return true;

View File

@ -2,6 +2,7 @@ package dk.camelot64.kickc.model;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementPhiBlock;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.LabelRef;
import dk.camelot64.kickc.model.values.ScopeRef;
@ -102,6 +103,21 @@ public interface Graph {
return out.toString();
}
default String toMermaid(ProcedureCompilation procedureCompilation, Program program) {
StringBuilder mermaid = new StringBuilder();
Procedure procedure = (Procedure) program.getScope().getProcedure(procedureCompilation.getProcedureRef());
mermaid.append("\nsubgraph \"").append(procedure.toString(program)).append("\"\n");
HashSet<String> flow = new HashSet<String>();
for(Graph.Block block : getAllBlocks()) {
mermaid.append(block.toMermaid(program, this, flow));
}
mermaid.append("\nend\n");
for(String edge : flow) {
mermaid.append(edge);
}
return mermaid.toString();
}
interface Block {
LabelRef getLabel();
@ -156,5 +172,7 @@ public interface Graph {
void addStatementBeforeCall(Statement newStatement);
String toString(Program program, Graph graph);
String toMermaid(Program program, Graph graph, HashSet<String> flow);
}
}

View File

@ -12,6 +12,8 @@ import dk.camelot64.kickc.model.values.ScopeRef;
import dk.camelot64.kickc.passes.calcs.*;
import dk.camelot64.kickc.passes.utils.ProcedureUtils;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Path;
import java.util.*;
@ -181,11 +183,53 @@ public class Program {
public String prettyControlFlowGraph() {
StringBuilder graphPretty = new StringBuilder();
for(ProcedureCompilation procedureCompilation : getProcedureCompilations()) {
graphPretty.append(procedureCompilation.getGraph().toString(this));
Graph graph = procedureCompilation.getGraph();
if(graph != null)
graphPretty.append(graph.toString(this));
}
return graphPretty.toString();
}
/**
* Mermaid-print the entire control flow graph of all procedures.
* @return The mermaid-printed control flow graph
*/
public String mermaidControlFlowGraph() {
StringBuilder mermaid = new StringBuilder();
// mermaid.append("```mermaid\n");
mermaid.append("---\n");
mermaid.append("config:\n");
mermaid.append(" theme: dark\n");
mermaid.append(" flowchart:\n");
mermaid.append(" wrappingWidth: 600\n");
mermaid.append("---\n");
mermaid.append("graph TD\n");
for(ProcedureCompilation procedureCompilation : getProcedureCompilations()) {
Graph graph = procedureCompilation.getGraph();
if(graph != null) {
mermaid.append(graph.toMermaid(procedureCompilation,this));
}
}
return mermaid.toString();
}
public void toFileMermaidControlFlowGraph(String pass) {
Path mermaidPath = getOutputFileManager().getMermaidOutputFile(pass, "mmd");
System.out.println("Writing mermaid file " + mermaidPath);
try {
FileOutputStream mermaidOutputStream = new FileOutputStream(mermaidPath.toFile());
OutputStreamWriter mermaidWriter = new OutputStreamWriter(mermaidOutputStream);
String mermaidDiagramString = mermaidControlFlowGraph();
if(mermaidDiagramString != null) {
mermaidWriter.write(mermaidDiagramString);
}
mermaidWriter.close();
mermaidOutputStream.close();
} catch(Exception e) {
throw new InternalError("Cannot open " + mermaidPath, e);
}
}
public OutputFileManager getOutputFileManager() {
return outputFileManager;
}