diff --git a/src/dk/camelot64/kickc/TODO.txt b/src/dk/camelot64/kickc/TODO.txt index eec7aecff..706a7674b 100644 --- a/src/dk/camelot64/kickc/TODO.txt +++ b/src/dk/camelot64/kickc/TODO.txt @@ -3,9 +3,11 @@ Features - Implement Register Allocation (that utilize real registers - and non-zeropage memory) - Add Fixed Point number types - Create a proper main function for the compiler -- Add for loop +- Add a for loop for(init;condition;increment) {stmt} -> { init; do { stmt; increment } while (condition) } - Add imports - Add structs +- Add ++/-- incrementing/decrementing operators. +- Let { stmt } introduce a new anonymous scope. - Add preprocessing / find a way to allow some functions to run at compile time - Implement inline compilation of functions (and a mechanism for choosing which methods / calls to inline) - Add ability to call ASM code from KC. @@ -13,7 +15,7 @@ Features - Add inline ASM (maybe?) Process/Code Structure Improvement -- Make each phase return a separate object graph (allowing for keeping the history in memory & performing rollbacks) +- Make each phase return a separate object graph (allowing for keeeping the history in memory & performing rollbacks) - Implemenent Assertions for the output of different phases (ensuring that the result of the phase is consistent) - Refactor Expression Operator Implementation & Evaluation into one class per operator @@ -25,6 +27,7 @@ Testing - Add assert statements to the language. Create KC programs that test the compiler by compiling, running and testing assertions. Optimizations +- Optimize phi transitions by ensuring that identical phi-transitions with regards to register allocation are collected into a single transition. - Optimize register allocation by combining with knowledge of ASM program cost (bytes/cycles) and different ASM fragments with different clobbering. - Optimize by finding optimal sequence for multiple phi assignments in entry-segments. - Optimize by allowing resequencing of statements and phi assignemtns in a final phase. Perhaps by converting phi statements to "normal" statements and using some optimization step. diff --git a/src/dk/camelot64/kickc/asm/fragment/aby=_star_cowo1.asm b/src/dk/camelot64/kickc/asm/fragment/aby=_star_cowo1.asm new file mode 100644 index 000000000..13dc5b605 --- /dev/null +++ b/src/dk/camelot64/kickc/asm/fragment/aby=_star_cowo1.asm @@ -0,0 +1 @@ +lda {cowo1} \ No newline at end of file diff --git a/src/dk/camelot64/kickc/asm/fragment/aby_neq_coby1_then_la1.asm b/src/dk/camelot64/kickc/asm/fragment/aby_neq_coby1_then_la1.asm new file mode 100644 index 000000000..5716d1bf5 --- /dev/null +++ b/src/dk/camelot64/kickc/asm/fragment/aby_neq_coby1_then_la1.asm @@ -0,0 +1,2 @@ +cmp #{coby1} +bne {la1} \ No newline at end of file diff --git a/src/dk/camelot64/kickc/asm/fragment/zpby1=zpby1_minus_1.asm b/src/dk/camelot64/kickc/asm/fragment/zpby1=zpby1_minus_1.asm new file mode 100644 index 000000000..7e2dcd258 --- /dev/null +++ b/src/dk/camelot64/kickc/asm/fragment/zpby1=zpby1_minus_1.asm @@ -0,0 +1 @@ +dec {zpby1} \ No newline at end of file diff --git a/src/dk/camelot64/kickc/icl/Pass1GenerateStatementSequence.java b/src/dk/camelot64/kickc/icl/Pass1GenerateStatementSequence.java index d0307275b..d83574632 100644 --- a/src/dk/camelot64/kickc/icl/Pass1GenerateStatementSequence.java +++ b/src/dk/camelot64/kickc/icl/Pass1GenerateStatementSequence.java @@ -61,7 +61,9 @@ public class Pass1GenerateStatementSequence extends KickCBaseVisitor { @Override public Void visitStmtBlock(KickCParser.StmtBlockContext ctx) { - this.visit(ctx.stmtSeq()); + if(ctx.stmtSeq()!=null) { + this.visit(ctx.stmtSeq()); + } return null; } @@ -127,7 +129,9 @@ public class Pass1GenerateStatementSequence extends KickCBaseVisitor { Label beginJumpLabel = getCurrentSymbols().addLabelIntermediate(); StatementLabel beginJumpTarget = new StatementLabel(beginJumpLabel); sequence.addStatement(beginJumpTarget); - this.visit(ctx.stmt()); + if(ctx.stmt()!=null) { + this.visit(ctx.stmt()); + } RValue rValue = (RValue) this.visit(ctx.expr()); Statement doJmpStmt = new StatementConditionalJump(rValue, beginJumpLabel); sequence.addStatement(doJmpStmt); diff --git a/src/dk/camelot64/kickc/icl/Pass2CullEmptyBlocks.java b/src/dk/camelot64/kickc/icl/Pass2CullEmptyBlocks.java index 1c1f05bb1..71b9748ea 100644 --- a/src/dk/camelot64/kickc/icl/Pass2CullEmptyBlocks.java +++ b/src/dk/camelot64/kickc/icl/Pass2CullEmptyBlocks.java @@ -11,17 +11,20 @@ public class Pass2CullEmptyBlocks extends Pass2SsaOptimization { @Override public boolean optimize() { - List remove = new ArrayList<>(); - Map replace = new HashMap<>(); + final List remove = new ArrayList<>(); for (ControlFlowBlock block : getGraph().getAllBlocks()) { if (block.getStatements().isEmpty() && block.getLabel().isIntermediate()) { remove.add(block); } } - for (ControlFlowBlock removeBlock : remove) { + + for (final ControlFlowBlock removeBlock : remove) { ControlFlowBlock successor = getGraph().getDefaultSuccessor(removeBlock); - for (ControlFlowBlock predecessor : getGraph().getPredecessors(removeBlock)) { - replace.put(removeBlock.getLabel(), predecessor.getLabel()); + // Replace all jumps (default/conditional/call) to @removeBlock with a jump to the default successor + final List predecessors = getGraph().getPredecessors(removeBlock); + for (ControlFlowBlock predecessor : predecessors) { + Map replace = new HashMap<>(); + replace.put(removeBlock.getLabel(), successor.getLabel()); if (removeBlock.getLabel().equals(predecessor.getDefaultSuccessor())) { predecessor.setDefaultSuccessor(successor.getLabel()); } @@ -31,12 +34,35 @@ public class Pass2CullEmptyBlocks extends Pass2SsaOptimization { if (removeBlock.getLabel().equals(predecessor.getCallSuccessor())) { predecessor.setCallSuccessor(successor.getLabel()); } + replaceLabels(predecessor, replace); } + // In all phi functions of a successor blocks make a copy of the phi assignment for each predecessor + ControlFlowGraphBaseVisitor phiFixVisitor = new ControlFlowGraphBaseVisitor() { + @Override + public Void visitPhi(StatementPhi phi) { + for (StatementPhi.PreviousSymbol previousSymbol : phi.getPreviousVersions()) { + if(previousSymbol.getBlock().equals(removeBlock.getLabel())) { + // Found a phi function referencing the remove block - add copies for each predecessor + RValue previousRValue = previousSymbol.getRValue(); + for (ControlFlowBlock predecessor : predecessors) { + if(previousSymbol!=null) { + previousSymbol.setBlock(predecessor.getLabel()); + previousSymbol = null; + } else { + phi.addPreviousVersion(predecessor.getLabel(), previousRValue); + } + } + break; + } + } + return null; + } + }; + phiFixVisitor.visitBlock(successor); getGraph().getAllBlocks().remove(removeBlock); getSymbols().remove(removeBlock.getLabel()); System.out.println("Culled Empty Block " + removeBlock.getLabel()); } - replaceLabels(replace); return remove.size()>0; } diff --git a/src/dk/camelot64/kickc/icl/Pass2SsaOptimization.java b/src/dk/camelot64/kickc/icl/Pass2SsaOptimization.java index 8c841f385..b250d3b4a 100644 --- a/src/dk/camelot64/kickc/icl/Pass2SsaOptimization.java +++ b/src/dk/camelot64/kickc/icl/Pass2SsaOptimization.java @@ -171,38 +171,53 @@ public abstract class Pass2SsaOptimization { * @param replacements Variables that have alias values. */ public void replaceLabels(final Map replacements) { - ControlFlowGraphBaseVisitor visitor = new ControlFlowGraphBaseVisitor() { - - @Override - public Void visitConditionalJump(StatementConditionalJump conditionalJump) { - if (getReplacement(replacements, conditionalJump.getDestination()) != null) { - conditionalJump.setDestination(getReplacement(replacements, conditionalJump.getDestination())); - } - return null; - } - - @Override - public Void visitJump(StatementJump jump) { - if (getReplacement(replacements, jump.getDestination()) != null) { - jump.setDestination(getReplacement(replacements, jump.getDestination())); - } - return null; - } - - @Override - public Void visitPhi(StatementPhi phi) { - for (StatementPhi.PreviousSymbol previousSymbol : phi.getPreviousVersions()) { - Label replacement = getReplacement(replacements, previousSymbol.getBlock()); - if (replacement != null) { - previousSymbol.setBlock(replacement); - } - } - return null; - } - }; + ControlFlowGraphBaseVisitor visitor = getLabelReplaceVisitor(replacements); visitor.visitGraph(graph); } + /** + * Replace all usages of a label in statements with another label. + * + * @param replacements Variables that have alias values. + */ + public void replaceLabels(ControlFlowBlock block, final Map replacements) { + ControlFlowGraphBaseVisitor visitor = getLabelReplaceVisitor(replacements); + visitor.visitBlock(block); + } + + /** Creates a visitor that can replace labels. */ + private ControlFlowGraphBaseVisitor getLabelReplaceVisitor(final Map replacements) { + return new ControlFlowGraphBaseVisitor() { + + @Override + public Void visitConditionalJump(StatementConditionalJump conditionalJump) { + if (getReplacement(replacements, conditionalJump.getDestination()) != null) { + conditionalJump.setDestination(getReplacement(replacements, conditionalJump.getDestination())); + } + return null; + } + + @Override + public Void visitJump(StatementJump jump) { + if (getReplacement(replacements, jump.getDestination()) != null) { + jump.setDestination(getReplacement(replacements, jump.getDestination())); + } + return null; + } + + @Override + public Void visitPhi(StatementPhi phi) { + for (StatementPhi.PreviousSymbol previousSymbol : phi.getPreviousVersions()) { + Label replacement = getReplacement(replacements, previousSymbol.getBlock()); + if (replacement != null) { + previousSymbol.setBlock(replacement); + } + } + return null; + } + }; + } + /** * Get the label to use as replacement for another label. * @@ -244,13 +259,13 @@ public abstract class Pass2SsaOptimization { } /** - * Remove variables from the symbol table + * Remove symbols from the symbol table * - * @param variables The variables to remove + * @param symbols The symbols to remove */ - public void deleteSymbols(Collection variables) { - for (LValue variable : variables) { - scope.remove((Symbol) variable); + public void deleteSymbols(Collection symbols) { + for (Symbol symbol : symbols) { + symbol.getScope().remove(symbol); } } diff --git a/src/dk/camelot64/kickc/icl/Pass3RegisterAllocation.java b/src/dk/camelot64/kickc/icl/Pass3RegisterAllocation.java index e73bc564c..91103339a 100644 --- a/src/dk/camelot64/kickc/icl/Pass3RegisterAllocation.java +++ b/src/dk/camelot64/kickc/icl/Pass3RegisterAllocation.java @@ -119,6 +119,13 @@ public class Pass3RegisterAllocation { allocation.allocate(symbols.getVariable("flip::i#2"), RegisterAllocation.getRegisterX()); allocation.allocate(symbols.getVariable("flip::$0"), RegisterAllocation.getRegisterA()); allocation.allocate(symbols.getVariable("flip::$8"), RegisterAllocation.getRegisterA()); + allocation.allocate(symbols.getVariable("$1"), RegisterAllocation.getRegisterA()); + allocation.allocate(symbols.getVariable("$3"), RegisterAllocation.getRegisterA()); + allocation.allocate(symbols.getVariable("c#0"), RegisterAllocation.getRegisterX()); + allocation.allocate(symbols.getVariable("c#1"), RegisterAllocation.getRegisterX()); + allocation.allocate(symbols.getVariable("c#2"), RegisterAllocation.getRegisterX()); + allocation.allocate(symbols.getVariable("c#3"), RegisterAllocation.getRegisterX()); + allocation.allocate(symbols.getVariable("c#4"), RegisterAllocation.getRegisterX()); symbols.setAllocation(allocation); @@ -137,6 +144,8 @@ public class Pass3RegisterAllocation { currentZp = currentZp + 2; } else if (symbol.getType().equals(SymbolTypeBasic.BOOLEAN)) { allocation.allocate(var, new RegisterAllocation.RegisterZpBool(currentZp++)); + } else if (symbol.getType().equals(SymbolTypeBasic.VOID)) { + // No need to allocate register for VOID value } else if (symbol.getType() instanceof SymbolTypePointer) { allocation.allocate(var, new RegisterAllocation.RegisterZpPointerByte(currentZp)); currentZp = currentZp + 2; diff --git a/src/dk/camelot64/kickc/test/flipper-rex2.kc b/src/dk/camelot64/kickc/test/flipper-rex2.kc index 159321d42..0adb19d4c 100644 --- a/src/dk/camelot64/kickc/test/flipper-rex2.kc +++ b/src/dk/camelot64/kickc/test/flipper-rex2.kc @@ -2,10 +2,18 @@ byte[1000] SCREEN = $0400; byte[16*16] buffer = $1000; byte[16*16] buffer2 = $1100; +byte *RASTER = $d012; + prepare(); do { - plot(); + byte c = 25; + do{ + do { } while(*RASTER!=254) + do { } while(*RASTER!=255) + c=c-1; + } while(c!=0) flip(); + plot(); } while(true) // Prepare buffer @@ -21,18 +29,18 @@ void prepare() { void flip() { byte srcIdx = 0; byte dstIdx = 15; - byte r=0; + byte r=16; do { - byte c = 0; + byte c = 16; do { buffer2[dstIdx] = buffer[srcIdx]; srcIdx = srcIdx+1; dstIdx = dstIdx+16; - c=c+1; - } while(c<16) - r=r+1; + c=c-1; + } while(c!=0) dstIdx = dstIdx-1; - } while(r<16) + r=r-1; + } while(r!=0) byte i=0; do { buffer[i] = buffer2[i]; @@ -43,7 +51,7 @@ void flip() { // Plot buffer on screen void plot() { byte* line = SCREEN+5*40+12; - byte y=0; + byte y=16; byte i=0; do { byte x=0; @@ -53,6 +61,6 @@ void plot() { i=i+1; } while(x<16) line = line+40; - y=y+1; - } while(y<16) + y=y-1; + } while(y!=0) } \ No newline at end of file