diff --git a/src/main/java/dk/camelot64/kickc/CompileLog.java b/src/main/java/dk/camelot64/kickc/CompileLog.java index 9246194d4..07b1ce0ec 100644 --- a/src/main/java/dk/camelot64/kickc/CompileLog.java +++ b/src/main/java/dk/camelot64/kickc/CompileLog.java @@ -134,6 +134,11 @@ public class CompileLog { this.verboseLoopUnroll = verboseLoopUnroll; } + public CompileLog verboseLoopUnroll() { + setVerboseLoopUnroll(true); + return this; + } + public void setVerboseLoopAnalysis(boolean verboseLoopAnalysis) { this.verboseLoopAnalysis = verboseLoopAnalysis; } diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 57731aac1..1fb31aa2d 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -322,9 +322,13 @@ public class Compiler { loopUnrolling.add(() -> { program.clearStatementInfos(); return false; }); loopUnrolling.add(() -> { program.clearDominators(); return false; }); loopUnrolling.add(() -> { program.clearLoopSet(); return false; }); - loopUnrolling.add(new Pass2LoopUnrollPhiPrepare(program)); loopUnrolling.add(new Pass2LoopUnroll(program)); + if(getLog().isVerboseLoopUnroll()) { + getLog().append("CONTROL FLOW GRAPH BEFORE UNROLLING"); + getLog().append(program.getGraph().toString(program)); + } + boolean unrolled; do { unrolled = pass2ExecuteOnce(loopUnrolling); diff --git a/src/main/java/dk/camelot64/kickc/model/BlockSet.java b/src/main/java/dk/camelot64/kickc/model/BlockSet.java new file mode 100644 index 000000000..68b1a3331 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/model/BlockSet.java @@ -0,0 +1,44 @@ +package dk.camelot64.kickc.model; + +import dk.camelot64.kickc.model.values.LabelRef; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * A set of blocks + */ +public interface BlockSet { + + /** + * Get the blocks in the set + * @return References to the blocks + */ + Set getBlocks(); + + /** + * Get the actual blocks (not refs) + * @param graph The control flow graph containing the blocks + * @return The blocks of the loop (in the same order as they appear in the control flow graph.) + */ + default public List getBlocks(ControlFlowGraph graph) { + ArrayList controlFlowBlocks = new ArrayList<>(); + for(ControlFlowBlock block : graph.getAllBlocks()) { + if(getBlocks().contains(block.getLabel())) { + controlFlowBlocks.add(block); + } + } + return controlFlowBlocks; + } + + /** + * Determine if a block is contained in the block set + * @param labelRef THe label of the block + * @return true if the block is contained in the set + */ + default boolean contains(LabelRef labelRef) { + return getBlocks().contains(labelRef); + } + +} diff --git a/src/main/java/dk/camelot64/kickc/model/NaturalLoop.java b/src/main/java/dk/camelot64/kickc/model/NaturalLoop.java index 0cd97dafb..ab5134cac 100644 --- a/src/main/java/dk/camelot64/kickc/model/NaturalLoop.java +++ b/src/main/java/dk/camelot64/kickc/model/NaturalLoop.java @@ -10,7 +10,7 @@ import java.util.Set; /** * A single natural loop in a control flow graph. */ -public class NaturalLoop { +public class NaturalLoop implements BlockSet { /** * The head of the natural loop. The block where control enters the loop and the block where the back edge returns to. Dominates all nodes in the loop. @@ -62,22 +62,6 @@ public class NaturalLoop { this.blocks = blocks; } - /** - * Get the actual blocks (not refs) - * @param graph The control flow graph containing the blocks - * @return The blocks of the loop (in the same order as they appear in the control flow graph.) - */ - public List getBlocks(ControlFlowGraph graph) { - ArrayList controlFlowBlocks = new ArrayList<>(); - for(ControlFlowBlock block : graph.getAllBlocks()) { - if(getBlocks().contains(block.getLabel())) { - controlFlowBlocks.add(block); - } - } - return controlFlowBlocks; - } - - @Override public String toString() { StringBuilder out = new StringBuilder(); diff --git a/src/main/java/dk/camelot64/kickc/passes/AliasReplacer.java b/src/main/java/dk/camelot64/kickc/passes/AliasReplacer.java index fd63db22a..d46466e46 100644 --- a/src/main/java/dk/camelot64/kickc/passes/AliasReplacer.java +++ b/src/main/java/dk/camelot64/kickc/passes/AliasReplacer.java @@ -15,7 +15,7 @@ import java.util.Map; /** A {@link ProgramValueIterator} that replaces symbols with their alias. */ public class AliasReplacer implements ProgramValueHandler { - /** true if anything has ben replaced. */ + /** true if anything has been replaced. */ private boolean replaced; /** The alias map. */ diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnroll.java b/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnroll.java index 55a9fa9a1..5e682701b 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnroll.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnroll.java @@ -1,14 +1,9 @@ package dk.camelot64.kickc.passes; import dk.camelot64.kickc.model.*; -import dk.camelot64.kickc.model.iterator.ProgramValue; -import dk.camelot64.kickc.model.iterator.ProgramValueIterator; import dk.camelot64.kickc.model.statements.*; -import dk.camelot64.kickc.model.symbols.Label; -import dk.camelot64.kickc.model.symbols.Scope; -import dk.camelot64.kickc.model.symbols.Variable; -import dk.camelot64.kickc.model.symbols.VariableVersion; import dk.camelot64.kickc.model.values.*; +import dk.camelot64.kickc.passes.utils.Unroller; import java.util.*; @@ -31,60 +26,42 @@ public class Pass2LoopUnroll extends Pass2SsaOptimization { NaturalLoop unrollLoop = chooseUnrollLoop(unrollLoops); getLog().append("Unrolling loop " + unrollLoop); - // Unroll the first iteration of the loop + // The original loop becomes the first iteration - the new copy becomes "the rest of the loop" + Unroller.UnrollStrategy loopUnrollStrategy = new Unroller.UnrollStrategy() { + @Override + public TransitionHandling getEntryStrategy(LabelRef from, LabelRef to) { + return TransitionHandling.TO_ORIGINAL; + } - // 0. Unroll Symbols - // - Create new versions of all symbols assigned inside the loop - Map definedToNewVar = copyVarsDefinedInLoop(unrollLoop); - - // - Create new labels for all blocks in the loop - Map blockToNewBlock = copyBlocksInLoop(unrollLoop); - - // 1. Copy all loop blocks to create the "rest of the loop" and modifying the existing loop to only be the first iteration) - // - Unroll Statements (copy all statements, replace symbols properly (with the new versions / the versions assigned in the existing loop) - // - Includes unrolling PHI-statements properly - // - Unroll Successors (loop-exit successors should point to the same exit, loop-internal successors should point to the new loop-internal block) - for(ControlFlowBlock block : unrollLoop.getBlocks(getGraph())) { - // Create the new block - LabelRef newBlockLabel = unrollLabel(block.getLabel(), blockToNewBlock); - ControlFlowBlock newBlock = new ControlFlowBlock(newBlockLabel, block.getScope()); - getProgram().getGraph().addBlock(newBlock); - for(Statement statement : block.getStatements()) { - Statement newStatement = unrollStatement(statement, unrollLoop, blockToNewBlock, definedToNewVar); - newBlock.addStatement(newStatement); - if(newStatement instanceof StatementConditionalJump) { - newBlock.setConditionalSuccessor(((StatementConditionalJump) newStatement).getDestination()); - } else if(newStatement instanceof StatementCall) { - newBlock.setCallSuccessor(block.getCallSuccessor()); + @Override + public TransitionHandling getInternalStrategy(LabelRef from, LabelRef to) { + if(unrollLoop.getHead().equals(to)) { + return TransitionHandling.TO_COPY; + } else { + return TransitionHandling.TO_BOTH; } } - newBlock.setDefaultSuccessor(unrollLabel(block.getDefaultSuccessor(), blockToNewBlock)); - } + }; - // Patch the "old loop" to only contain the first iteration - for(ControlFlowBlock block : unrollLoop.getBlocks(getGraph())) { - // - All successors in the old loop to the loop head should point to the new loop head instead. - - // If the successor is the loop head then update to the new copied loop head instead - block.setDefaultSuccessor(fixSuccessor(block.getDefaultSuccessor(), blockToNewBlock, unrollLoop)); + // Unroll the first iteration of the loop + Unroller unroller = new Unroller(getProgram(), unrollLoop, loopUnrollStrategy); + unroller.unroll(); + // Mark the original loop as unrolled + markOriginalUnrolled(unrollLoop, getProgram()); + return true; + } + /** + * Update the declaredUnroll / wasUnrolled information of the original loop blocks + * + * @param unroll The (original) blocks being unrolled + */ + private static void markOriginalUnrolled(BlockSet unroll, Program program) { + for(ControlFlowBlock block : unroll.getBlocks(program.getGraph())) { for(Statement statement : block.getStatements()) { - if(statement instanceof StatementPhiBlock) { - // - Remove phi-variables in the old head from looping. (as this predecessor now no longer exists) - for(StatementPhiBlock.PhiVariable phiVariable : ((StatementPhiBlock) statement).getPhiVariables()) { - List phiVariableValues = phiVariable.getValues(); - ListIterator phiRValueListIterator = phiVariableValues.listIterator(); - while(phiRValueListIterator.hasNext()) { - StatementPhiBlock.PhiRValue phiRValue = phiRValueListIterator.next(); - if(unrollLoop.getBlocks().contains(phiRValue.getPredecessor())) { - // Found predecessor inside the same loop - remove it! - phiRValueListIterator.remove(); - } - } - } - } else if(statement instanceof StatementConditionalJump) { + if(statement instanceof StatementConditionalJump) { // - Remove the "unroll" directive on the condition in the old loop (as it is already unrolled). - // MAYBE: Only remove "unroll" from the conditional that represents the loop we are unrolling + // TODO: Maybe only remove "unroll" from the conditional that represents the loop we are unrolling StatementConditionalJump conditionalJump = (StatementConditionalJump) statement; if(conditionalJump.isDeclaredUnroll()) { // Mark is unrolled - to ensure it is removed before unrolling more @@ -92,241 +69,11 @@ public class Pass2LoopUnroll extends Pass2SsaOptimization { // Remove unroll declaration - now only the "rest" of the loop needs unrolling conditionalJump.setDeclaredUnroll(false); } - // Fix the destination (if needed)! - LabelRef fixedDestination = fixSuccessor(conditionalJump.getDestination(), blockToNewBlock, unrollLoop); - conditionalJump.setDestination(fixedDestination); - block.setConditionalSuccessor(fixedDestination); } } } - - // Update phi-blocks in loop successors to also include the new unrolled "rest" loop - List loopSuccessorBlocks = getLoopSuccessorBlocks(unrollLoop, getGraph()); - for(LoopSuccessorBlock loopSuccessorBlock : loopSuccessorBlocks) { - ControlFlowBlock successorBlock = getGraph().getBlock(loopSuccessorBlock.successor); - StatementPhiBlock phiBlock = successorBlock.getPhiBlock(); - for(StatementPhiBlock.PhiVariable phiVariable : phiBlock.getPhiVariables()) { - for(StatementPhiBlock.PhiRValue phiRValue : phiVariable.getValues()) { - if(unrollLoop.getBlocks().contains(phiRValue.getPredecessor())) { - // Found a phi variable with values from the original loop in a loop successor block - // Add another value when entering from the unrolled loop - phiVariable.setrValue(unrollLabel(phiRValue.getPredecessor(), blockToNewBlock), unrollValue(phiRValue.getrValue(), definedToNewVar)); - break; - } - } - } - } - - return true; } - /** - * Fix a successor block in the original loop (that is now only the first iteration). - * - * @param successor The successor - * @param blockToNewBlock The map from loop blocks to the corresponding unrolled "rest" loop blocks. - * @param unrollLoop The loop being unrolled - * @return The fixed successor label - */ - private LabelRef fixSuccessor(LabelRef successor, Map blockToNewBlock, NaturalLoop unrollLoop) { - LabelRef fixed; - if(unrollLoop.getHead().equals(successor)) { - fixed = blockToNewBlock.get(successor); - } else { - fixed = successor; - } - return fixed; - } - - /** - * Unroll a single statement inside a loop. - * Copy the statement, replace symbols properly (with the new versions if needed). This includes unrolling loop PHI-statements properly - * Unroll Successors (loop-exit successors should point to the same exit, loop-internal successors should point to the new loop-internal block) - * - * @param statement The statement to unroll - * @param unrollLoop The loop being unrolled - * @param blockToNewBlock Map from loop block label to the label of the copied block in the new (rest) loop - * @param definedToNewVar Map from variables defined in the loop to the copied variable in the new (rest) loop - * @return The copied & unrolled statement - */ - private Statement unrollStatement(Statement statement, NaturalLoop unrollLoop, Map blockToNewBlock, Map definedToNewVar) { - if(statement instanceof StatementPhiBlock) { - StatementPhiBlock phiBlock = (StatementPhiBlock) statement; - StatementPhiBlock newPhiBlock = new StatementPhiBlock(Comment.NO_COMMENTS); - for(StatementPhiBlock.PhiVariable phiVariable : phiBlock.getPhiVariables()) { - VariableRef phiVar = phiVariable.getVariable(); - VariableRef newVar = definedToNewVar.get(phiVar); - StatementPhiBlock.PhiVariable newPhiVariable = newPhiBlock.addPhiVariable(newVar); - for(StatementPhiBlock.PhiRValue phiRValue : phiVariable.getValues()) { - LabelRef predecessor = phiRValue.getPredecessor(); - if(unrollLoop.getBlocks().contains(predecessor)) { - // Predecessor inside the loop - create two copies (one from the original loop block and onw from the new copy) - // Entering from iteration 1 use the original value from iteration 1 - RValue rValue = copyValue(phiRValue.getrValue()); - newPhiVariable.setrValue(predecessor, rValue); - // Entering from the new rest-of-loop block perform a mapping replacing all references to variables inside the loop to the new versions. - RValue rValueNew = unrollValue(phiRValue.getrValue(), definedToNewVar); - newPhiVariable.setrValue(unrollLabel(predecessor, blockToNewBlock), rValueNew); - } else { - // Predecessor outside loop - do not copy since all entry to the copy of the loop will be through the first iteration - } - } - } - return newPhiBlock; - } else if(statement instanceof StatementAssignment) { - StatementAssignment assignment = (StatementAssignment) statement; - return new StatementAssignment( - (LValue) unrollValue(assignment.getlValue(), definedToNewVar), - unrollValue(assignment.getrValue1(), definedToNewVar), - assignment.getOperator(), - unrollValue(assignment.getrValue2(), definedToNewVar), - assignment.getSource(), - Comment.NO_COMMENTS - ); - } else if(statement instanceof StatementConditionalJump) { - StatementConditionalJump conditional = (StatementConditionalJump) statement; - LabelRef labelRef = conditional.getDestination(); - StatementConditionalJump newConditional = new StatementConditionalJump( - unrollValue(conditional.getrValue1(), definedToNewVar), - conditional.getOperator(), - unrollValue(conditional.getrValue2(), definedToNewVar), - unrollLabel(labelRef, blockToNewBlock), - conditional.getSource(), - Comment.NO_COMMENTS - ); - newConditional.setDeclaredUnroll(conditional.isDeclaredUnroll()); - return newConditional; - } else if(statement instanceof StatementCall) { - StatementCall call = (StatementCall) statement; - StatementCall newCall = new StatementCall(null, call.getProcedureName(), null, call.getSource(), Comment.NO_COMMENTS); - newCall.setProcedure(call.getProcedure()); - return newCall; - } else { - throw new RuntimeException("Statement not handled by unroll " + statement); - } - } - - /** - * If the passed label is from the original loop return the corresponding label from the unrolled "rest" loop - * Otherwise the passed label is returned. - * - * @param labelRef The label - * @param blockToNewBlock Maps labels from the original loop to the new labels in the unrolled "rest" loop. - * @return The label to use in the unrolled loop - */ - private LabelRef unrollLabel(LabelRef labelRef, Map blockToNewBlock) { - LabelRef unrolledLabel = blockToNewBlock.get(labelRef); - if(unrolledLabel == null) { - return labelRef; - } else { - return unrolledLabel; - } - } - - /** - * Update all variable references in an RValue that point to variables inside the loop to the new unrolled "rest" loop. - * - * @param rValue The rValue to update - * @param definedToNewVar Map from variables defined in the original loop to the variables in the new unrolled "rest" loop - * @return A copy of the RValue with all relevant variable sreferences updated - */ - private RValue unrollValue(RValue rValue, Map definedToNewVar) { - if(rValue == null) return null; - RValue rValueCopy = copyValue(rValue); - ProgramValue.GenericValue genericValue = new ProgramValue.GenericValue(rValueCopy); - ProgramValueIterator.execute(genericValue, (programValue, currentStmt, stmtIt, currentBlock) -> { - Value rVal = programValue.get(); - if(rVal instanceof VariableRef) { - if(definedToNewVar.get(rVal) != null) { - programValue.set(definedToNewVar.get(rVal)); - } - } - }, null, null, null); - return (RValue) genericValue.get(); - } - - /** - * Create a copy of the passed value object (to avoid that two parts of the model points to the same object). - * - * @param rValue The value to copy - * @return An exact copy of the value - */ - private RValue copyValue(RValue rValue) { - if(rValue == null) return null; - ProgramValue.GenericValue genericValue = new ProgramValue.GenericValue(rValue); - ProgramValueIterator.execute(genericValue, (programValue, currentStmt, stmtIt, currentBlock) -> { - Value rVal = programValue.get(); - if(rVal instanceof PointerDereferenceSimple) { - programValue.set(new PointerDereferenceSimple(((PointerDereferenceSimple) rVal).getPointer())); - } else if(rVal instanceof PointerDereferenceIndexed) { - programValue.set(new PointerDereferenceIndexed(((PointerDereferenceIndexed) rVal).getPointer(), ((PointerDereferenceIndexed) rVal).getIndex())); - } else if(rVal instanceof CastValue) { - programValue.set(new CastValue(((CastValue) rVal).getToType(), ((CastValue) rVal).getValue())); - } else if(rVal instanceof ValueList) { - programValue.set(new ValueList(new ArrayList<>(((ValueList) rVal).getList()))); - } - }, null, null, null); - return (RValue) genericValue.get(); - } - - /** - * Copy all blocks in a loop. The new copies are named from the original name plus an integer suffix. - * - * @param unrollLoop The loop being copied - * @return A map from each block label (from the loop) to the new copied labels - */ - private Map copyBlocksInLoop(NaturalLoop unrollLoop) { - LinkedHashMap blockToNewBlock = new LinkedHashMap<>(); - for(ControlFlowBlock block : unrollLoop.getBlocks(getGraph())) { - Scope blockScope = getScope().getScope(block.getScope()); - // Find the serial number - int unrollSerial = 1; - String localName = block.getLabel().getLocalName(); - int serialPos = localName.lastIndexOf("_"); - if(serialPos>=0) { - localName = localName.substring(0, serialPos); - } - - String unrollLabelName; - do { - unrollLabelName = localName + "_" + unrollSerial++; - } while(blockScope.getLabel(unrollLabelName) != null); - // Create a label - Label unrollLabel = blockScope.addLabel(unrollLabelName); - blockToNewBlock.put(block.getLabel(), unrollLabel.getRef()); - } - return blockToNewBlock; - } - - /** - * Create new versions of all symbols assigned inside the loop - * - * @param unrollLoop The loop being unrolled - * @return A map from variables assigned inside the loop to the new copy of the variable - */ - private Map copyVarsDefinedInLoop(NaturalLoop unrollLoop) { - Map definedToNewVar = new LinkedHashMap<>(); - for(VariableRef definedVarRef : getVarsDefinedInLoop(unrollLoop, getProgram())) { - Variable definedVar = getScope().getVariable(definedVarRef); - Variable newVar; - if(definedVarRef.isIntermediate()) { - newVar = definedVar.getScope().addVariableIntermediate(); - newVar.setType(definedVar.getType()); - newVar.setDeclaredRegister(definedVar.getDeclaredRegister()); - newVar.setDeclaredVolatile(definedVar.isDeclaredVolatile()); - newVar.setInferedVolatile(definedVar.isInferedVolatile()); - newVar.setDeclaredAlignment(definedVar.getDeclaredAlignment()); - newVar.setInferredType(definedVar.isInferredType()); - } else if(definedVarRef.isVersion()) { - newVar = ((VariableVersion) definedVar).getVersionOf().createVersion(); - } else { - throw new RuntimeException("Error! Variable is not versioned or intermediate " + definedVar.toString(getProgram())); - } - definedToNewVar.put(definedVarRef, newVar.getRef()); - //getLog().append("Defined in loop: " + definedVarRef.getFullName() + " -> " + newVar.getRef().getFullName()); - } - return definedToNewVar; - } /** * Choose which loop to unroll first from a candidate set. @@ -356,7 +103,7 @@ public class Pass2LoopUnroll extends Pass2SsaOptimization { * @param program The program * @return All loops declared to be unrolled */ - static List findUnrollLoops(Program program) { + private static List findUnrollLoops(Program program) { NaturalLoopSet loops = program.getLoopSet(); List unrollLoopCandidates = new ArrayList<>(); for(ControlFlowBlock block : program.getGraph().getAllBlocks()) { @@ -382,75 +129,4 @@ public class Pass2LoopUnroll extends Pass2SsaOptimization { return unrollLoopCandidates; } - /** - * Get all variables defined inside a loop - * - * @param loop The loop - * @param program The program - * @return All variables defined inside the blocks of the loop - */ - static List getVarsDefinedInLoop(NaturalLoop loop, Program program) { - VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos(); - List definedInLoop = new ArrayList<>(); - for(ControlFlowBlock block : loop.getBlocks(program.getGraph())) { - for(Statement statement : block.getStatements()) { - Collection definedVars = variableReferenceInfos.getDefinedVars(statement); - for(VariableRef definedVarRef : definedVars) { - definedInLoop.add(definedVarRef); - } - } - } - return definedInLoop; - } - - /** Information about a block succeeding a loop - ie. a place where the flow of control leaves a loop. */ - public static class LoopSuccessorBlock { - /** A block that is the successor to a block inside the loop. */ - LabelRef successor; - /** The block inside the loop that is the predecessor of the loop successor block. */ - LabelRef predecessor; - - public LoopSuccessorBlock(LabelRef successor, LabelRef predecessor) { - this.successor = successor; - this.predecessor = predecessor; - } - } - - /** - * Find all transitions where the flow of control leaves a loop - * - * @param loop The loop to examine - * @param graph The control flow graph - * @return - */ - static List getLoopSuccessorBlocks(NaturalLoop loop, ControlFlowGraph graph) { - List loopSuccessors = new ArrayList<>(); - for(ControlFlowBlock block : loop.getBlocks(graph)) { - if(block.getDefaultSuccessor() != null && !loop.getBlocks().contains(block.getDefaultSuccessor())) { - // Default successor is outside - loopSuccessors.add(new LoopSuccessorBlock(block.getDefaultSuccessor(), block.getLabel())); - } - if(block.getConditionalSuccessor() != null && !loop.getBlocks().contains(block.getConditionalSuccessor())) { - // Conditional successor is outside - loopSuccessors.add(new LoopSuccessorBlock(block.getConditionalSuccessor(), block.getLabel())); - } - } - return loopSuccessors; - } - - static boolean isReferencedOutsideLoop(VariableRef definedVarRef, NaturalLoop unrollLoop, Program program) { - boolean referencedOutsideLoop = false; - VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos(); - Collection varRefStatements = variableReferenceInfos.getVarRefStatements(definedVarRef); - for(Integer varRefStatement : varRefStatements) { - StatementInfos statementInfos = program.getStatementInfos(); - ControlFlowBlock refBlock = statementInfos.getBlock(varRefStatement); - if(!unrollLoop.getBlocks().contains(refBlock.getLabel())) { - referencedOutsideLoop = true; - break; - } - } - return referencedOutsideLoop; - } - } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnrollPhiPrepare.java b/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnrollPhiPrepare.java deleted file mode 100644 index 79a21e799..000000000 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopUnrollPhiPrepare.java +++ /dev/null @@ -1,73 +0,0 @@ -package dk.camelot64.kickc.passes; - -import dk.camelot64.kickc.model.ControlFlowBlock; -import dk.camelot64.kickc.model.NaturalLoop; -import dk.camelot64.kickc.model.Program; -import dk.camelot64.kickc.model.iterator.ProgramValueIterator; -import dk.camelot64.kickc.model.statements.StatementPhiBlock; -import dk.camelot64.kickc.model.symbols.Variable; -import dk.camelot64.kickc.model.symbols.VariableVersion; -import dk.camelot64.kickc.model.values.LabelRef; -import dk.camelot64.kickc.model.values.RValue; -import dk.camelot64.kickc.model.values.SymbolRef; -import dk.camelot64.kickc.model.values.VariableRef; - -import java.util.LinkedHashMap; - -/** - * Prepare for unrolling loops declared as inline. - * This is done by ensuring that all variables defined inside the loops and used outside the loop is passed through a PHI-functions - * upon loop exit thus ensuring that these variables are only used inside the loop and in the PHI-function. - * This makes it much easier to perform the unrolling as only a few parts of the program must be modified. - */ -public class Pass2LoopUnrollPhiPrepare extends Pass2SsaOptimization { - - public Pass2LoopUnrollPhiPrepare(Program program) { - super(program); - } - - @Override - public boolean step() { - // Look for loops to unroll - for(NaturalLoop unrollLoop : Pass2LoopUnroll.findUnrollLoops(getProgram())) { - // - Ensure that all variables assigned inside the loop has a PHI in successor blocks to the loop - for(VariableRef definedVarRef : Pass2LoopUnroll.getVarsDefinedInLoop(unrollLoop, getProgram())) { - // Find out if the variable is ever referenced outside the loop - if(Pass2LoopUnroll.isReferencedOutsideLoop(definedVarRef, unrollLoop, getProgram())) { - // - Add any needed PHI-statements to the successors - for(Pass2LoopUnroll.LoopSuccessorBlock loopSuccessor : Pass2LoopUnroll.getLoopSuccessorBlocks(unrollLoop, getGraph())) { - - LabelRef successorBlockRef = loopSuccessor.successor; - LabelRef successorPredecessorRef = loopSuccessor.predecessor; - ControlFlowBlock successorBlock = getGraph().getBlock(successorBlockRef); - StatementPhiBlock phiBlock = successorBlock.getPhiBlock(); - - // Create a new version of the variable - Variable definedVar = getScope().getVariable(definedVarRef); - Variable newVar = ((VariableVersion) definedVar).getVersionOf().createVersion(); - - // Replace all references outside the loop to the new version! - LinkedHashMap aliases = new LinkedHashMap<>(); - aliases.put(definedVarRef, newVar.getRef()); - ProgramValueIterator.execute(getProgram(), (programValue, currentStmt, stmtIt, currentBlock) -> { - if(currentBlock != null) { - if(!unrollLoop.getBlocks().contains(currentBlock.getLabel())) { - new AliasReplacer(aliases).execute(programValue, currentStmt, stmtIt, currentBlock); - } - } - }); - - // Create the new phi-variable in the successor phi block - StatementPhiBlock.PhiVariable newPhiVar = phiBlock.addPhiVariable(newVar.getRef()); - newPhiVar.setrValue(successorPredecessorRef, definedVarRef); - getLog().append("Creating PHI for " + definedVarRef.getFullName() + " in block " + successorBlock.getLabel() + " - " + phiBlock.toString(getProgram(), false)); - - } - } - } - } - return false; - } - - -} diff --git a/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java b/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java new file mode 100644 index 000000000..5f2cf6a66 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java @@ -0,0 +1,547 @@ +package dk.camelot64.kickc.passes.utils; + +import dk.camelot64.kickc.model.*; +import dk.camelot64.kickc.model.iterator.ProgramValue; +import dk.camelot64.kickc.model.iterator.ProgramValueIterator; +import dk.camelot64.kickc.model.statements.*; +import dk.camelot64.kickc.model.symbols.Label; +import dk.camelot64.kickc.model.symbols.Scope; +import dk.camelot64.kickc.model.symbols.Variable; +import dk.camelot64.kickc.model.symbols.VariableVersion; +import dk.camelot64.kickc.model.values.*; +import dk.camelot64.kickc.passes.AliasReplacer; + +import java.util.*; + +/** + * Utility for copying blocks in a program - typically to unroll loops or conditions. + *

+ * Unrolling has a number of phases + *

    + *
  1. Prepare by ensuring that all successors of the blocks have PHI-statements for all variables defined inside the blocks
  2. + *
  3. Copy all variables defined inside the blocks
  4. + *
  5. Copy all block labels
  6. + *
  7. Copy all blocks & statements - rewriting all internal transitions in both original and copy according to a strategy.
  8. + *
  9. Patch all predecessor blocks so they hit either the original or the new copied block according to a strategy.
  10. + *
  11. Patch all successor blocks so they are now hit by both original and copy.
  12. + *
+ *

+ * The {@link UnrollStrategy} defines + *

    + *
  • For each block transition entering the blocks being copied - should the transition hit the original or the copy?
  • + *
  • For each block transition between two blocks being copied - should the transition be copied, always hit the original or always hit the copy?
  • + *
+ */ +public class Unroller { + + /** The program. */ + private Program program; + /** The blocks being copied/unrolled. */ + private BlockSet unrollBlocks; + /** The strategy used for rewriting transitions into the block / inside the block. */ + private UnrollStrategy strategy; + /** Maps variables defined in the original block to the copies of these variables defined in the new block. */ + private Map varsOriginalToCopied; + /** Maps labels of blocks in the original block to the labels of the copied blocks. */ + private Map blocksOriginalToCopied; + + public Unroller(Program program, BlockSet unrollBlocks, UnrollStrategy unrollStrategy) { + this.program = program; + this.unrollBlocks = unrollBlocks; + this.strategy = unrollStrategy; + } + + /** + * Perform unrolling by copying all specified blocks and updating block transitions according to the strategy + */ + public void unroll() { + // 0. Prepare for copying by ensuring that all variables defined in the blocks are represented in PHI-blocks of the successors + prepare(); + // 1. Create new versions of all symbols assigned inside the loop + this.varsOriginalToCopied = copyDefinedVars(unrollBlocks, program); + // 2. Create new labels for all blocks in the loop + this.blocksOriginalToCopied = copyBlockLabels(unrollBlocks, program); + // 3. Copy all blocks being unrolled - rewriting internal transitions in both original and copy according to strategy + unrollBlocks(); + } + + /** + * Ensure that all variables defined inside the blocks to be copied has a PHI in successor blocks. + */ + private void prepare() { + for(VariableRef definedVarRef : getVarsDefinedIn(unrollBlocks, program)) { + // Find out if the variable is ever referenced outside the loop + if(isReferencedOutside(definedVarRef, unrollBlocks, program)) { + // Add any needed PHI-statements to the successors + for(SuccessorTransition successorTransition : getSuccessorTransitions(unrollBlocks, program.getGraph())) { + ControlFlowBlock successorBlock = program.getGraph().getBlock(successorTransition.successor); + StatementPhiBlock phiBlock = successorBlock.getPhiBlock(); + // Create a new version of the variable + Variable definedVar = program.getScope().getVariable(definedVarRef); + Variable newVar = ((VariableVersion) definedVar).getVersionOf().createVersion(); + // Replace all references outside the loop to the new version! + LinkedHashMap aliases = new LinkedHashMap<>(); + aliases.put(definedVarRef, newVar.getRef()); + ProgramValueIterator.execute(program, (programValue, currentStmt, stmtIt, currentBlock) -> { + if(currentBlock != null) { + if(!unrollBlocks.getBlocks().contains(currentBlock.getLabel())) { + new AliasReplacer(aliases).execute(programValue, currentStmt, stmtIt, currentBlock); + } + } + }); + // Create the new phi-variable in the successor phi block + StatementPhiBlock.PhiVariable newPhiVar = phiBlock.addPhiVariable(newVar.getRef()); + newPhiVar.setrValue(successorTransition.predecessor, definedVarRef); + program.getLog().append("Creating PHI for " + definedVarRef.getFullName() + " in block " + successorBlock.getLabel() + " - " + phiBlock.toString(program, false)); + } + } + } + } + + /** + * Create new versions of all symbols assigned inside some blocks to be unrolled + * + * @param unrollBlocks The blocks being unrolled + * @return A map from variables assigned inside the unroll blocks to the new copy of the variable + */ + private static Map copyDefinedVars(BlockSet unrollBlocks, Program program) { + Map definedToNewVar = new LinkedHashMap<>(); + for(VariableRef definedVarRef : getVarsDefinedIn(unrollBlocks, program)) { + Variable definedVar = program.getScope().getVariable(definedVarRef); + Variable newVar; + if(definedVarRef.isIntermediate()) { + newVar = definedVar.getScope().addVariableIntermediate(); + newVar.setType(definedVar.getType()); + newVar.setDeclaredRegister(definedVar.getDeclaredRegister()); + newVar.setDeclaredVolatile(definedVar.isDeclaredVolatile()); + newVar.setInferedVolatile(definedVar.isInferedVolatile()); + newVar.setDeclaredAlignment(definedVar.getDeclaredAlignment()); + newVar.setInferredType(definedVar.isInferredType()); + } else if(definedVarRef.isVersion()) { + newVar = ((VariableVersion) definedVar).getVersionOf().createVersion(); + } else { + throw new RuntimeException("Error! Variable is not versioned or intermediate " + definedVar.toString(program)); + } + definedToNewVar.put(definedVarRef, newVar.getRef()); + //getLog().append("Defined in loop: " + definedVarRef.getFullName() + " -> " + newVar.getRef().getFullName()); + } + return definedToNewVar; + } + + /** + * Copy labels for all blocks to be unrolled. The new copies are named from the original name plus an integer suffix. + * + * @param unrollBlocks The blocks being copied + * @return A map from each block label (to be unrolled) to the new copied labels + */ + private static Map copyBlockLabels(BlockSet unrollBlocks, Program program) { + LinkedHashMap blockToNewBlock = new LinkedHashMap<>(); + for(ControlFlowBlock block : unrollBlocks.getBlocks(program.getGraph())) { + Scope blockScope = program.getScope().getScope(block.getScope()); + // Find the serial number + int unrollSerial = 1; + String localName = block.getLabel().getLocalName(); + int serialPos = localName.lastIndexOf("_"); + if(serialPos >= 0) { + localName = localName.substring(0, serialPos); + } + String unrollLabelName; + do { + unrollLabelName = localName + "_" + unrollSerial++; + } while(blockScope.getLabel(unrollLabelName) != null); + // Create a label + Label unrollLabel = blockScope.addLabel(unrollLabelName); + blockToNewBlock.put(block.getLabel(), unrollLabel.getRef()); + } + return blockToNewBlock; + } + + /** + * Copy all blocks being unrolled in the control flow graph + * - Unroll Statements (copy all statements, replace symbols properly (with the new versions / the versions assigned in the existing loop) + * - Rewrite transitions in both original and copy according to the strategy + */ + private void unrollBlocks() { + for(ControlFlowBlock origBlock : unrollBlocks.getBlocks(program.getGraph())) { + // Create the new block + LabelRef newBlockLabel = blocksOriginalToCopied.get(origBlock.getLabel()); + ControlFlowBlock newBlock = new ControlFlowBlock(newBlockLabel, origBlock.getScope()); + program.getGraph().addBlock(newBlock); + for(Statement origStatement : origBlock.getStatements()) { + Statement newStatement = unrollStatement(origStatement, origBlock.getLabel()); + newBlock.addStatement(newStatement); + if(origStatement instanceof StatementConditionalJump) { + // Update conditional successors + origBlock.setConditionalSuccessor(((StatementConditionalJump) origStatement).getDestination()); + newBlock.setConditionalSuccessor(((StatementConditionalJump) newStatement).getDestination()); + } else if(newStatement instanceof StatementCall) { + // Update call successors + newBlock.setCallSuccessor(origBlock.getCallSuccessor()); + } + } + + // Set default successor for both new & original blocks + LabelRef origSuccessor = origBlock.getDefaultSuccessor(); + if(unrollBlocks.contains(origSuccessor)) { + // Default Successor is inside copied blocks - Use strategy to find default successors + UnrollStrategy.TransitionHandling handling = strategy.getInternalStrategy(origBlock.getLabel(), origSuccessor); + if(UnrollStrategy.TransitionHandling.TO_COPY.equals(handling)) { + // The transition in both original and copy should go to the copy + LabelRef newSuccessor = blocksOriginalToCopied.get(origSuccessor); + origBlock.setDefaultSuccessor(newSuccessor); + newBlock.setDefaultSuccessor(newSuccessor); + } else if(UnrollStrategy.TransitionHandling.TO_ORIGINAL.equals(handling)) { + // The transition in both original and copy should go to the original + newBlock.setDefaultSuccessor(origSuccessor); + } else if(UnrollStrategy.TransitionHandling.TO_BOTH.equals(handling)) { + // The transition in the new copy should go between the newly copied blocks + LabelRef newSuccessor = blocksOriginalToCopied.get(origSuccessor); + newBlock.setDefaultSuccessor(newSuccessor); + } + } else { + // Default Successor is outside copied blocks + // Set the same successor for the copied blocks as in the original + newBlock.setDefaultSuccessor(origSuccessor); + // Update the PHI blocks of the external default successor to also get values from the copied PHI block + patchSuccessorBlockPhi(origSuccessor, origBlock.getLabel(), newBlockLabel); + } + + // Examine whether conditional successor is external + LabelRef origConditionalSuccessor = origBlock.getConditionalSuccessor(); + if(origConditionalSuccessor !=null) { + if(!unrollBlocks.contains(origConditionalSuccessor)) { + // Update the PHI blocks of the external conditional successor to also get values from the copied PHI block + patchSuccessorBlockPhi(origConditionalSuccessor, origBlock.getLabel(), newBlockLabel); + } + } + + } + } + + /** + * Patch the PHI-block of an external successor block. Ensures that the PHI-block also receives data from the new coped block. + * @param successor The successor block's label + * @param origBlock The label of the original block + * @param newBlock The label of the newly created copy + */ + private void patchSuccessorBlockPhi(LabelRef successor, LabelRef origBlock, LabelRef newBlock) { + ControlFlowBlock successorBlock = program.getGraph().getBlock(successor); + StatementPhiBlock successorPhiBlock = successorBlock.getPhiBlock(); + for(StatementPhiBlock.PhiVariable phiVariable : successorPhiBlock.getPhiVariables()) { + List phiRValues = phiVariable.getValues(); + ListIterator phiRValuesIt = phiRValues.listIterator(); + while(phiRValuesIt.hasNext()) { + StatementPhiBlock.PhiRValue phiRValue = phiRValuesIt.next(); + if(phiRValue.getPredecessor().equals(origBlock)) { + RValue newRValue = valueToNew(phiRValue.getrValue(), varsOriginalToCopied); + phiRValuesIt.add(new StatementPhiBlock.PhiRValue(newBlock, newRValue)); + } + } + } + } + + /** + * Unroll a single statement inside a block. + * Copy the statement, replace symbols properly (with the new versions if needed). + * This includes handling any PHI-statements and successors + * + * @param origStatement The statement to unroll + * @return The copied & unrolled statement + */ + private Statement unrollStatement(Statement origStatement, LabelRef origBlock) { + if(origStatement instanceof StatementPhiBlock) { + return unrollStatementPhi((StatementPhiBlock) origStatement, origBlock); + } else if(origStatement instanceof StatementAssignment) { + StatementAssignment assignment = (StatementAssignment) origStatement; + return new StatementAssignment( + (LValue) valueToNew(assignment.getlValue(), varsOriginalToCopied), + valueToNew(assignment.getrValue1(), varsOriginalToCopied), + assignment.getOperator(), + valueToNew(assignment.getrValue2(), varsOriginalToCopied), + assignment.getSource(), + Comment.NO_COMMENTS + ); + } else if(origStatement instanceof StatementConditionalJump) { + return unrollStatementConditionalJump((StatementConditionalJump) origStatement, origBlock); + } else if(origStatement instanceof StatementCall) { + StatementCall call = (StatementCall) origStatement; + StatementCall newCall = new StatementCall(null, call.getProcedureName(), null, call.getSource(), Comment.NO_COMMENTS); + newCall.setProcedure(call.getProcedure()); + return newCall; + } else { + throw new RuntimeException("Statement not handled by unroll " + origStatement); + } + } + + /** + * Create a copy of a conditional jump statement. Also updates the original conditional jump if specified by the strategy. + * @param origConditional The original conditional jump + * @param origBlock The block containing the original PHI statement + * @return The new copied conditional jump statement. + */ + private Statement unrollStatementConditionalJump(StatementConditionalJump origConditional, LabelRef origBlock) { + // First copy the statement + StatementConditionalJump newConditional = new StatementConditionalJump( + valueToNew(origConditional.getrValue1(), varsOriginalToCopied), + origConditional.getOperator(), + valueToNew(origConditional.getrValue2(), varsOriginalToCopied), + origConditional.getDestination(), + origConditional.getSource(), + Comment.NO_COMMENTS + ); + newConditional.setDeclaredUnroll(origConditional.isDeclaredUnroll()); + // Then make sure the destination is correct in both the original and copy + LabelRef origSuccessor = origConditional.getDestination(); + if(unrollBlocks.contains(origSuccessor)) { + // Successor is inside the copied blocks! + UnrollStrategy.TransitionHandling handling = strategy.getInternalStrategy(origBlock, origSuccessor); + if(UnrollStrategy.TransitionHandling.TO_COPY.equals(handling)) { + // The transition in both original and copy should go to the copy + LabelRef newSuccessor = blocksOriginalToCopied.get(origSuccessor); + origConditional.setDestination(newSuccessor); + newConditional.setDestination(newSuccessor); + } else if(UnrollStrategy.TransitionHandling.TO_ORIGINAL.equals(handling)) { + // The transition in both original and copy should go to the original + // No action necessary since both original & copy already point to the original successor + } else if(UnrollStrategy.TransitionHandling.TO_BOTH.equals(handling)) { + // The transition in the new copy should go between the newly copied blocks + LabelRef newSuccessor = blocksOriginalToCopied.get(origSuccessor); + newConditional.setDestination(newSuccessor); + } + } else { + // Successor is outside the copied blocks! + // No action necessary since both original & copy already point to the successor + } + return newConditional; + } + + /** + * Create a copy of a PHI-statement. Also updates the original PHI-statement if specified by the strategy. + * @param origPhiBlock The original PHI statement + * @param origBlock The block containing the original PHI statement + * @return The new copied PH statement. + */ + private Statement unrollStatementPhi(StatementPhiBlock origPhiBlock, LabelRef origBlock) { + StatementPhiBlock newPhiBlock = new StatementPhiBlock(Comment.NO_COMMENTS); + for(StatementPhiBlock.PhiVariable origPhiVariable : origPhiBlock.getPhiVariables()) { + VariableRef origPhiVar = origPhiVariable.getVariable(); + VariableRef newPhiVar = varsOriginalToCopied.get(origPhiVar); + StatementPhiBlock.PhiVariable newPhiVariable = newPhiBlock.addPhiVariable(newPhiVar); + List origPhiRValues = origPhiVariable.getValues(); + ListIterator origPhiRValuesIt = origPhiRValues.listIterator(); + while(origPhiRValuesIt.hasNext()) { + StatementPhiBlock.PhiRValue origPhiRValue = origPhiRValuesIt.next(); + LabelRef predecessor = origPhiRValue.getPredecessor(); + if(unrollBlocks.contains(predecessor)) { + // Predecessor is inside the loop + UnrollStrategy.TransitionHandling handling = strategy.getInternalStrategy(predecessor, origBlock); + if(UnrollStrategy.TransitionHandling.TO_COPY.equals(handling)) { + // The transition in both original and copy should go to the copy + // - First create new entry from the new predecessor block + RValue rValueNew = valueToNew(origPhiRValue.getrValue(), varsOriginalToCopied); + newPhiVariable.setrValue(blocksOriginalToCopied.get(predecessor), rValueNew); + // - Then an entry from the existing predecessor block + RValue rValue = valueToOrig(origPhiRValue.getrValue()); + newPhiVariable.setrValue(predecessor, rValue); + // Finally remove the phi entry into the original block (since both will hit the new block) + origPhiRValuesIt.remove(); + } else if(UnrollStrategy.TransitionHandling.TO_ORIGINAL.equals(handling)) { + // The transition in both original and copy should go to the original + // Create new entry in the original block from the new predecessor block + RValue rValueNew = valueToNew(origPhiRValue.getrValue(), varsOriginalToCopied); + origPhiRValuesIt.add(new StatementPhiBlock.PhiRValue(blocksOriginalToCopied.get(predecessor), rValueNew)); + } else if(UnrollStrategy.TransitionHandling.TO_BOTH.equals(handling)) { + // The transition in the new copy should go between the newly copied blocks + RValue rValueNew = valueToNew(origPhiRValue.getrValue(), varsOriginalToCopied); + newPhiVariable.setrValue(blocksOriginalToCopied.get(predecessor), rValueNew); + } + } else { + // Predecessor is outside loop + UnrollStrategy.TransitionHandling handling = strategy.getEntryStrategy(predecessor, origBlock); + if(UnrollStrategy.TransitionHandling.TO_COPY.equals(handling)) { + // The transition should go to the new copy + newPhiVariable.setrValue(predecessor, origPhiRValue.getrValue()); + // Remove the phi entry into the original block since only the new block is hit + origPhiRValuesIt.remove(); + // Update the successor in the predecessor block to point to the new copy + ControlFlowBlock predecessorBlock = program.getGraph().getBlock(predecessor); + LabelRef newBlock = blocksOriginalToCopied.get(origBlock); + if(origBlock.equals(predecessorBlock.getDefaultSuccessor())) { + predecessorBlock.setDefaultSuccessor(newBlock); + } else if(origBlock.equals(predecessorBlock.getConditionalSuccessor())) { + predecessorBlock.setConditionalSuccessor(newBlock); + for(Statement predecessorStatement : predecessorBlock.getStatements()) { + if(predecessorStatement instanceof StatementConditionalJump) { + ((StatementConditionalJump) predecessorStatement).setDestination(newBlock); + } + } + } + } else if(UnrollStrategy.TransitionHandling.TO_ORIGINAL.equals(handling)) { + // The transition should go to the original + // Do nothing! + } + } + } + } + return newPhiBlock; + } + + /** + * Any variable references inside the value points to variables in the new copied version of the copied blocks + * Creates a copy of the passed value object (to avoid that two parts of the model points to the same object). + * + * @param rValue The rValue to update + * @param definedToNewVar Map from variables defined in the original loop to the variables in the new unrolled "rest" loop + * @return A copy of the RValue with all relevant variable references updated + */ + private static RValue valueToNew(RValue rValue, Map definedToNewVar) { + if(rValue == null) return null; + RValue rValueCopy = valueToOrig(rValue); + ProgramValue.GenericValue genericValue = new ProgramValue.GenericValue(rValueCopy); + ProgramValueIterator.execute(genericValue, (programValue, currentStmt, stmtIt, currentBlock) -> { + Value rVal = programValue.get(); + if(rVal instanceof VariableRef) { + if(definedToNewVar.get(rVal) != null) { + programValue.set(definedToNewVar.get(rVal)); + } + } + }, null, null, null); + return (RValue) genericValue.get(); + } + + /** + * Any variable references inside the value points to variables in the original version of the copied blocks + * Creates a copy of the passed value object (to avoid that two parts of the model points to the same object). + * + * @param rValue The value to copy + * @return An exact copy of the value + */ + private static RValue valueToOrig(RValue rValue) { + if(rValue == null) return null; + ProgramValue.GenericValue genericValue = new ProgramValue.GenericValue(rValue); + ProgramValueIterator.execute(genericValue, (programValue, currentStmt, stmtIt, currentBlock) -> { + Value rVal = programValue.get(); + if(rVal instanceof PointerDereferenceSimple) { + programValue.set(new PointerDereferenceSimple(((PointerDereferenceSimple) rVal).getPointer())); + } else if(rVal instanceof PointerDereferenceIndexed) { + programValue.set(new PointerDereferenceIndexed(((PointerDereferenceIndexed) rVal).getPointer(), ((PointerDereferenceIndexed) rVal).getIndex())); + } else if(rVal instanceof CastValue) { + programValue.set(new CastValue(((CastValue) rVal).getToType(), ((CastValue) rVal).getValue())); + } else if(rVal instanceof ValueList) { + programValue.set(new ValueList(new ArrayList<>(((ValueList) rVal).getList()))); + } + }, null, null, null); + return (RValue) genericValue.get(); + } + + /** Strategy for handling transitions into blocks that have been copied */ + public interface UnrollStrategy { + + /** Specifies where a transition destination should be after the unrolling. */ + enum TransitionHandling { + /** The transition should go the original block */ + TO_ORIGINAL, + /** The transition should go the copied block */ + TO_COPY, + /** The transition should be copied itself (only legal for transitions where both blocks have been copied) */ + TO_BOTH + } + + /** + * Find out how to handle a transition going into the blocks being copied. + * + * @param from The predecessor block of the transition (outside the copied blocks) + * @param to The (original) destination block of the transition (inside the copied blocks). + * @return Whether the transition should hit the original or the copy in the modified graph. + */ + TransitionHandling getEntryStrategy(LabelRef from, LabelRef to); + + /** + * Find out how to handle a transition between two blocks being copied. + * + * @param from The predecessor block of the transition (inside the copied blocks) + * @param to The (original) destination block of the transition (inside the copied blocks). + * @return Whether the transition should be copied itself, always go to the original or always to the copy. + */ + TransitionHandling getInternalStrategy(LabelRef from, LabelRef to); + + } + + /** + * Find all transitions where the flow of control leaves a block set + * + * @param blockSet The loop to examine + * @param graph The control flow graph + * @return All transitions leaving the block set + */ + private static List getSuccessorTransitions(BlockSet blockSet, ControlFlowGraph graph) { + List successorTransitions = new ArrayList<>(); + for(ControlFlowBlock block : blockSet.getBlocks(graph)) { + if(block.getDefaultSuccessor() != null && !blockSet.getBlocks().contains(block.getDefaultSuccessor())) { + // Default successor is outside + successorTransitions.add(new SuccessorTransition(block.getDefaultSuccessor(), block.getLabel())); + } + if(block.getConditionalSuccessor() != null && !blockSet.getBlocks().contains(block.getConditionalSuccessor())) { + // Conditional successor is outside + successorTransitions.add(new SuccessorTransition(block.getConditionalSuccessor(), block.getLabel())); + } + } + return successorTransitions; + } + + /** Information about a transition leaving a block set - ie. a transition from a block inside the set to a successor block outside. */ + public static class SuccessorTransition { + /** A block that is the successor to a block in the set. */ + LabelRef successor; + /** The block inside the set that is the predecessor of the successor block. */ + LabelRef predecessor; + + SuccessorTransition(LabelRef successor, LabelRef predecessor) { + this.successor = successor; + this.predecessor = predecessor; + } + } + + /** + * Determine whether a variable is referenced outside a block set + * + * @param variableRef The variable + * @param blockSet The block set + * @param program The program + * @return true if the variable is ever referenced outside the block set + */ + private static boolean isReferencedOutside(VariableRef variableRef, BlockSet blockSet, Program program) { + boolean referencedOutside = false; + VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos(); + StatementInfos statementInfos = program.getStatementInfos(); + Collection varRefStatements = variableReferenceInfos.getVarRefStatements(variableRef); + for(Integer varRefStatement : varRefStatements) { + ControlFlowBlock refBlock = statementInfos.getBlock(varRefStatement); + if(!blockSet.getBlocks().contains(refBlock.getLabel())) { + referencedOutside = true; + break; + } + } + return referencedOutside; + } + + /** + * Get all variables defined inside the blocks + * + * @param blockSet The blocks to be unrolled + * @param program The program + * @return All variables defined inside the blocks to be unrolled + */ + private static List getVarsDefinedIn(BlockSet blockSet, Program program) { + VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos(); + List definedInBlocks = new ArrayList<>(); + for(ControlFlowBlock block : blockSet.getBlocks(program.getGraph())) { + for(Statement statement : block.getStatements()) { + Collection definedVars = variableReferenceInfos.getDefinedVars(statement); + definedInBlocks.addAll(definedVars); + } + } + return definedInBlocks; + } + +} diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index da4f259cb..92a06c212 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -1712,6 +1712,16 @@ public class TestPrograms { compileAndCompare("unroll-loop-modifyvar"); } + @Test + public void testUnrollWhileMin() throws IOException, URISyntaxException { + compileAndCompare("unroll-while-min"); + } + + @Test + public void testUnrollForMin() throws IOException, URISyntaxException { + compileAndCompare("unroll-for-min"); + } + @Test public void testLoop100() throws IOException, URISyntaxException { compileAndCompare("loop100"); @@ -2505,7 +2515,7 @@ public class TestPrograms { @Test public void testLoopWhileMin() throws IOException, URISyntaxException { - compileAndCompare("loop-while-min"); + compileAndCompare("loop-while-min", log().verboseLoopAnalysis()); } @Test diff --git a/src/test/kc/unroll-for-min.kc b/src/test/kc/unroll-for-min.kc new file mode 100644 index 000000000..132d3370c --- /dev/null +++ b/src/test/kc/unroll-for-min.kc @@ -0,0 +1,7 @@ +// Minimal unrolled ranged for() loop +void main() { + char* SCREEN = $400; + inline for(char i : 0..2) { + SCREEN[i] = 'a'; + } +} \ No newline at end of file diff --git a/src/test/kc/unroll-while-min.kc b/src/test/kc/unroll-while-min.kc new file mode 100644 index 000000000..485b6d867 --- /dev/null +++ b/src/test/kc/unroll-while-min.kc @@ -0,0 +1,8 @@ +// Minimal unrolled while() loop +void main() { + char* SCREEN = $400; + char i=0; + inline while(i<2) { + SCREEN[i++] = 'a'; + } +} \ No newline at end of file diff --git a/src/test/ref/loop-while-min.log b/src/test/ref/loop-while-min.log index d5892f35a..9dc4ca245 100644 --- a/src/test/ref/loop-while-min.log +++ b/src/test/ref/loop-while-min.log @@ -69,6 +69,10 @@ Successful SSA optimization Pass2ConditionalJumpSimplification Constant (const byte*) SCREEN#0 = (byte*) 1024 Constant (const byte) main::i#0 = 0 Successful SSA optimization Pass2ConstantIdentification +Found back edge: Loop head: main::@1 tails: main::@2 blocks: null +Populated: Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 +Found back edge: Loop head: main::@1 tails: main::@2 blocks: null +Populated: Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 Inlining constant with var siblings (const byte) main::i#0 Constant inlined main::i#0 = (byte) 0 Successful SSA optimization Pass2ConstantInlining @@ -114,6 +118,26 @@ main::@2: scope:[main] from main::@1 [9] (byte) main::i#1 ← ++ (byte) main::i#2 to:main::@1 +DOMINATORS +@begin dominated by @begin +@1 dominated by @1 @begin +@end dominated by @1 @begin @end +main dominated by @1 @begin main +main::@1 dominated by @1 @begin main::@1 main +main::@return dominated by main::@return @1 @begin main::@1 main +main::@2 dominated by @1 @begin main::@1 main::@2 main + +NATURAL LOOPS +Found back edge: Loop head: main::@1 tails: main::@2 blocks: null +Populated: Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 +Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 + +NATURAL LOOPS WITH DEPTH +Found 0 loops in scope [] +Found 1 loops in scope [main] + Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 +Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 depth: 1 + VARIABLE REGISTER WEIGHTS (byte*) SCREEN diff --git a/src/test/ref/unroll-for-min.asm b/src/test/ref/unroll-for-min.asm new file mode 100644 index 000000000..725733505 --- /dev/null +++ b/src/test/ref/unroll-for-min.asm @@ -0,0 +1,12 @@ +// Minimal unrolled ranged for() loop +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +main: { + .label SCREEN = $400 + lda #'a' + sta SCREEN + sta SCREEN+1 + sta SCREEN+2 + rts +} diff --git a/src/test/ref/unroll-for-min.cfg b/src/test/ref/unroll-for-min.cfg new file mode 100644 index 000000000..a36a7f804 --- /dev/null +++ b/src/test/ref/unroll-for-min.cfg @@ -0,0 +1,24 @@ +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main + [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' + to:main::@1_1 +main::@1_1: scope:[main] from main::@1 + [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' + to:main::@1_2 +main::@1_2: scope:[main] from main::@1_1 + [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' + to:main::@return +main::@return: scope:[main] from main::@1_2 + [8] return + to:@return diff --git a/src/test/ref/unroll-for-min.log b/src/test/ref/unroll-for-min.log new file mode 100644 index 000000000..c4eda7291 --- /dev/null +++ b/src/test/ref/unroll-for-min.log @@ -0,0 +1,371 @@ +Identified constant variable (byte*) main::SCREEN +Culled Empty Block (label) main::@2 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 +main: scope:[main] from @1 + (byte*) main::SCREEN#0 ← ((byte*)) (number) $400 + (byte) main::i#0 ← (byte) 0 + to:main::@1 +main::@1: scope:[main] from main main::@1 + (byte) main::i#2 ← phi( main/(byte) main::i#0 main::@1/(byte) main::i#1 ) + *((byte*) main::SCREEN#0 + (byte) main::i#2) ← (byte) 'a' + (byte) main::i#1 ← (byte) main::i#2 + rangenext(0,2) + (bool~) main::$0 ← (byte) main::i#1 != rangelast(0,2) + unroll if((bool~) main::$0) goto main::@1 + to:main::@return +main::@return: scope:[main] from main::@1 + return + to:@return +@1: scope:[] from @begin + call main + to:@2 +@2: scope:[] from @1 + to:@end +@end: scope:[] from @2 + +SYMBOL TABLE SSA +(label) @1 +(label) @2 +(label) @begin +(label) @end +(void()) main() +(bool~) main::$0 +(label) main::@1 +(label) main::@return +(byte*) main::SCREEN +(byte*) main::SCREEN#0 +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 + +Inlining cast (byte*) main::SCREEN#0 ← (byte*)(number) $400 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Successful SSA optimization PassNCastSimplification +Simple Condition (bool~) main::$0 [6] unroll if((byte) main::i#1!=rangelast(0,2)) goto main::@1 +Successful SSA optimization Pass2ConditionalJumpSimplification +Constant (const byte*) main::SCREEN#0 = (byte*) 1024 +Constant (const byte) main::i#0 = 0 +Successful SSA optimization Pass2ConstantIdentification +Resolved ranged next value [4] main::i#1 ← ++ main::i#2 to ++ +Resolved ranged comparison value [6] unroll if(main::i#1!=rangelast(0,2)) goto main::@1 to (number) 3 +Adding number conversion cast (unumber) 3 in unroll if((byte) main::i#1!=(number) 3) goto main::@1 +Successful SSA optimization PassNAddNumberTypeConversions +Simplifying constant integer cast 3 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 3 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Unrolling loop Loop head: main::@1 tails: main::@1 blocks: main::@1 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#2 (const byte) main::i#0 +Successful SSA optimization Pass2IdenticalPhiElimination +Negating conditional jump and destination [3] if((byte) main::i#1==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConditionalJumpSequenceImprovement +Constant right-side identified [2] (byte) main::i#1 ← ++ (const byte) main::i#0 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#1 = ++main::i#0 +Successful SSA optimization Pass2ConstantIdentification +if() condition always false - eliminating [3] if((const byte) main::i#1==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConstantIfs +Simplifying expression containing zero main::SCREEN#0 in [1] *((const byte*) main::SCREEN#0 + (const byte) main::i#0) ← (byte) 'a' +Successful SSA optimization PassNSimplifyExpressionWithZero +Unrolling loop Loop head: main::@1_1 tails: main::@1_1 blocks: main::@1_1 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#3 (const byte) main::i#1 +Successful SSA optimization Pass2IdenticalPhiElimination +Negating conditional jump and destination [5] if((byte) main::i#4==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConditionalJumpSequenceImprovement +Constant right-side identified [4] (byte) main::i#4 ← ++ (const byte) main::i#1 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#4 = ++main::i#1 +Successful SSA optimization Pass2ConstantIdentification +if() condition always false - eliminating [5] if((const byte) main::i#4==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConstantIfs +Unrolling loop Loop head: main::@1_2 tails: main::@1_2 blocks: main::@1_2 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#5 (const byte) main::i#4 +Successful SSA optimization Pass2IdenticalPhiElimination +Negating conditional jump and destination [6] if((byte) main::i#6==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConditionalJumpSequenceImprovement +Constant right-side identified [5] (byte) main::i#6 ← ++ (const byte) main::i#4 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#6 = ++main::i#4 +Successful SSA optimization Pass2ConstantIdentification +Removing PHI-reference to removed block (main::@1_2) in block main::@1_3 +if() condition always true - replacing block destination [6] if((const byte) main::i#6==(byte) 3) goto main::@return +Successful SSA optimization Pass2ConstantIfs +Eliminating unused constant (const byte) main::i#6 +Successful SSA optimization PassNEliminateUnusedVars +Eliminating variable (byte) main::i#7 from unused block main::@1_3 +Eliminating variable (byte) main::i#8 from unused block main::@1_3 +Removing unused block main::@1_3 +Successful SSA optimization Pass2EliminateUnusedBlocks +Inlining constant with different constant siblings (const byte) main::i#0 +Inlining constant with different constant siblings (const byte) main::i#1 +Inlining constant with different constant siblings (const byte) main::i#4 +Constant inlined main::i#0 = (byte) 0 +Constant inlined main::i#1 = ++(byte) 0 +Constant inlined main::i#4 = ++++(byte) 0 +Successful SSA optimization Pass2ConstantInlining +Consolidated array index constant in *(main::SCREEN#0+++0) +Consolidated array index constant in *(main::SCREEN#0+++++0) +Successful SSA optimization Pass2ConstantAdditionElimination +Simplifying constant integer increment ++0 +Simplifying constant integer increment ++0 +Successful SSA optimization Pass2ConstantSimplification +Simplifying constant integer increment ++1 +Successful SSA optimization Pass2ConstantSimplification +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @2 +Adding NOP phi() at start of @end +Adding NOP phi() at start of main +CALL GRAPH +Calls in [] to main:2 + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Culled Empty Block (label) @2 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end +Adding NOP phi() at start of main + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main + [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' + to:main::@1_1 +main::@1_1: scope:[main] from main::@1 + [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' + to:main::@1_2 +main::@1_2: scope:[main] from main::@1_1 + [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' + to:main::@return +main::@return: scope:[main] from main::@1_2 + [8] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte*) main::SCREEN +(byte) main::i + +Initial phi equivalence classes +Complete equivalence classes + +INITIAL ASM +Target platform is c64basic + // File Comments +// Minimal unrolled ranged for() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" + // Global Constants & labels + // @begin +bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 + // @1 +b1: + // [2] call main + // [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend + // @end +bend: + // main +main: { + .label SCREEN = $400 + jmp b1 + // main::@1 + b1: + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + jmp b1_1 + // main::@1_1 + b1_1: + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+1 + jmp b1_2 + // main::@1_2 + b1_2: + // [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+2 + jmp breturn + // main::@return + breturn: + // [8] return + rts +} + // File Data + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' [ ] ( main:2 [ ] ) always clobbers reg byte a + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 75 combination +Uplifting [] best 75 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Minimal unrolled ranged for() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" + // Global Constants & labels + // @begin +bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 + // @1 +b1: + // [2] call main + // [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend + // @end +bend: + // main +main: { + .label SCREEN = $400 + jmp b1 + // main::@1 + b1: + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + jmp b1_1 + // main::@1_1 + b1_1: + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+1 + jmp b1_2 + // main::@1_2 + b1_2: + // [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+2 + jmp breturn + // main::@return + breturn: + // [8] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp b1 +Removing instruction jmp b1_1 +Removing instruction jmp b1_2 +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction b1_from_bbegin: +Removing instruction b1: +Removing instruction main_from_b1: +Removing instruction bend_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction bend: +Removing instruction b1: +Removing instruction b1_1: +Removing instruction b1_2: +Removing instruction breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Updating BasicUpstart to call main directly +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin +Removing instruction lda #'a' +Removing instruction lda #'a' +Succesful ASM optimization Pass5UnnecesaryLoadElimination +Removing instruction bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@1_1 +(label) main::@1_2 +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(byte) main::i + + + +FINAL ASSEMBLER +Score: 20 + + // File Comments +// Minimal unrolled ranged for() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + // @begin + // [1] phi from @begin to @1 [phi:@begin->@1] + // @1 + // [2] call main + // [4] phi from @1 to main [phi:@1->main] + // [3] phi from @1 to @end [phi:@1->@end] + // @end + // main +main: { + .label SCREEN = $400 + // main::@1 + // SCREEN[i] = 'a' + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + // main::@1_1 + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + sta SCREEN+1 + // main::@1_2 + // [7] *((const byte*) main::SCREEN#0+(byte) 2) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + sta SCREEN+2 + // main::@return + // } + // [8] return + rts +} + // File Data + diff --git a/src/test/ref/unroll-for-min.sym b/src/test/ref/unroll-for-min.sym new file mode 100644 index 000000000..05ab69f03 --- /dev/null +++ b/src/test/ref/unroll-for-min.sym @@ -0,0 +1,12 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@1_1 +(label) main::@1_2 +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(byte) main::i + diff --git a/src/test/ref/unroll-loop-modifyvar.log b/src/test/ref/unroll-loop-modifyvar.log index a51912a24..3bea6e4ff 100644 --- a/src/test/ref/unroll-loop-modifyvar.log +++ b/src/test/ref/unroll-loop-modifyvar.log @@ -66,8 +66,8 @@ Successful SSA optimization Pass2ConditionalJumpSimplification Constant (const byte*) main::SCREEN#0 = (byte*) 1024 Constant (const byte) main::a#0 = 3 Successful SSA optimization Pass2ConstantIdentification -Creating PHI for main::a#1 in block main::@2 - (byte) main::a#4 ← phi( main::@1/(byte) main::a#1 ) Unrolling loop Loop head: main::@1 tails: main::@1 blocks: main::@1 +Creating PHI for main::a#1 in block main::@2 - (byte) main::a#4 ← phi( main::@1/(byte) main::a#1 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#2 (const byte) main::a#0 Successful SSA optimization Pass2IdenticalPhiElimination @@ -82,8 +82,8 @@ if() condition always false - eliminating [3] if((const byte) main::a#1>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#4 = (byte) main::a#6 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#4 in block main::@2 - (byte) main::a#7 ← phi( main::@1_1/(byte) main::a#4 ) Unrolling loop Loop head: main::@1_1 tails: main::@1_1 blocks: main::@1_1 +Creating PHI for main::a#4 in block main::@2 - (byte) main::a#7 ← phi( main::@1_1/(byte) main::a#4 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#5 (const byte) main::a#1 Successful SSA optimization Pass2IdenticalPhiElimination @@ -98,8 +98,8 @@ if() condition always false - eliminating [5] if((const byte) main::a#4>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#7 = (byte) main::a#9 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#7 in block main::@2 - (byte) main::a#10 ← phi( main::@1_2/(byte) main::a#7 ) Unrolling loop Loop head: main::@1_2 tails: main::@1_2 blocks: main::@1_2 +Creating PHI for main::a#7 in block main::@2 - (byte) main::a#10 ← phi( main::@1_2/(byte) main::a#7 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#8 (const byte) main::a#4 Successful SSA optimization Pass2IdenticalPhiElimination @@ -114,8 +114,8 @@ if() condition always false - eliminating [6] if((const byte) main::a#7>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#10 = (byte) main::a#12 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#10 in block main::@2 - (byte) main::a#13 ← phi( main::@1_3/(byte) main::a#10 ) Unrolling loop Loop head: main::@1_3 tails: main::@1_3 blocks: main::@1_3 +Creating PHI for main::a#10 in block main::@2 - (byte) main::a#13 ← phi( main::@1_3/(byte) main::a#10 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#11 (const byte) main::a#7 Successful SSA optimization Pass2IdenticalPhiElimination @@ -130,8 +130,8 @@ if() condition always false - eliminating [7] if((const byte) main::a#10>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#13 = (byte) main::a#15 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#13 in block main::@2 - (byte) main::a#16 ← phi( main::@1_4/(byte) main::a#13 ) Unrolling loop Loop head: main::@1_4 tails: main::@1_4 blocks: main::@1_4 +Creating PHI for main::a#13 in block main::@2 - (byte) main::a#16 ← phi( main::@1_4/(byte) main::a#13 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#14 (const byte) main::a#10 Successful SSA optimization Pass2IdenticalPhiElimination @@ -146,8 +146,8 @@ if() condition always false - eliminating [8] if((const byte) main::a#13>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#16 = (byte) main::a#18 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#16 in block main::@2 - (byte) main::a#19 ← phi( main::@1_5/(byte) main::a#16 ) Unrolling loop Loop head: main::@1_5 tails: main::@1_5 blocks: main::@1_5 +Creating PHI for main::a#16 in block main::@2 - (byte) main::a#19 ← phi( main::@1_5/(byte) main::a#16 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#17 (const byte) main::a#13 Successful SSA optimization Pass2IdenticalPhiElimination @@ -162,8 +162,8 @@ if() condition always false - eliminating [9] if((const byte) main::a#16>=(byte) Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#19 = (byte) main::a#21 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#19 in block main::@2 - (byte) main::a#22 ← phi( main::@1_6/(byte) main::a#19 ) Unrolling loop Loop head: main::@1_6 tails: main::@1_6 blocks: main::@1_6 +Creating PHI for main::a#19 in block main::@2 - (byte) main::a#22 ← phi( main::@1_6/(byte) main::a#19 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#20 (const byte) main::a#16 Successful SSA optimization Pass2IdenticalPhiElimination @@ -178,8 +178,8 @@ if() condition always false - eliminating [10] if((const byte) main::a#19>=(byte Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#22 = (byte) main::a#24 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#22 in block main::@2 - (byte) main::a#25 ← phi( main::@1_7/(byte) main::a#22 ) Unrolling loop Loop head: main::@1_7 tails: main::@1_7 blocks: main::@1_7 +Creating PHI for main::a#22 in block main::@2 - (byte) main::a#25 ← phi( main::@1_7/(byte) main::a#22 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#23 (const byte) main::a#19 Successful SSA optimization Pass2IdenticalPhiElimination @@ -194,8 +194,8 @@ if() condition always false - eliminating [11] if((const byte) main::a#22>=(byte Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#25 = (byte) main::a#27 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#25 in block main::@2 - (byte) main::a#28 ← phi( main::@1_8/(byte) main::a#25 ) Unrolling loop Loop head: main::@1_8 tails: main::@1_8 blocks: main::@1_8 +Creating PHI for main::a#25 in block main::@2 - (byte) main::a#28 ← phi( main::@1_8/(byte) main::a#25 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#26 (const byte) main::a#22 Successful SSA optimization Pass2IdenticalPhiElimination @@ -210,8 +210,8 @@ if() condition always false - eliminating [12] if((const byte) main::a#25>=(byte Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#28 = (byte) main::a#30 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#28 in block main::@2 - (byte) main::a#31 ← phi( main::@1_9/(byte) main::a#28 ) Unrolling loop Loop head: main::@1_9 tails: main::@1_9 blocks: main::@1_9 +Creating PHI for main::a#28 in block main::@2 - (byte) main::a#31 ← phi( main::@1_9/(byte) main::a#28 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#29 (const byte) main::a#25 Successful SSA optimization Pass2IdenticalPhiElimination @@ -226,8 +226,8 @@ if() condition always false - eliminating [13] if((const byte) main::a#28>=(byte Successful SSA optimization Pass2ConstantIfs Alias (byte) main::a#31 = (byte) main::a#33 Successful SSA optimization Pass2AliasElimination -Creating PHI for main::a#31 in block main::@2 - (byte) main::a#34 ← phi( main::@1_10/(byte) main::a#31 ) Unrolling loop Loop head: main::@1_10 tails: main::@1_10 blocks: main::@1_10 +Creating PHI for main::a#31 in block main::@2 - (byte) main::a#34 ← phi( main::@1_10/(byte) main::a#31 ) Successful SSA optimization Pass2LoopUnroll Identical Phi Values (byte) main::a#32 (const byte) main::a#28 Successful SSA optimization Pass2IdenticalPhiElimination diff --git a/src/test/ref/unroll-while-min.asm b/src/test/ref/unroll-while-min.asm new file mode 100644 index 000000000..620413f23 --- /dev/null +++ b/src/test/ref/unroll-while-min.asm @@ -0,0 +1,11 @@ +// Minimal unrolled while() loop +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +main: { + .label SCREEN = $400 + lda #'a' + sta SCREEN + sta SCREEN+1 + rts +} diff --git a/src/test/ref/unroll-while-min.cfg b/src/test/ref/unroll-while-min.cfg new file mode 100644 index 000000000..87802ba5a --- /dev/null +++ b/src/test/ref/unroll-while-min.cfg @@ -0,0 +1,21 @@ +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main + [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' + to:main::@2_1 +main::@2_1: scope:[main] from main::@1 + [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' + to:main::@return +main::@return: scope:[main] from main::@2_1 + [7] return + to:@return diff --git a/src/test/ref/unroll-while-min.log b/src/test/ref/unroll-while-min.log new file mode 100644 index 000000000..4272c5f92 --- /dev/null +++ b/src/test/ref/unroll-while-min.log @@ -0,0 +1,358 @@ +Identified constant variable (byte*) main::SCREEN +Culled Empty Block (label) main::@4 +Culled Empty Block (label) main::@3 +Culled Empty Block (label) main::@5 +Culled Empty Block (label) main::@6 + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 +main: scope:[main] from @1 + (byte*) main::SCREEN#0 ← ((byte*)) (number) $400 + (byte) main::i#0 ← (number) 0 + to:main::@1 +main::@1: scope:[main] from main main::@2 + (byte) main::i#2 ← phi( main/(byte) main::i#0 main::@2/(byte) main::i#1 ) + (bool~) main::$0 ← (byte) main::i#2 < (number) 2 + unroll if((bool~) main::$0) goto main::@2 + to:main::@return +main::@2: scope:[main] from main::@1 + (byte) main::i#3 ← phi( main::@1/(byte) main::i#2 ) + *((byte*) main::SCREEN#0 + (byte) main::i#3) ← (byte) 'a' + (byte) main::i#1 ← ++ (byte) main::i#3 + to:main::@1 +main::@return: scope:[main] from main::@1 + return + to:@return +@1: scope:[] from @begin + call main + to:@2 +@2: scope:[] from @1 + to:@end +@end: scope:[] from @2 + +SYMBOL TABLE SSA +(label) @1 +(label) @2 +(label) @begin +(label) @end +(void()) main() +(bool~) main::$0 +(label) main::@1 +(label) main::@2 +(label) main::@return +(byte*) main::SCREEN +(byte*) main::SCREEN#0 +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 +(byte) main::i#3 + +Adding number conversion cast (unumber) 0 in (byte) main::i#0 ← (number) 0 +Adding number conversion cast (unumber) 2 in (bool~) main::$0 ← (byte) main::i#2 < (number) 2 +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast (byte*) main::SCREEN#0 ← (byte*)(number) $400 +Inlining cast (byte) main::i#0 ← (unumber)(number) 0 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 0 +Simplifying constant integer cast 2 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 0 +Finalized unsigned number type (byte) 2 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Alias (byte) main::i#2 = (byte) main::i#3 +Successful SSA optimization Pass2AliasElimination +Simple Condition (bool~) main::$0 [4] unroll if((byte) main::i#2<(byte) 2) goto main::@2 +Successful SSA optimization Pass2ConditionalJumpSimplification +Constant (const byte*) main::SCREEN#0 = (byte*) 1024 +Constant (const byte) main::i#0 = 0 +Successful SSA optimization Pass2ConstantIdentification +Unrolling loop Loop head: main::@1 tails: main::@2 blocks: main::@2 main::@1 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#2 (const byte) main::i#0 +Successful SSA optimization Pass2IdenticalPhiElimination +Constant right-side identified [3] (byte) main::i#1 ← ++ (const byte) main::i#0 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#1 = ++main::i#0 +Successful SSA optimization Pass2ConstantIdentification +if() condition always true - replacing block destination [1] if((const byte) main::i#0<(byte) 2) goto main::@2 +Successful SSA optimization Pass2ConstantIfs +Simplifying expression containing zero main::SCREEN#0 in [2] *((const byte*) main::SCREEN#0 + (const byte) main::i#0) ← (byte) 'a' +Successful SSA optimization PassNSimplifyExpressionWithZero +Unrolling loop Loop head: main::@1_1 tails: main::@2_1 blocks: main::@2_1 main::@1_1 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#4 (const byte) main::i#1 +Successful SSA optimization Pass2IdenticalPhiElimination +Constant right-side identified [7] (byte) main::i#5 ← ++ (const byte) main::i#1 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#5 = ++main::i#1 +Successful SSA optimization Pass2ConstantIdentification +if() condition always true - replacing block destination [3] if((const byte) main::i#1<(byte) 2) goto main::@2_1 +Successful SSA optimization Pass2ConstantIfs +Unrolling loop Loop head: main::@1_2 tails: main::@2_2 blocks: main::@2_2 main::@1_2 +Successful SSA optimization Pass2LoopUnroll +Identical Phi Values (byte) main::i#6 (const byte) main::i#5 +Successful SSA optimization Pass2IdenticalPhiElimination +Constant right-side identified [8] (byte) main::i#7 ← ++ (const byte) main::i#5 +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte) main::i#7 = ++main::i#5 +Successful SSA optimization Pass2ConstantIdentification +if() condition always false - eliminating [4] if((const byte) main::i#5<(byte) 2) goto main::@2_2 +Successful SSA optimization Pass2ConstantIfs +Eliminating variable (byte) main::i#8 from unused block main::@1_3 +Eliminating variable (byte) main::i#9 from unused block main::@2_3 +Removing PHI-reference to removed block (main::@2_2) in block main::@1_3 +Removing unused block main::@2_2 +Removing unused block main::@1_3 +Removing unused block main::@2_3 +Successful SSA optimization Pass2EliminateUnusedBlocks +Eliminating unused constant (const byte) main::i#7 +Successful SSA optimization PassNEliminateUnusedVars +Eliminating unused constant (const byte) main::i#5 +Successful SSA optimization PassNEliminateUnusedVars +Inlining constant with different constant siblings (const byte) main::i#0 +Inlining constant with different constant siblings (const byte) main::i#1 +Constant inlined main::i#0 = (byte) 0 +Constant inlined main::i#1 = ++(byte) 0 +Successful SSA optimization Pass2ConstantInlining +Consolidated array index constant in *(main::SCREEN#0+++0) +Successful SSA optimization Pass2ConstantAdditionElimination +Simplifying constant integer increment ++0 +Successful SSA optimization Pass2ConstantSimplification +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @2 +Adding NOP phi() at start of @end +Adding NOP phi() at start of main +Adding NOP phi() at start of main::@1 +Adding NOP phi() at start of main::@1_1 +Adding NOP phi() at start of main::@1_2 +CALL GRAPH +Calls in [] to main:2 + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Culled Empty Block (label) @2 +Culled Empty Block (label) main::@1 +Culled Empty Block (label) main::@1_1 +Culled Empty Block (label) main::@1_2 +Renumbering block main::@2 to main::@1 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end +Adding NOP phi() at start of main + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() +main: scope:[main] from @1 + [4] phi() + to:main::@1 +main::@1: scope:[main] from main + [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' + to:main::@2_1 +main::@2_1: scope:[main] from main::@1 + [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' + to:main::@return +main::@return: scope:[main] from main::@2_1 + [7] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte*) main::SCREEN +(byte) main::i + +Initial phi equivalence classes +Complete equivalence classes + +INITIAL ASM +Target platform is c64basic + // File Comments +// Minimal unrolled while() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" + // Global Constants & labels + // @begin +bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 + // @1 +b1: + // [2] call main + // [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend + // @end +bend: + // main +main: { + .label SCREEN = $400 + jmp b1 + // main::@1 + b1: + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + jmp b2_1 + // main::@2_1 + b2_1: + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+1 + jmp breturn + // main::@return + breturn: + // [7] return + rts +} + // File Data + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' [ ] ( main:2 [ ] ) always clobbers reg byte a +Statement [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' [ ] ( main:2 [ ] ) always clobbers reg byte a + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 66 combination +Uplifting [] best 66 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Minimal unrolled while() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(bbegin) +.pc = $80d "Program" + // Global Constants & labels + // @begin +bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 + // @1 +b1: + // [2] call main + // [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend + // @end +bend: + // main +main: { + .label SCREEN = $400 + jmp b1 + // main::@1 + b1: + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + jmp b2_1 + // main::@2_1 + b2_1: + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN+1 + jmp breturn + // main::@return + breturn: + // [7] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp b1 +Removing instruction jmp b2_1 +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction b1_from_bbegin: +Removing instruction b1: +Removing instruction main_from_b1: +Removing instruction bend_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction bend: +Removing instruction b1: +Removing instruction b2_1: +Removing instruction breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Updating BasicUpstart to call main directly +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin +Removing instruction lda #'a' +Succesful ASM optimization Pass5UnnecesaryLoadElimination +Removing instruction bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@2_1 +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(byte) main::i + + + +FINAL ASSEMBLER +Score: 16 + + // File Comments +// Minimal unrolled while() loop + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + // @begin + // [1] phi from @begin to @1 [phi:@begin->@1] + // @1 + // [2] call main + // [4] phi from @1 to main [phi:@1->main] + // [3] phi from @1 to @end [phi:@1->@end] + // @end + // main +main: { + .label SCREEN = $400 + // main::@1 + // SCREEN[i++] = 'a' + // [5] *((const byte*) main::SCREEN#0) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + lda #'a' + sta SCREEN + // main::@2_1 + // [6] *((const byte*) main::SCREEN#0+(byte) 1) ← (byte) 'a' -- _deref_pbuc1=vbuc2 + sta SCREEN+1 + // main::@return + // } + // [7] return + rts +} + // File Data + diff --git a/src/test/ref/unroll-while-min.sym b/src/test/ref/unroll-while-min.sym new file mode 100644 index 000000000..a1a8c74b9 --- /dev/null +++ b/src/test/ref/unroll-while-min.sym @@ -0,0 +1,11 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@2_1 +(label) main::@return +(byte*) main::SCREEN +(const byte*) main::SCREEN#0 SCREEN = (byte*) 1024 +(byte) main::i +