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

Compare commits

...

8 Commits

Author SHA1 Message Date
Sven Van de Velde
8d6d5e06d8 Merge branch 'master-mermaid' into 'master'
Draft: #830 - Generation of various reports in markdown, with embedded graphics in mermaid syntax

See merge request camelot/kickc!55
2023-12-31 20:28:55 +00:00
Sven Van de Velde
137053f093 - Added a register interference graph (RIG) per procedure, showing the alive variables interfering with each other, as each edge is an interference.
- Activate with option -rrig or -reportrig.
2023-12-04 17:32:37 +01:00
Sven Van de Velde
6f0caee5fa - Add mermaid and markdown files as part of .gitignore 2023-12-04 07:42:38 +01:00
Sven Van de Velde
efc9ed5fab - Added coalesce example if.c to test equivalence classes and live range analysis. 2023-12-03 21:44:27 +01:00
Sven Van de Velde
7b856a0f74 - Added MermaidGraph class for mail flow charts generation. 2023-12-03 21:42:42 +01:00
Sven Van de Velde
f0dc36b205 Documentation of classes and structural improvements. 2023-12-03 13:03:15 +01:00
Sven Van de Velde
8359ffde8a Control Block Flow Graph
- Working version
- Added option -rcfg to generate .md files, which are markdown files showing for each procedure the CFG.
- Modelled the report generation classes.

Just compile using -rcfg and it will work.
2023-12-02 14:51:57 +01:00
Sven Van de Velde
3b571bbe6a - 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.
2023-11-28 19:04:28 +01:00
21 changed files with 1080 additions and 35 deletions

5
.gitignore vendored
View File

@ -6,6 +6,10 @@
*/*.brk
*/*.prg
*/*.sym
*/src/*.md
*/src/*.mmd
*/src/*.mermaid
*/src/*.markdown
*/.tmpdirs
*/bin/
**/.DS_Store
@ -15,6 +19,7 @@ kickc.iml
**/.vscode/*
**/.vscode/*.log
**/target/*
**/.target/*
*/workspace.xml
/gen/*
/src/main/fragment/cache

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.
*/
@ -62,10 +68,6 @@ public class CompileLog {
*/
private boolean verboseParse = false;
/**
* Should the creation of the SSA be verbose.
*/
private boolean verboseCreateSsa = false;
/**
* Output information about struct unwinding
@ -95,6 +97,24 @@ public class CompileLog {
*/
private boolean sysOut = false;
/**
* Create .mmd files containing the Control Block Flow Graph in mermaid.
*/
private boolean reportsCFGMermaid = false;
/**
* Create .mmd files containing the Call Graph in mermaid.
*/
private boolean reportsCGMermaid = false;
/**
* Create .md files containing the Register Interference Graph of the alive
* variables in mermaid. The RIG is input for the register coalesce.
*/
private boolean reportsRIGMermaid = false;
public CompileLog() {
this.log = new StringBuilder();
}
@ -273,6 +293,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 +359,30 @@ public class CompileLog {
public boolean isVerboseComments() {
return verboseComments;
}
private boolean verboseCreateSsa = false;
public boolean isReportsCFGMermaid() {
return reportsCFGMermaid;
}
public void setReportsCFGMermaid(boolean reportsCFGMermaid) {
this.reportsCFGMermaid = reportsCFGMermaid;
}
public boolean isReportsCGMermaid() {
return reportsCGMermaid;
}
public void setReportsCGMermaid(boolean reportsCGMermaid) {
this.reportsCGMermaid = reportsCGMermaid;
}
public boolean isReportsRIGMermaid() {
return reportsRIGMermaid;
}
public void setReportsRIGMermaid(boolean reportsRIGMermaid) {
this.reportsRIGMermaid = reportsRIGMermaid;
}
}

View File

@ -10,6 +10,7 @@ import dk.camelot64.kickc.model.values.StringEncoding;
import dk.camelot64.kickc.parser.CParser;
import dk.camelot64.kickc.parser.KickCParser;
import dk.camelot64.kickc.passes.*;
import dk.camelot64.kickc.passes.reports.*;
import dk.camelot64.kickc.preprocessor.CPreprocessor;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenSource;
@ -220,6 +221,8 @@ public class Compiler {
new Pass1GenerateControlFlowGraph(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("FIRST CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-after-parsing", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new Pass1ResolveForwardReferences(program).execute();
@ -249,13 +252,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().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-before-sizeof-fix", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
@ -270,6 +277,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("CONTROL FLOW GRAPH AFTER UNWIND");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program,"pass1-cfg-after-unwind", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
@ -283,6 +292,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("CONTROL FLOW GRAPH BEFORE INLINING");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-before-inlining", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new Pass1ProcedureInline(program).execute();
@ -293,6 +304,8 @@ public class Compiler {
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("INITIAL CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-initial", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
@ -314,6 +327,8 @@ public class Compiler {
new Pass1CallStackVarConvert(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("PROCEDURE CALLS");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-procedure-calls", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new Pass1CallStack(program).execute();
@ -321,6 +336,8 @@ public class Compiler {
new Pass1CallPhiParameters(program).execute();
if(getLog().isVerbosePass1CreateSsa()) {
getLog().append("PROCEDURE PARAMETERS");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-procedure-parameters", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new PassNUnwindLValueLists(program).execute();
@ -334,6 +351,8 @@ public class Compiler {
getLog().append("\nCONTROL FLOW GRAPH SSA");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass1-cfg-final-ssa", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
getLog().append("SYMBOL TABLE SSA");
@ -465,6 +484,8 @@ public class Compiler {
if(getLog().isVerboseLoopUnroll()) {
getLog().append("CONTROL FLOW GRAPH BEFORE UNROLLING");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass2-cfg-before-unrolling", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
@ -474,6 +495,8 @@ public class Compiler {
if(unrolled) {
if(getLog().isVerboseLoopUnroll()) {
getLog().append("UNROLLED CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass2-cfg-after-unrolling", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
pass2Optimize();
@ -560,6 +583,8 @@ public class Compiler {
getLog().append("Successful SSA optimization " + optimization.getClass().getSimpleName() + "");
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass-2-cfg-ssa-" + optimization.getClass().getSimpleName(), "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
}
@ -604,16 +629,22 @@ public class Compiler {
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass3x-cfg-optimized", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new PassNCullEmptyBlocks(program, false).step();
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass3-cfg-empty-blocks-culled", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new PassNRenumberLabels(program, false).execute();
if(getLog().isVerboseSSAOptimize()) {
getLog().append("CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass3-cfg-labels-renumbered", "md").generateReport();
getLog().append(program.prettyControlFlowGraph());
}
new PassNBlockSequencePlanner(program).step();
@ -637,6 +668,11 @@ public class Compiler {
program.getLiveRangeVariablesEffective();
getLog().append("\nFINAL CONTROL FLOW GRAPH");
if(getLog().isReportsCFGMermaid())
new ReportCFG(program, "pass3-cfg-final", "md").generateReport();
if(getLog().isReportsCGMermaid()) {
new ReportCG(program, "pass3-cg-final", "md").generateReport();
}
getLog().append(program.prettyControlFlowGraph());
}
@ -717,6 +753,8 @@ public class Compiler {
new Pass4RegisterUpliftRemains(program).performUplift(upliftCombinations);
}
if(getLog().isReportsRIGMermaid())
new ReportRIG(program, "pass4-rig", "md").generateReport();
// Register coalesce on assignment (saving bytes & cycles)
new Pass4MemoryCoalesceAssignment(program).coalesce();
@ -747,8 +785,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 +809,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 +828,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,7 @@ 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 = {"-voptimize"}, description = "Verbosity Option. Control Flow Graph Optimization.")
private boolean verboseSSAOptimize = false;
@ -156,6 +160,15 @@ public class KickC implements Callable<Integer> {
@CommandLine.Option(names = {"-vfixlongbranch"}, description = "Verbosity Option. Fix Long ASM branches.")
private boolean verboseFixLongBranch = false;
@CommandLine.Option(names = {"-rcfg", "-reportcfg"}, description = "Report Option. Visualize the Control block Flow Graph in Mermaid.")
private boolean reportCFGMermaid = false;
@CommandLine.Option(names = {"-rcg", "-reportcg"}, description = "Report Option. Visualize the Call Graph between the different functions in Mermaid.")
private boolean reportCGMermaid = false;
@CommandLine.Option(names = {"-rrig", "-reportrig"}, description = "Report Option. Visualize the Register Interference Graph between the different alive variables per procedure in Mermaid.")
private boolean reportRIGMermaid = false;
@CommandLine.Option(names = {"-Wfragment"}, description = "Warning Option. Missing fragments produces a warning instead of an error.")
private boolean warnFragmentMissing = false;
@ -192,6 +205,7 @@ public class KickC implements Callable<Integer> {
@CommandLine.Option(names = {"-calling"}, description = "Configure calling convention. Default is __phicall. See #pragma calling")
private String calling = null;
/** Program Exit Code signaling a compile error. */
public static final int COMPILE_ERROR = CommandLine.ExitCode.SOFTWARE;
@ -499,34 +513,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());
@ -594,11 +599,26 @@ 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);
}
if(reportCFGMermaid) {
compiler.getLog().setReportsCFGMermaid(true);
compiler.getLog().setSysOut(true);
}
if(reportCGMermaid) {
compiler.getLog().setReportsCGMermaid(true);
compiler.getLog().setSysOut(true);
}
if(reportRIGMermaid) {
compiler.getLog().setReportsRIGMermaid(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,7 @@ public class ControlFlowBlock implements Graph.Block {
return out.toString();
}
@Override
public boolean equals(Object o) {
if(this == o) return true;

View File

@ -33,6 +33,17 @@ public class LiveRange {
return s;
}
public String toString(int maxIndex) {
StringBuilder range = new StringBuilder();
range.append(" ").append(String.format("%4d", size())).append(" : ");
StringBuilder canvas = new StringBuilder(" ".repeat(maxIndex+1));
for(LiveInterval interval : intervals) {
interval.paintOn(canvas);
}
range.append(canvas);
return range.toString();
}
/**
* Get the underlying statement intervals
* @return The intervals
@ -185,6 +196,11 @@ public class LiveRange {
return lastStatementIdx - firstStatementIdx + 1;
}
public void paintOn(StringBuilder canvas) {
for(int i=firstStatementIdx; i<=lastStatementIdx;i++) {
canvas.setCharAt(i, '*');
}
}
}

View File

@ -61,7 +61,7 @@ public class LiveRangeEquivalenceClassSet {
}
/**
* Consolidates two live range equivalence calsses into one.
* Consolidates two live range equivalence classes into one.
* All variables and live ranges from the other class is added to the first one - and the other one is deleted.
*
* @param equivalenceClass The first live range equivalence class.
@ -73,7 +73,7 @@ public class LiveRangeEquivalenceClassSet {
}
/**
* Informs the set that class of a variable has ben set - called by add/remove methods inside LiveRangeEquivalenceClass
* Informs the set that class of a variable has been set - called by add/remove methods inside LiveRangeEquivalenceClass
*
* @param variable The variable
* @param equivalenceClass The class

View File

@ -86,6 +86,20 @@ public class LiveRangeVariables {
return liveRanges.size();
}
/**
* Get the maximum index of all live ranges
* @return The maximum index of all live ranges
*/
public int getMaxIndex() {
int maxIndex = 0;
for(LiveRange liveRange: liveRanges.values()) {
int liveRangeMaxIndex = liveRange.getMaxIndex();
if(maxIndex < liveRangeMaxIndex)
maxIndex = liveRangeMaxIndex;
}
return maxIndex;
}
/**
* Get variable live ranges by statement index. Usable for quick access if you plan to iterate many statements.
* @return variable live ranges by statement index

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,14 @@ 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();
}
public OutputFileManager getOutputFileManager() {
return outputFileManager;
}

View File

@ -68,6 +68,25 @@ public class Pass4LiveRangeEquivalenceClassesFinalize extends Pass2Base {
addToEquivalenceClassSet(variable.getVariableRef(), new ArrayList<>(), liveRangeEquivalenceClassSet);
}
getLog().append("Overlapping live ranges variables");
LiveRangeVariables liveRanges = getProgram().getLiveRangeVariables();
int maxIndex = liveRanges.getMaxIndex();
StringBuilder div = new StringBuilder();
div.append(" ".repeat(8));
for(int i=0; i<=maxIndex; i++) {
if(i % 10 != 0)
div.append("-");
else
div.append("x");
}
getLog().append(div.toString());
for(Variable variable: getProgram().getScope().getAllVariables(true)) {
LiveRange liveRange = liveRanges.getLiveRange(variable.getVariableRef());
if (liveRange != null) {
getLog().append(liveRange.toString(maxIndex) + " : " + variable.toString(getProgram()));
}
}
getLog().append("Complete equivalence classes");
for(LiveRangeEquivalenceClass liveRangeEquivalenceClass : liveRangeEquivalenceClassSet.getEquivalenceClasses()) {
getLog().append(liveRangeEquivalenceClass.toString());

View File

@ -0,0 +1,37 @@
package dk.camelot64.kickc.passes.reports;
/**
* A structure to generate reports expressed in the mermaid syntax for graphics generation.
*/
public abstract class Mermaid {
protected String id;
protected String title;
public Mermaid(String id, String title) {
this.id = id;
this.title = title;
}
public static String toID(String label) {
return label.replace(":","_").replace("@","_");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

View File

@ -0,0 +1,91 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Program;
/**
* Create the flow components for mermaid report generation.
* Flows are interconnecting mermaid nodes or sub graphs.
*/
public class MermaidFlow extends Mermaid{
public enum Type {
FULL, DOTTED, LINED
}
public enum Direction {
UNI, BI
}
private MermaidNode from, to;
private Type type;
private Direction direction;
MermaidFlow(MermaidNode from, MermaidNode to, String title, Type type, Direction direction) {
super(from + "_" + to, title);
this.from = from;
this.to = to;
this.type = type;
this.direction = direction;
}
public String getText(Program program) {
StringBuilder text = new StringBuilder();
text.append(from.getId());
if(direction == Direction.BI) {
text.append("<");
}
switch(type) {
case FULL -> {
String title = getTitle();
if(title != null)
text.append("--").append(getTitle()).append("-->");
else
text.append("-->");
}
case DOTTED -> {
String title = getTitle();
if(title != null)
text.append("-.").append(getTitle()).append(".->");
else
text.append("-.->");
}
}
text.append(to.getId()).append("\n");
return text.toString();
}
public MermaidNode getFrom() {
return from;
}
public void setFrom(MermaidNode from) {
this.from = from;
}
public MermaidNode getTo() {
return to;
}
public void setTo(MermaidNode to) {
this.to = to;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
}

View File

@ -0,0 +1,123 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Program;
import java.util.HashMap;
/**
* Create a flow chart for mermaid report generation.
* Flowcharts contain nodes or sub graphs and flows, and can either be located as a single entity in root, or embedded in other sub graphs.
*/
public class MermaidGraph extends Mermaid {
public enum Direction {
TOP_DOWN ("TD"),
BOTTOM_UP ("BU"),
LEFT_RIGHT("LR"),
RIGHT_LEFT("RL");
private final String direction;
Direction(String direction) {
this.direction = direction;
}
public String toString() {
return this.direction;
}
}
protected Direction direction;
protected HashMap<String, MermaidNode> nodes;
protected HashMap<String, MermaidFlow> flows;
protected HashMap<String, MermaidSubGraph> subGraphs;
public MermaidGraph(Direction direction) {
super(toID(""), "");
this.direction = direction;
this.nodes = new HashMap<String, MermaidNode>();
this.flows = new HashMap<String, MermaidFlow>();
this.subGraphs = new HashMap<String, MermaidSubGraph>();
}
public String getText(Program program) {
StringBuilder text = new StringBuilder();
// graph with direction
text.append("graph ").append(this.direction.toString()).append("\n");
// sub graphs
for(MermaidSubGraph subGraph : getSubGraphs().values()) {
text.append(subGraph.getText(program));
}
// nodes of the graph
for(MermaidNode node : nodes.values()) {
text.append(node.getText(program));
}
// flows to and from nodes
for(MermaidFlow flow : flows.values()) {
text.append(flow.getText(program));
}
return text.toString();
}
public HashMap<String, MermaidNode> getNodes() {
return nodes;
}
public void setNodes(HashMap<String, MermaidNode> nodes) {
this.nodes = nodes;
}
public MermaidNode addNode(MermaidNode node) {
this.nodes.putIfAbsent(node.id, node);
return node;
}
public MermaidNode addNode(String nodeName) {
MermaidNode node = new MermaidNode(nodeName);
this.nodes.putIfAbsent(node.id, node);
return node;
}
public HashMap<String, MermaidFlow> getFlows() {
return flows;
}
public void setFlows(HashMap<String, MermaidFlow> flows) {
this.flows = flows;
}
public void addFlow(MermaidFlow flow) {
this.flows.put(flow.getFrom().getId() + "::" + flow.getTo().getId(), flow);
}
public void addFlow(MermaidNode from, MermaidNode to, String title, MermaidFlow.Type type, MermaidFlow.Direction direction) {
MermaidFlow flow = new MermaidFlow(from, to, title, type, direction);
this.flows.put(from.getId() + "::" + to.getId(), flow);
}
public MermaidFlow getFlow(MermaidNode from, MermaidNode to) {
return this.flows.get(from.getId() + "::" + to.getId());
}
public void removeFlow(MermaidFlow flow) {
this.flows.remove(flow.getFrom().getId() + "::" + flow.getTo().getId());
}
public HashMap<String, MermaidSubGraph> getSubGraphs() {
return subGraphs;
}
public void setSubGraphs(HashMap<String, MermaidSubGraph> subGraphs) {
this.subGraphs = subGraphs;
}
public MermaidSubGraph addProcedureSubGraph(MermaidSubGraph subGraph) {
this.subGraphs.putIfAbsent(subGraph.id, subGraph);
return subGraph;
}
}

View File

@ -0,0 +1,137 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementCall;
import dk.camelot64.kickc.model.statements.StatementConditionalJump;
import javax.swing.text.AbstractDocument;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
/**
* Create nodes for mermaid report generation.
* Nodes are the final element in sub graphs or root.
* They are expressed with an Id and a Title. The Id represents the
* key to be interconnected through flows, while the Title is displayed
* inside the graphics box. The title is expressed in markdown.
* Each node can have statements, which is a list of Statements and are
* shown in the graphics box expressed in markdown.
*/
public class MermaidNode extends Mermaid {
public MermaidNode(String title) {
super(toID(title), title);
this.type = Type.SEQUENCE;
this.statements = new LinkedList<Statement>();
this.flows = new HashSet<MermaidFlow>();
}
public MermaidNode(String id, String title) {
super(id, title);
this.type = Type.SEQUENCE;
this.statements = new LinkedList<Statement>();
this.flows = new HashSet<MermaidFlow>();
}
public enum Type { SEQUENCE, CALL, BRANCH }
private Type type;
protected HashSet<MermaidFlow> flows;
private List<Statement> statements;
public List<Statement> getStatements() {
return statements;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public void setStatements(List<Statement> statements) {
this.statements = statements;
if(!statements.isEmpty()) {
Statement lastStatement = this.statements.get(this.statements.size() - 1);
if (lastStatement instanceof StatementCall) {
type = Type.CALL;
} else if (lastStatement instanceof StatementConditionalJump) {
type = Type.BRANCH;
} else {
type = Type.SEQUENCE;
}
} else {
type = Type.SEQUENCE;
}
}
public String toStringStatements() {
StringBuilder stmtString = new StringBuilder();
for(Statement statement : getStatements()) {
stmtString.append(statement).append("\n");
}
return stmtString.toString();
}
public String getText(Program program) {
StringBuilder text = new StringBuilder();
text.append(getId());
switch(getType()) {
case SEQUENCE -> {
text.append("[\"`");
}
case CALL -> {
text.append("[[\"`");
}
case BRANCH -> {
text.append("{{\"`");
}
}
text.append("**").append(getTitle()).append("**").append("\n\n");
for(Statement statement: getStatements()) {
text.append(statement.toString(program, program.getLog().isVerboseLiveRanges())).append("\n");
}
switch(getType()) {
case SEQUENCE -> {
text.append("`\"]\n");
}
case CALL -> {
text.append("`\"]]\n");
}
case BRANCH -> {
text.append("`\"}}\n");
}
}
return text.toString();
}
public HashSet<MermaidFlow> getFlows() {
return flows;
}
public void setFlows(HashSet<MermaidFlow> flows) {
this.flows = flows;
}
public void addFlow(MermaidFlow flow) {
this.flows.add(flow);
}
public void addFlow(MermaidNode from, MermaidNode to, String title, MermaidFlow.Type type, MermaidFlow.Direction direction) {
MermaidFlow flow = new MermaidFlow(from, to, title, type, direction);
this.flows.add(flow);
}
}

View File

@ -0,0 +1,111 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Program;
import java.util.HashMap;
import java.util.HashSet;
/**
* Create sub graphics for mermaid report generation.
* Sub graphs contain nodes, and can either be located as a single entity in root, or embedded in other sub graphs.
* A sub graph models dependencies to other sub graphs.
*/
public class MermaidSubGraph extends Mermaid {
protected HashMap<String, MermaidNode> nodes;
protected HashSet<MermaidFlow> flows;
protected HashMap<String, MermaidSubGraph> procedureSubGraphs;
public MermaidSubGraph(String title) {
super(toID(title), title);
this.nodes = new HashMap<String, MermaidNode>();
this.flows = new HashSet<MermaidFlow>();
this.procedureSubGraphs = new HashMap<String, MermaidSubGraph>();
}
public MermaidSubGraph(String id, String title) {
super(id, title);
this.nodes = new HashMap<String, MermaidNode>();
this.flows = new HashSet<MermaidFlow>();
this.procedureSubGraphs = new HashMap<String, MermaidSubGraph>();
}
public String getText(Program program) {
StringBuilder text = new StringBuilder();
// subgraph and nodes
text.append("subgraph ").append("sg_" + id).append("[\"").append(title).append("\"]\n");
for(MermaidNode node : nodes.values()) {
text.append(node.getText(program));
}
text.append("end\n\n");
// procedure subgraphs
for(MermaidSubGraph procedureSubGraph : getProcedureSubGraphs().values()) {
text.append(procedureSubGraph.getText(program));
}
// flows to and from nodes (outside the subgraph)
for(MermaidFlow flow : flows) {
text.append(flow.getText(program));
}
return text.toString();
}
public HashMap<String, MermaidNode> getNodes() {
return nodes;
}
public void setNodes(HashMap<String, MermaidNode> nodes) {
this.nodes = nodes;
}
public MermaidNode addNode(MermaidNode node) {
this.nodes.putIfAbsent(node.id, node);
return node;
}
public MermaidNode addNode(String nodeName) {
MermaidNode node = new MermaidNode(nodeName);
this.nodes.putIfAbsent(node.id, node);
return node;
}
public HashSet<MermaidFlow> getFlows() {
return flows;
}
public void setFlows(HashSet<MermaidFlow> flows) {
this.flows = flows;
}
public void addFlow(MermaidFlow flow) {
this.flows.add(flow);
}
public void addFlow(MermaidNode from, MermaidNode to, String title, MermaidFlow.Type type, MermaidFlow.Direction direction) {
MermaidFlow flow = new MermaidFlow(from, to, title, type, direction);
this.flows.add(flow);
}
public HashMap<String, MermaidSubGraph> getProcedureSubGraphs() {
return procedureSubGraphs;
}
public void setProcedureSubGraphs(HashMap<String, MermaidSubGraph> procedureSubGraphs) {
this.procedureSubGraphs = procedureSubGraphs;
}
public MermaidSubGraph addProcedureSubGraph(MermaidSubGraph subGraph) {
this.procedureSubGraphs.putIfAbsent(subGraph.id, subGraph);
return subGraph;
}
public MermaidSubGraph addProcedureSubGraph(String subGraphName) {
MermaidSubGraph subGraph = new MermaidSubGraph(subGraphName);
this.procedureSubGraphs.putIfAbsent(subGraph.id, subGraph);
return subGraph;
}
}

View File

@ -0,0 +1,57 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.InternalError;
import dk.camelot64.kickc.model.Program;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Path;
/**
* Base class for compiler reports generated in separate files in the compiler output directory.
*/
public abstract class Report {
final protected Program program;
protected String pass;
protected String extension;
public String configureReport() {
StringBuilder report = new StringBuilder();
report.append("---\n");
report.append("config:\n");
report.append(" theme: dark\n");
report.append(" flowchart:\n");
report.append(" wrappingWidth: 800\n");
report.append(" nodeSpacing : 40\n");
report.append(" rankSpacing : 40\n");
report.append(" titleTopMargin : 40\n");
report.append(" padding : 10\n");
report.append(" htmlLabels: false\n");
report.append("---\n");
return report.toString();
}
abstract String generateReport(Program program);
Report(Program program, String pass, String extension) {
this.program = program;
this.pass = pass;
this.extension = extension;
};
public void generateReport() {
Path reportPath = program.getOutputFileManager().getMermaidOutputFile(pass, extension);
System.out.println("Generating report " + reportPath);
try {
FileOutputStream reportOutputStream = new FileOutputStream(reportPath.toFile());
OutputStreamWriter reportWriter = new OutputStreamWriter(reportOutputStream);
String reportDiagramString = generateReport(this.program);
reportWriter.write(reportDiagramString);
reportWriter.close();
reportOutputStream.close();
} catch(Exception e) {
throw new InternalError("Error generating report " + reportPath, e);
}
}
}

View File

@ -0,0 +1,110 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Graph;
import dk.camelot64.kickc.model.ProcedureCompilation;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementCall;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.LabelRef;
import dk.camelot64.kickc.model.values.ProcedureRef;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Create a report of the Control Block Flow Graph (CFG) of the whole Program.
* This class generates a markdown report with embedded mermaid graphics of
* the whole Program CFGs.
*/
public class ReportCFG extends Report {
private MermaidGraph mermaidGraph;
public ReportCFG(Program program, String pass, String extension) {
super(program, pass, extension);
}
public void mermaidCFGBlock(Graph graph, Graph.Block block, MermaidSubGraph mermaidSubGraph) {
String blockName = block.getLabel().getFullName();
MermaidNode node = mermaidSubGraph.addNode(new MermaidNode(blockName));
node.setStatements(block.getStatements());
List<Graph.Block> predecessors = program.getGraph().getPredecessors(block);
if(!predecessors.isEmpty()) {
for(Graph.Block predecessor : predecessors) {
String predecessorNodeName = predecessor.getLabel().getFullName();
String predecessorLocalNodeName = predecessor.getScope().getLocalName();
if (program.isProcedureEntry(block)) {
MermaidSubGraph procedureSubGraph = mermaidSubGraph.addProcedureSubGraph(predecessorLocalNodeName);
MermaidNode nodePredecessor = procedureSubGraph.addNode(predecessorNodeName);
mermaidSubGraph.addFlow(nodePredecessor, node, "call", MermaidFlow.Type.FULL, MermaidFlow.Direction.UNI);
String blockReturnName = block.getLabel().getLocalName() + "::@return";
MermaidNode nodeReturn = mermaidSubGraph.addNode(blockReturnName);
mermaidSubGraph.addFlow(nodeReturn, nodePredecessor, "return", MermaidFlow.Type.FULL, MermaidFlow.Direction.UNI);
} else {
MermaidNode nodePredecessor = mermaidSubGraph.addNode(predecessorNodeName);
LabelRef successorLabel = predecessor.getConditionalSuccessor();
Graph.Block successor = null;
if(successorLabel != null) {
successor = graph.getBlock(successorLabel);
}
if( successor != null && successor.equals(block)) {
mermaidSubGraph.addFlow(nodePredecessor, node, "true", MermaidFlow.Type.DOTTED, MermaidFlow.Direction.UNI);
} else {
mermaidSubGraph.addFlow(nodePredecessor, node, null, MermaidFlow.Type.DOTTED, MermaidFlow.Direction.UNI);
}
}
}
}
if(!block.getStatements().isEmpty()) {
for (Statement statement : block.getStatements()) {
if(statement instanceof StatementCall) {
String procedureName = ((StatementCall) statement).getProcedureName();
MermaidSubGraph procedureSubGraph = mermaidSubGraph.addProcedureSubGraph(procedureName);
MermaidNode procedureNode = procedureSubGraph.addNode(procedureName);
mermaidSubGraph.addFlow(node, procedureNode,"call", MermaidFlow.Type.FULL, MermaidFlow.Direction.BI);
}
}
}
}
private String mermaidCFGProcedure(Procedure procedure, Graph graph) {
String procedureName = procedure.getLocalName();
String procedureTitle = procedure.toString(program);
MermaidSubGraph subGraphProcedure = new MermaidSubGraph(procedureName, procedureTitle);
mermaidGraph.addProcedureSubGraph(subGraphProcedure);
for(Graph.Block block : graph.getAllBlocks()) {
mermaidCFGBlock(graph, block, subGraphProcedure);
}
return mermaidGraph.getText(program);
}
@Override
public String generateReport(Program program) {
StringBuilder report = new StringBuilder();
for(ProcedureCompilation procedureCompilation : program.getProcedureCompilations()) {
Procedure procedure = program.getScope().getProcedure(procedureCompilation.getProcedureRef());
Graph graph = procedureCompilation.getGraph();
if(graph != null) {
mermaidGraph = new MermaidGraph(MermaidGraph.Direction.TOP_DOWN);
report.append("Procedure " + procedure.getFullName() + "\n");
report.append("```mermaid\n");
report.append(configureReport());
report.append(mermaidCFGProcedure(procedure, graph));
report.append("```\n\n");
}
}
return report.toString();
}
}

View File

@ -0,0 +1,100 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.Graph;
import dk.camelot64.kickc.model.ProcedureCompilation;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementCall;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.LabelRef;
import dk.camelot64.kickc.model.values.ProcedureRef;
import java.util.List;
/**
* Create a report of the Call Graph (CFG) of the procedures of the whole Program.
* The report is generated in markdown with embedded mermaid graphics.
*/
public class ReportCG extends Report {
public MermaidGraph mermaidGraph;
public ReportCG(Program program, String pass, String extension) {
super(program, pass, extension);
this.mermaidGraph = new MermaidGraph(MermaidGraph.Direction.TOP_DOWN);
generateReport();
}
public ReportCG(Program program, String pass, String extension, MermaidGraph mermaidGraph) {
super(program, pass, extension);
this.mermaidGraph = mermaidGraph;
generateReport();
}
public void mermaidCGBlock(Graph.Block block, MermaidNode node) {
if(!block.getStatements().isEmpty()) {
for (Statement statement : block.getStatements()) {
if(statement instanceof StatementCall) {
String procedureName = ((StatementCall) statement).getProcedureName();
Procedure procedure = program.getScope().getProcedure(new ProcedureRef(procedureName));
ProcedureCompilation procedureCompilation = program.getProcedureCompilation(new ProcedureRef(procedureName));
Graph procedureGraph = procedureCompilation.getGraph();
MermaidNode procedureNode = mermaidCGProcedure(procedure, procedureGraph);
MermaidFlow flow = mermaidGraph.getFlow(node, procedureNode);
String flowTitle = "1";
if(flow != null) {
flowTitle = flow.getTitle();
Integer count = Integer.parseInt(flowTitle);
count++;
flowTitle = count.toString();
mermaidGraph.removeFlow(flow);
}
mermaidGraph.addFlow(node, procedureNode, flowTitle, MermaidFlow.Type.FULL, MermaidFlow.Direction.BI);
}
}
}
}
private MermaidNode mermaidCGProcedure(Procedure procedure, Graph graph) {
String procedureName = procedure.getLocalName();
String procedureTitle = procedure.toString(program);
MermaidNode node = new MermaidNode(procedureName, procedureTitle);
for(Graph.Block block : graph.getAllBlocks()) {
mermaidCGBlock(block, node);
}
return node;
}
private String mermaidCGRoot(Procedure procedure, Graph graph) {
String procedureName = procedure.getLocalName();
String procedureTitle = procedure.toString(program);
MermaidNode node = new MermaidNode(procedureName, procedureTitle);
mermaidGraph.addNode(node);
for(Graph.Block block : graph.getAllBlocks()) {
mermaidCGBlock(block, node);
}
return mermaidGraph.getText(program);
}
@Override
public String generateReport(Program program) {
StringBuilder report = new StringBuilder();
ProcedureCompilation procedureCompilation = program.getProcedureCompilation(new ProcedureRef("main"));
if(procedureCompilation != null) {
Procedure procedure = program.getScope().getProcedure(procedureCompilation.getProcedureRef());
Graph graph = procedureCompilation.getGraph();
if(graph != null) {
report.append("Procedure " + procedure.getFullName() + "\n");
report.append("```mermaid\n");
report.append(configureReport());
report.append(mermaidCGRoot(procedure, graph));
report.append("```\n\n");
}
}
return report.toString();
}
}

View File

@ -0,0 +1,68 @@
package dk.camelot64.kickc.passes.reports;
import dk.camelot64.kickc.model.*;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.VariableRef;
import java.util.List;
/**
* Creates a reference interference graph from the liveliness analysis and coalesce actions.
*/
public class ReportRIG extends Report {
public MermaidGraph mermaidGraph;
public ReportRIG(Program program, String pass, String extension) {
super(program, pass, extension);
}
public void mermaidRIGBlock(Graph.Block block, LiveRangeVariables liveRanges) {
if(!block.getStatements().isEmpty()) {
for (Statement statement : block.getStatements()) {
List<VariableRef> aliveVariables = liveRanges.getAlive(statement.getIndex());
int size = aliveVariables.size();
if(size > 2) {
for (int i = 0; i < size - 1; i++) {
VariableRef varInner = aliveVariables.get(i);
MermaidNode innerNode = mermaidGraph.addNode(varInner.getFullName());
for (int o = i+1; o < size; o++) {
VariableRef varOuter = aliveVariables.get(o);
MermaidNode outerNode = mermaidGraph.addNode(varOuter.getFullName());
mermaidGraph.addFlow(innerNode, outerNode, null, MermaidFlow.Type.FULL, MermaidFlow.Direction.BI);
}
}
}
}
}
}
public String mermaidLiveRanges(Procedure procedure, Graph graph, LiveRangeVariables liveRanges) {
for(Graph.Block block : graph.getAllBlocks()) {
mermaidRIGBlock(block, liveRanges);
}
return mermaidGraph.getText(program);
}
@Override
public String generateReport(Program program) {
StringBuilder report = new StringBuilder();
LiveRangeVariables liveRanges = program.getLiveRangeVariables();
for(ProcedureCompilation procedureCompilation : program.getProcedureCompilations()) {
Procedure procedure = program.getScope().getProcedure(procedureCompilation.getProcedureRef());
Graph graph = procedureCompilation.getGraph();
if(graph != null) {
report.append("Procedure " + procedure.getFullName() + "\n");
report.append("```mermaid\n");
mermaidGraph = new MermaidGraph(MermaidGraph.Direction.TOP_DOWN);
report.append(configureReport());
report.append(mermaidLiveRanges(procedure, graph, liveRanges));
report.append("```\n\n");
}
}
return report.toString();
}
}

View File

@ -0,0 +1,26 @@
__export char p = *((char*)0x400);
__export char a = *((char*)0x401);
__export char b = *((char*)0x402);
__export char r;
void main() {
r = calc(a, b, p);
}
char calc(char a, char b, char p) {
char x, y;
if(a<b) {
x = 12;
y = b;
} else {
x = 42+b;
y = a;
}
return x+a+p;
}