mirror of
https://gitlab.com/camelot/kickc.git
synced 2024-11-27 19:50:10 +00:00
Implemented new Unroller that can copy a set of blocks in the control flow graph and handles block transitions according to a specified strategy. Moved loop-unroll implementation to the new Unroller.
This commit is contained in:
parent
af5bb803c1
commit
c63b031dbe
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
44
src/main/java/dk/camelot64/kickc/model/BlockSet.java
Normal file
44
src/main/java/dk/camelot64/kickc/model/BlockSet.java
Normal file
@ -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<LabelRef> 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<ControlFlowBlock> getBlocks(ControlFlowGraph graph) {
|
||||
ArrayList<ControlFlowBlock> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<ControlFlowBlock> getBlocks(ControlFlowGraph graph) {
|
||||
ArrayList<ControlFlowBlock> 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();
|
||||
|
@ -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. */
|
||||
|
@ -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<VariableRef, VariableRef> definedToNewVar = copyVarsDefinedInLoop(unrollLoop);
|
||||
|
||||
// - Create new labels for all blocks in the loop
|
||||
Map<LabelRef, LabelRef> 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<StatementPhiBlock.PhiRValue> phiVariableValues = phiVariable.getValues();
|
||||
ListIterator<StatementPhiBlock.PhiRValue> 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<LoopSuccessorBlock> 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<LabelRef, LabelRef> 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<LabelRef, LabelRef> blockToNewBlock, Map<VariableRef, VariableRef> 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<LabelRef, LabelRef> 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<VariableRef, VariableRef> 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<LabelRef, LabelRef> copyBlocksInLoop(NaturalLoop unrollLoop) {
|
||||
LinkedHashMap<LabelRef, LabelRef> 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<VariableRef, VariableRef> copyVarsDefinedInLoop(NaturalLoop unrollLoop) {
|
||||
Map<VariableRef, VariableRef> 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<NaturalLoop> findUnrollLoops(Program program) {
|
||||
private static List<NaturalLoop> findUnrollLoops(Program program) {
|
||||
NaturalLoopSet loops = program.getLoopSet();
|
||||
List<NaturalLoop> 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<VariableRef> getVarsDefinedInLoop(NaturalLoop loop, Program program) {
|
||||
VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos();
|
||||
List<VariableRef> definedInLoop = new ArrayList<>();
|
||||
for(ControlFlowBlock block : loop.getBlocks(program.getGraph())) {
|
||||
for(Statement statement : block.getStatements()) {
|
||||
Collection<VariableRef> 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<LoopSuccessorBlock> getLoopSuccessorBlocks(NaturalLoop loop, ControlFlowGraph graph) {
|
||||
List<LoopSuccessorBlock> 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<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<SymbolRef, RValue> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
547
src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java
Normal file
547
src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java
Normal file
@ -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.
|
||||
* <p>
|
||||
* Unrolling has a number of phases
|
||||
* <ol>
|
||||
* <li>Prepare by ensuring that all successors of the blocks have PHI-statements for all variables defined inside the blocks </li>
|
||||
* <li>Copy all variables defined inside the blocks </li>
|
||||
* <li>Copy all block labels</li>
|
||||
* <li>Copy all blocks & statements - rewriting all internal transitions in both original and copy according to a strategy.</li>
|
||||
* <li>Patch all predecessor blocks so they hit either the original or the new copied block according to a strategy.</li>
|
||||
* <li>Patch all successor blocks so they are now hit by both original and copy.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* The {@link UnrollStrategy} defines
|
||||
* <ul>
|
||||
* <li> For each block transition entering the blocks being copied - should the transition hit the original or the copy?</li>
|
||||
* <li> For each block transition between two blocks being copied - should the transition be copied, always hit the original or always hit the copy?</li>
|
||||
* </ul>
|
||||
*/
|
||||
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<VariableRef, VariableRef> varsOriginalToCopied;
|
||||
/** Maps labels of blocks in the original block to the labels of the copied blocks. */
|
||||
private Map<LabelRef, LabelRef> 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<SymbolRef, RValue> 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<VariableRef, VariableRef> copyDefinedVars(BlockSet unrollBlocks, Program program) {
|
||||
Map<VariableRef, VariableRef> 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<LabelRef, LabelRef> copyBlockLabels(BlockSet unrollBlocks, Program program) {
|
||||
LinkedHashMap<LabelRef, LabelRef> 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<StatementPhiBlock.PhiRValue> phiRValues = phiVariable.getValues();
|
||||
ListIterator<StatementPhiBlock.PhiRValue> 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<StatementPhiBlock.PhiRValue> origPhiRValues = origPhiVariable.getValues();
|
||||
ListIterator<StatementPhiBlock.PhiRValue> 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<VariableRef, VariableRef> 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<SuccessorTransition> getSuccessorTransitions(BlockSet blockSet, ControlFlowGraph graph) {
|
||||
List<SuccessorTransition> 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<Integer> 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<VariableRef> getVarsDefinedIn(BlockSet blockSet, Program program) {
|
||||
VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos();
|
||||
List<VariableRef> definedInBlocks = new ArrayList<>();
|
||||
for(ControlFlowBlock block : blockSet.getBlocks(program.getGraph())) {
|
||||
for(Statement statement : block.getStatements()) {
|
||||
Collection<VariableRef> definedVars = variableReferenceInfos.getDefinedVars(statement);
|
||||
definedInBlocks.addAll(definedVars);
|
||||
}
|
||||
}
|
||||
return definedInBlocks;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
7
src/test/kc/unroll-for-min.kc
Normal file
7
src/test/kc/unroll-for-min.kc
Normal file
@ -0,0 +1,7 @@
|
||||
// Minimal unrolled ranged for() loop
|
||||
void main() {
|
||||
char* SCREEN = $400;
|
||||
inline for(char i : 0..2) {
|
||||
SCREEN[i] = 'a';
|
||||
}
|
||||
}
|
8
src/test/kc/unroll-while-min.kc
Normal file
8
src/test/kc/unroll-while-min.kc
Normal file
@ -0,0 +1,8 @@
|
||||
// Minimal unrolled while() loop
|
||||
void main() {
|
||||
char* SCREEN = $400;
|
||||
char i=0;
|
||||
inline while(i<2) {
|
||||
SCREEN[i++] = 'a';
|
||||
}
|
||||
}
|
@ -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
|
||||
|
12
src/test/ref/unroll-for-min.asm
Normal file
12
src/test/ref/unroll-for-min.asm
Normal file
@ -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
|
||||
}
|
24
src/test/ref/unroll-for-min.cfg
Normal file
24
src/test/ref/unroll-for-min.cfg
Normal file
@ -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
|
371
src/test/ref/unroll-for-min.log
Normal file
371
src/test/ref/unroll-for-min.log
Normal file
@ -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
|
||||
|
12
src/test/ref/unroll-for-min.sym
Normal file
12
src/test/ref/unroll-for-min.sym
Normal file
@ -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
|
||||
|
@ -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
|
||||
|
11
src/test/ref/unroll-while-min.asm
Normal file
11
src/test/ref/unroll-while-min.asm
Normal file
@ -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
|
||||
}
|
21
src/test/ref/unroll-while-min.cfg
Normal file
21
src/test/ref/unroll-while-min.cfg
Normal file
@ -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
|
358
src/test/ref/unroll-while-min.log
Normal file
358
src/test/ref/unroll-while-min.log
Normal file
@ -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
|
||||
|
11
src/test/ref/unroll-while-min.sym
Normal file
11
src/test/ref/unroll-while-min.sym
Normal file
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user