diff --git a/src/dk/camelot64/kickc/Compiler.java b/src/dk/camelot64/kickc/Compiler.java index 1d72dc477..a1ddc8e8d 100644 --- a/src/dk/camelot64/kickc/Compiler.java +++ b/src/dk/camelot64/kickc/Compiler.java @@ -16,13 +16,13 @@ public class Compiler { public static class CompilationResult { private AsmProgram asmProgram; private ControlFlowGraph graph; - private ProgramScope symbols; + private ProgramScope scope; private CompileLog log; - public CompilationResult(AsmProgram asmProgram, ControlFlowGraph graph, ProgramScope symbols, CompileLog log) { + public CompilationResult(AsmProgram asmProgram, ControlFlowGraph graph, ProgramScope scope, CompileLog log) { this.asmProgram = asmProgram; this.graph = graph; - this.symbols = symbols; + this.scope = scope; this.log = log; } @@ -34,8 +34,8 @@ public class Compiler { return graph; } - public ProgramScope getSymbols() { - return symbols; + public ProgramScope getScope() { + return scope; } public CompileLog getLog() { @@ -49,7 +49,7 @@ public class Compiler { KickCParser.FileContext file = pass0ParseInput(input, log); Program program = pass1GenerateSSA(file, log); pass2OptimizeSSA(program, log); - pass3IntervalAnalysis(program, log); + pass3LiveRangeAnalysis(program, log); AsmProgram asmProgram = pass4GenerateAsm(program, log); pass5OptimizeAsm(asmProgram, log); @@ -98,7 +98,7 @@ public class Compiler { } - private void pass3IntervalAnalysis(Program program, CompileLog log) { + private void pass3LiveRangeAnalysis(Program program, CompileLog log) { Pass3BlockSequencePlanner pass3BlockSequencePlanner = new Pass3BlockSequencePlanner(program, log); pass3BlockSequencePlanner.plan(); @@ -110,8 +110,8 @@ public class Compiler { log.append(program.getGraph().toString(program.getScope())); pass2AssertSSA(program, log); - Pass3IdentifyAliveRanges pass3IdentifyAliveRanges = new Pass3IdentifyAliveRanges(program, log); - pass3IdentifyAliveRanges.findLiveRanges(); + Pass3IdentifyLiveRanges pass3IdentifyLiveRanges = new Pass3IdentifyLiveRanges(program, log); + pass3IdentifyLiveRanges.findLiveRanges(); log.append("CONTROL FLOW GRAPH - LIVE RANGES"); log.append(program.getGraph().toString(program.getScope())); pass2AssertSSA(program, log); @@ -121,11 +121,14 @@ public class Compiler { Pass2CullEmptyBlocks cullEmptyBlocks = new Pass2CullEmptyBlocks(program, log); cullEmptyBlocks.optimize(); pass3BlockSequencePlanner.plan(); - pass3IdentifyAliveRanges.findLiveRanges(); + pass3IdentifyLiveRanges.findLiveRanges(); log.append("CONTROL FLOW GRAPH - PHI MEM COALESCED"); log.append(program.getGraph().toString(program.getScope())); pass2AssertSSA(program, log); + Pass3LoopAnalysis pass3LoopAnalysis = new Pass3LoopAnalysis(program, log); + pass3LoopAnalysis.detectLoops(); + } public void pass2OptimizeSSA(Program program, CompileLog log) { diff --git a/src/dk/camelot64/kickc/TODO.txt b/src/dk/camelot64/kickc/TODO.txt index 1e5857bf2..4a5601a10 100644 --- a/src/dk/camelot64/kickc/TODO.txt +++ b/src/dk/camelot64/kickc/TODO.txt @@ -17,13 +17,10 @@ Features + Add ++/-- incrementing/decrementing operators. Register Allocation -- Matrix Phi operation (instead of separate statements) - Safe-Copy based SSA deconstruction - Optimize phi transitions by ensuring that identical phi-transitions with regards to register allocation are collected into a single transition. - Optimize by finding optimal sequence for multiple phi assignments in entry-segments. -- Phi Lifting - Interval Analysis (variable liveness intervals) -- PhiLifting & PhiMemCoalesce (http://compilers.cs.ucla.edu/fernando/projects/soc/reports/short_tech.pdf) - ComputeLoopNestDepth(b) - Assign loop nesting levels to blocks. - ComputeRegisterPreference(v), ComputeWeight(v) - CalculateClobbering(i) @@ -32,13 +29,16 @@ Register Allocation - Maybe register preference should also incorporate the types of operations that can be effectively performed with the register? (Maybe based on fragment cost?) - Implement a register allocation (coloring) algorithm using by liveness intervals, preferences, weights & clobbering information. - Optimize register allocation by combining with knowledge of ASM program cost (bytes/cycles) and different ASM fragments with different clobbering. ++ Matrix Phi operation (instead of separate statements) ++ Phi Lifting ++ PhiLifting & PhiMemCoalesce (http://compilers.cs.ucla.edu/fernando/projects/soc/reports/short_tech.pdf) Process/Code Structure Improvement -- Make each phase return a separate object graph (allowing for keeeping the history in memory & performing rollbacks) - Refactor Expression Operator Implementation & Evaluation into one class per operator - Improve error messages to give better context - Offer to compile resulting ASM with KickAssembler + Implemenent Assertions for the output of different phases (ensuring that the result of the phase is consistent) ++ Make each phase return a separate object graph (allowing for keeeping the history in memory & performing rollbacks) Testing - Test that the parse tree for specific KC syntax is as expected. Use a print function for the parse tree to generate output for comparison. diff --git a/src/dk/camelot64/kickc/icl/LiveRange.java b/src/dk/camelot64/kickc/icl/LiveRange.java index 4702d1f31..41e506780 100644 --- a/src/dk/camelot64/kickc/icl/LiveRange.java +++ b/src/dk/camelot64/kickc/icl/LiveRange.java @@ -66,7 +66,7 @@ public class LiveRange { private Integer getIndex(Statement statement) { Integer index = statement.getIndex(); if (index == null) { - throw new RuntimeException("Statement index not defined! Live Ranges only work after defining statement indexes (Pass3IdentifyAliveRanges.generateStatementIndexes)."); + throw new RuntimeException("Statement index not defined! Live Ranges only work after defining statement indexes (Pass3IdentifyLiveRanges.generateStatementIndexes)."); } return index; } diff --git a/src/dk/camelot64/kickc/passes/Pass3IdentifyAliveRanges.java b/src/dk/camelot64/kickc/passes/Pass3IdentifyLiveRanges.java similarity index 99% rename from src/dk/camelot64/kickc/passes/Pass3IdentifyAliveRanges.java rename to src/dk/camelot64/kickc/passes/Pass3IdentifyLiveRanges.java index 789f37688..b873bada2 100644 --- a/src/dk/camelot64/kickc/passes/Pass3IdentifyAliveRanges.java +++ b/src/dk/camelot64/kickc/passes/Pass3IdentifyLiveRanges.java @@ -11,12 +11,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class Pass3IdentifyAliveRanges { +public class Pass3IdentifyLiveRanges { private final Program program; private final CompileLog log; - public Pass3IdentifyAliveRanges(Program program, CompileLog log) { + public Pass3IdentifyLiveRanges(Program program, CompileLog log) { this.program = program; this.log = log; } diff --git a/src/dk/camelot64/kickc/passes/Pass3LoopAnalysis.java b/src/dk/camelot64/kickc/passes/Pass3LoopAnalysis.java new file mode 100644 index 000000000..8feb5346f --- /dev/null +++ b/src/dk/camelot64/kickc/passes/Pass3LoopAnalysis.java @@ -0,0 +1,222 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.CompileLog; +import dk.camelot64.kickc.icl.ControlFlowBlock; +import dk.camelot64.kickc.icl.LabelRef; +import dk.camelot64.kickc.icl.Program; + +import java.util.*; + +/** + * Finds loops and nested loops in the control flow graph + */ +public class Pass3LoopAnalysis { + + private Program program; + private CompileLog log; + + public Pass3LoopAnalysis(Program program, CompileLog log) { + this.program = program; + this.log = log; + } + + public Program getProgram() { + return program; + } + + public CompileLog getLog() { + return log; + } + + public void detectLoops() { + + GraphDominators graphDominators = analyseDominators(); + + + } + + /** + * Analyse the control flow graph to find dominators for all blocks. + *

+ * Definition: d dom i if all paths from entry to node i include d + *

+ * See http://www.cs.colostate.edu/~cs553/ClassNotes/lecture09-control-dominators.ppt.pdf + * + * @return The graph dominators + */ + private GraphDominators analyseDominators() { + GraphDominators graphDominators = new GraphDominators(); + + // Initialize dominators: Dom[first]={first}, Dom[block]={all} + LabelRef firstBlock = program.getGraph().getFirstBlock().getLabel(); + BlockDominators firstDominators = graphDominators.addDominators(firstBlock); + firstDominators.add(firstBlock); + List allBlocks = new ArrayList<>(); + for (ControlFlowBlock block : program.getGraph().getAllBlocks()) { + allBlocks.add(block.getLabel()); + } + for (ControlFlowBlock block : program.getGraph().getAllBlocks()) { + if (!block.getLabel().equals(firstBlock)) { + BlockDominators blockDominators = graphDominators.addDominators(block.getLabel()); + blockDominators.addAll(allBlocks); + } + } + + // Iteratively refine dominators until they do not change + // For all nodes: + // Dom[n] = {n} UNION ( INTERSECT Dom[p] for all p that are predecessors of n) + boolean change = false; + do { + change = false; + for (ControlFlowBlock block : program.getGraph().getAllBlocks()) { + if (!block.getLabel().equals(firstBlock)) { + List predecessors = program.getGraph().getPredecessors(block); + BlockDominators newDominators = new BlockDominators(); + newDominators.addAll(allBlocks); + for (ControlFlowBlock predecessor : predecessors) { + BlockDominators predecessorDominators = graphDominators.getDominators(predecessor.getLabel()); + newDominators.intersect(predecessorDominators); + } + newDominators.add(block.getLabel()); + BlockDominators currentDominators = graphDominators.getDominators(block.getLabel()); + if (!currentDominators.equals(newDominators)) { + change = true; + graphDominators.setDominators(block.getLabel(), newDominators); + } + } + } + + } while (change); + + return graphDominators; + } + + + public static class GraphDominators { + + /** + * Maps block label to the dominators of the block. + */ + Map blockDominators; + + public GraphDominators() { + this.blockDominators = new LinkedHashMap<>(); + } + + /** + * Get the dominators for a specific block. + * + * @param block Label of the block + * @return The block dominators + */ + public BlockDominators getDominators(LabelRef block) { + return blockDominators.get(block); + } + + /** + * Creates dominators for a specific block + * + * @param block Label of the block + * @return The newly created block dominators + */ + public BlockDominators addDominators(LabelRef block) { + BlockDominators blockDominators = new BlockDominators(); + this.blockDominators.put(block, blockDominators); + return blockDominators; + } + + + /** + * Set the dominators for a specific block + * + * @param block The block + * @param dominators The new dominators + */ + public void setDominators(LabelRef block, BlockDominators dominators) { + blockDominators.put(block, dominators); + } + } + + /** + * The Dominators for a specific block. + */ + public static class BlockDominators { + + /** + * Set containing the labels of all blocks that are dominators of the block. + */ + Set dominators; + + public BlockDominators() { + this.dominators = new HashSet<>(); + } + + /** + * Add a single dominator + * + * @param dominator The dominator to add + */ + public void add(LabelRef dominator) { + dominators.add(dominator); + } + + /** + * Adds a bunch of dominators + * + * @param dominators The dominators to add + */ + public void addAll(Collection dominators) { + for (LabelRef dominator : dominators) { + add(dominator); + } + + } + + /** + * Modifies this set of dominators to be the intersection between this set and the passed set. + * Effectively removes all labels from this set that is not also present in the passed set. + * + * @param other The dominator set to intersect with + */ + public void intersect(BlockDominators other) { + Iterator iterator = dominators.iterator(); + while (iterator.hasNext()) { + LabelRef dominator = iterator.next(); + if (!other.contains(dominator)) { + iterator.remove(); + } + } + } + + /** + * Determines if the dominator set contains a specific block + * + * @param block The block to look for + * @return true if the dominator set contains the block + */ + private boolean contains(LabelRef block) { + return dominators.contains(block); + } + + public Set getDominators() { + return dominators; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlockDominators that = (BlockDominators) o; + + return dominators != null ? dominators.equals(that.dominators) : that.dominators == null; + } + + @Override + public int hashCode() { + return dominators != null ? dominators.hashCode() : 0; + } + } + + +} diff --git a/src/dk/camelot64/kickc/passes/Pass4ZeroPageAllocationLiveRange.java b/src/dk/camelot64/kickc/passes/Pass4ZeroPageAllocationLiveRange.java index 1a56749c0..858e5a737 100644 --- a/src/dk/camelot64/kickc/passes/Pass4ZeroPageAllocationLiveRange.java +++ b/src/dk/camelot64/kickc/passes/Pass4ZeroPageAllocationLiveRange.java @@ -29,6 +29,7 @@ public class Pass4ZeroPageAllocationLiveRange { for (LiveRangeEquivalenceClass liveRangeEquivalenceClass : liveRangeEquivalenceClassSet.getEquivalenceClasses()) { log.append(liveRangeEquivalenceClass.toString()); } + // Coalesce over copy assignments EquivalenceClassCopyCoalescer equivalenceClassCopyCoalescer = new EquivalenceClassCopyCoalescer(liveRangeEquivalenceClassSet); equivalenceClassCopyCoalescer.visitGraph(program.getGraph()); diff --git a/src/dk/camelot64/kickc/test/TestCompilationOutput.java b/src/dk/camelot64/kickc/test/TestCompilationOutput.java index a3fa837d8..709212fab 100644 --- a/src/dk/camelot64/kickc/test/TestCompilationOutput.java +++ b/src/dk/camelot64/kickc/test/TestCompilationOutput.java @@ -6,15 +6,7 @@ import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import java.io.*; -import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; /** * Compile a number of source files and compare the resulting assembler with expected output @@ -64,8 +56,8 @@ public class TestCompilationOutput extends TestCase { Compiler.CompilationResult output = compiler.compile(input); boolean success = true; success &= helper.testOutput(fileName, ".asm", output.getAsmProgram().toString(false)); - success &= helper.testOutput(fileName, ".sym", output.getSymbols().getSymbolTableContents()); - success &= helper.testOutput(fileName, ".cfg", output.getGraph().toString(output.getSymbols())); + success &= helper.testOutput(fileName, ".sym", output.getScope().getSymbolTableContents()); + success &= helper.testOutput(fileName, ".cfg", output.getGraph().toString(output.getScope())); success &= helper.testOutput(fileName, ".log", output.getLog().toString()); if (!success) { fail("Output does not match reference!");