diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index b8e465cb3..30460b9bb 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -31,6 +31,9 @@ public class Compiler { /** Enable the zero-page coalesce pass. It takes a lot of time, but limits the zero page usage significantly. */ private boolean enableZeroPageCoalasce = false; + /** Enable loop head constant optimization. It identified whenever a while()/for() has a constant condition on the first iteration and rewrites it. */ + private boolean enableLoopHeadConstant = false; + public Compiler() { this.program = new Program(); } @@ -43,6 +46,10 @@ public class Compiler { this.enableZeroPageCoalasce = true; } + void enableLoopHeadConstant() { + this.enableLoopHeadConstant = true; + } + void setTargetPlatform(TargetPlatform targetPlatform) { program.setTargetPlatform(targetPlatform); } @@ -305,10 +312,13 @@ public class Compiler { optimizations.add(new PassNSimplifyExpressionWithZero(program)); optimizations.add(new PassNEliminateUnusedVars(program, true)); optimizations.add(new Pass2EliminateUnusedBlocks(program)); - optimizations.add(new PassNStatementIndices(program)); - optimizations.add(() -> { program.clearDominators(); return false; }); - optimizations.add(() -> { program.clearLoopSet(); return false; }); - optimizations.add(new Pass2LoopHeadConstantIdentification(program)); + if(enableLoopHeadConstant) { + optimizations.add(new PassNStatementIndices(program)); + optimizations.add(() -> { program.clearDominators(); return false; }); + optimizations.add(() -> { program.clearLoopSet(); return false; }); + optimizations.add(new Pass2LoopHeadConstantIdentification(program)); + optimizations.add(() -> { program.clearStatementIndices(); return false; }); + } return optimizations; } diff --git a/src/main/java/dk/camelot64/kickc/KickC.java b/src/main/java/dk/camelot64/kickc/KickC.java index d4b4cfa6d..78c7da32b 100644 --- a/src/main/java/dk/camelot64/kickc/KickC.java +++ b/src/main/java/dk/camelot64/kickc/KickC.java @@ -66,6 +66,9 @@ public class KickC implements Callable { @CommandLine.Option(names = {"-Ocoalesce"}, description = "Optimization Option. Enables zero-page coalesce pass which limits zero-page usage significantly, but takes a lot of compile time.") private boolean optimizeZeroPageCoalesce = false; + @CommandLine.Option(names = {"-Oloophead"}, description = "Optimization Option. Enables loop-head constant pass which identifies loops where the condition is constant on the first iteration.") + private boolean optimizeLoopHeadConstant = false; + @CommandLine.Option(names = {"-Ocache"}, description = "Optimization Option. Enables a fragment cache file.") private boolean optimizeFragmentCache = false; @@ -212,6 +215,10 @@ public class KickC implements Callable { compiler.enableZeroPageCoalasce(); } + if(optimizeLoopHeadConstant) { + compiler.enableLoopHeadConstant(); + } + System.out.println("Compiling " + kcFile); Program program = null; try { diff --git a/src/main/java/dk/camelot64/kickc/model/Program.java b/src/main/java/dk/camelot64/kickc/model/Program.java index 6a9a500bf..aba70686c 100644 --- a/src/main/java/dk/camelot64/kickc/model/Program.java +++ b/src/main/java/dk/camelot64/kickc/model/Program.java @@ -2,6 +2,7 @@ package dk.camelot64.kickc.model; import dk.camelot64.kickc.CompileLog; import dk.camelot64.kickc.asm.AsmProgram; +import dk.camelot64.kickc.model.statements.Statement; import dk.camelot64.kickc.model.statements.StatementInfos; import dk.camelot64.kickc.model.symbols.ProgramScope; import dk.camelot64.kickc.model.values.LabelRef; @@ -302,6 +303,17 @@ public class Program { this.statementInfos = null; } + /** + * Clear index numbers for all statements in the control flow graph. + */ + public void clearStatementIndices() { + for(ControlFlowBlock block : getGraph().getAllBlocks()) { + for(Statement statement : block.getStatements()) { + statement.setIndex(null); + } + } + } + public SymbolInfos getSymbolInfos() { if(symbolInfos==null) this.symbolInfos = new PassNCalcSymbolInfos(this).calculate(); diff --git a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java index e8086431e..90309aa16 100644 --- a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java +++ b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramValue.java @@ -8,11 +8,10 @@ import dk.camelot64.kickc.model.values.*; /** * Any Value in the program being iterated by {@link ProgramValueIterator}. - * + *

* The Value can be inspected using get() and replaced inside the model using set(val). - * + *

* The context of the Value can be determined from the sub-class containing it plus the parameters to the ProgramValueHandler. - * */ public interface ProgramValue { @@ -204,6 +203,11 @@ public interface ProgramValue { public void set(Value value) { phiVariable.getValues().get(i).setrValue((RValue) value); } + + public LabelRef getPredecessor() { + return phiVariable.getValues().get(i).getPredecessor(); + } + } class PhiValuePredecessor implements ProgramValue { @@ -472,7 +476,7 @@ public interface ProgramValue { @Override public void set(Value value) { - structValue.setValue(memberRef, (ConstantValue)value); + structValue.setValue(memberRef, (ConstantValue) value); } } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorEqual.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorEqual.java index cc990790f..de755ff97 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorEqual.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorEqual.java @@ -3,7 +3,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; +import dk.camelot64.kickc.model.values.ConstantEnumerable; import dk.camelot64.kickc.model.values.ConstantLiteral; import java.util.Objects; @@ -17,8 +17,8 @@ public class OperatorEqual extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(Objects.equals(((ConstantInteger) left).getInteger(), ((ConstantInteger) right).getInteger())); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(Objects.equals(((ConstantEnumerable) left).getInteger(), ((ConstantEnumerable) right).getInteger())); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThan.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThan.java index f1614c0be..87084a4f4 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThan.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThan.java @@ -2,9 +2,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; -import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; -import dk.camelot64.kickc.model.values.ConstantLiteral; +import dk.camelot64.kickc.model.values.*; /** Binary greater-than Operator ( x > y ) */ public class OperatorGreaterThan extends OperatorBinary { @@ -15,8 +13,8 @@ public class OperatorGreaterThan extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(((ConstantInteger) left).getInteger() > ((ConstantInteger) right).getInteger()); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(((ConstantEnumerable) left).getInteger() > ((ConstantEnumerable) right).getInteger()); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThanEqual.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThanEqual.java index 2ce48f329..1808dc617 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThanEqual.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorGreaterThanEqual.java @@ -2,9 +2,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; -import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; -import dk.camelot64.kickc.model.values.ConstantLiteral; +import dk.camelot64.kickc.model.values.*; /** Binary greater-than-equal Operator ( x >= y ) */ public class OperatorGreaterThanEqual extends OperatorBinary { @@ -15,8 +13,8 @@ public class OperatorGreaterThanEqual extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(((ConstantInteger) left).getInteger() >= ((ConstantInteger) right).getInteger()); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(((ConstantEnumerable) left).getInteger() >= ((ConstantEnumerable) right).getInteger()); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThan.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThan.java index ad61da0e8..db528c5fe 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThan.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThan.java @@ -3,7 +3,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; +import dk.camelot64.kickc.model.values.ConstantEnumerable; import dk.camelot64.kickc.model.values.ConstantLiteral; /** Binary less-than Operator ( x < y ) */ @@ -15,8 +15,8 @@ public class OperatorLessThan extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(((ConstantInteger) left).getInteger() < ((ConstantInteger) right).getInteger()); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(((ConstantEnumerable) left).getInteger() < ((ConstantEnumerable) right).getInteger()); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThanEqual.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThanEqual.java index cbf042583..e84ecc471 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThanEqual.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorLessThanEqual.java @@ -2,9 +2,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; -import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; -import dk.camelot64.kickc.model.values.ConstantLiteral; +import dk.camelot64.kickc.model.values.*; /** Binary less-than-equal Operator ( x <= y ) */ public class OperatorLessThanEqual extends OperatorBinary { @@ -15,8 +13,8 @@ public class OperatorLessThanEqual extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(((ConstantInteger) left).getInteger() <= ((ConstantInteger) right).getInteger()); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(((ConstantEnumerable) left).getInteger() <= ((ConstantEnumerable) right).getInteger()); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorNotEqual.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorNotEqual.java index 48f6088d4..fb133cfab 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorNotEqual.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorNotEqual.java @@ -2,10 +2,7 @@ package dk.camelot64.kickc.model.operators; import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.types.SymbolType; -import dk.camelot64.kickc.model.values.ConstantBool; -import dk.camelot64.kickc.model.values.ConstantInteger; -import dk.camelot64.kickc.model.values.ConstantLiteral; -import dk.camelot64.kickc.model.values.ConstantPointer; +import dk.camelot64.kickc.model.values.*; import java.util.Objects; @@ -18,10 +15,8 @@ public class OperatorNotEqual extends OperatorBinary { @Override public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { - if(left instanceof ConstantInteger && right instanceof ConstantInteger) { - return new ConstantBool(!Objects.equals(((ConstantInteger) left).getInteger(), ((ConstantInteger) right).getInteger())); - } else if(left instanceof ConstantPointer && right instanceof ConstantPointer) { - return new ConstantBool(!Objects.equals(((ConstantPointer) left).getLocation(), ((ConstantPointer) right).getLocation())); + if(left instanceof ConstantEnumerable && right instanceof ConstantEnumerable) { + return new ConstantBool(!Objects.equals(((ConstantEnumerable) left).getInteger(), ((ConstantEnumerable) right).getInteger())); } throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java index 2f0c5b181..06a9175bf 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java @@ -945,16 +945,32 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { BlockScope blockScope = getCurrentScope().addBlockScope(); scopeStack.push(blockScope); loopStack.push(new Loop(blockScope)); + // Add initialization this.visit(ctx.forClassicInit()); KickCParser.StmtForContext stmtForCtx = (KickCParser.StmtForContext) ctx.getParent(); // Add label - Label repeatLabel = getCurrentScope().addLabelIntermediate(); + Label beginJumpLabel = getCurrentScope().addLabelIntermediate(); + Label doJumpLabel = getCurrentScope().addLabelIntermediate(); + Label endJumpLabel = getCurrentScope().addLabelIntermediate(); List comments = getCommentsSymbol(stmtForCtx); - StatementLabel repeatTarget = new StatementLabel(repeatLabel.getRef(), StatementSource.forClassic(ctx), comments); + StatementLabel repeatTarget = new StatementLabel(beginJumpLabel.getRef(), StatementSource.forClassic(ctx), comments); sequence.addStatement(repeatTarget); + // Add condition + KickCParser.CommaExprContext conditionCtx = ctx.commaExpr(0); + PrePostModifierHandler.addPreModifiers(this, conditionCtx, StatementSource.forClassic(ctx)); + RValue rValue = (RValue) this.visit(conditionCtx); + PrePostModifierHandler.addPostModifiers(this, conditionCtx, StatementSource.forClassic(ctx)); + // Add jump if condition was met + StatementConditionalJump doJmpStmt = new StatementConditionalJump(rValue, doJumpLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); + sequence.addStatement(doJmpStmt); + Statement endJmpStmt = new StatementJump(endJumpLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); + sequence.addStatement(endJmpStmt); + StatementLabel doJumpTarget = new StatementLabel(doJumpLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); + sequence.addStatement(doJumpTarget); + // Reuse the begin jump target for continue. + loopStack.peek().setContinueLabel(beginJumpLabel); // Add body addLoopBody(stmtForCtx.stmt()); - addLoopContinueLabel(loopStack.peek(), ctx); // Add increment KickCParser.CommaExprContext incrementCtx = ctx.commaExpr(1); if(incrementCtx != null) { @@ -962,14 +978,11 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { this.visit(incrementCtx); PrePostModifierHandler.addPostModifiers(this, incrementCtx, StatementSource.forClassic(ctx)); } - // Add condition - KickCParser.CommaExprContext conditionCtx = ctx.commaExpr(0); - PrePostModifierHandler.addPreModifiers(this, conditionCtx, StatementSource.forClassic(ctx)); - RValue rValue = (RValue) this.visit(conditionCtx); - PrePostModifierHandler.addPostModifiers(this, conditionCtx, StatementSource.forClassic(ctx)); - // Add jump if condition was met - StatementConditionalJump doJmpStmt = new StatementConditionalJump(rValue, repeatLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); - sequence.addStatement(doJmpStmt); + // Jump back to beginning + Statement beginJmpStmt = new StatementJump(beginJumpLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); + sequence.addStatement(beginJmpStmt); + StatementLabel endJumpTarget = new StatementLabel(endJumpLabel.getRef(), StatementSource.forClassic(ctx), Comment.NO_COMMENTS); + sequence.addStatement(endJumpTarget); addDirectives(doJmpStmt, stmtForCtx.directive()); addLoopBreakLabel(loopStack.pop(), ctx); scopeStack.pop(); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java index bcff14b25..294e4af93 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertUsedVars.java @@ -39,7 +39,7 @@ public class Pass1AssertUsedVars extends Pass1Base { ControlFlowBlock beginBlock = getProgram().getGraph().getBlock(new LabelRef(SymbolRef.BEGIN_BLOCK_NAME)); assertUsedVars(beginBlock, null, referenceInfos, new LinkedHashSet<>(), new LinkedHashSet<>()); getProgram().clearVariableReferenceInfos(); - new PassNStatementIndices(getProgram()).clearStatementIndices(); + getProgram().clearStatementIndices(); return false; } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIfs.java b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIfs.java index 52be32e61..c4c5150e1 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIfs.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIfs.java @@ -1,5 +1,6 @@ package dk.camelot64.kickc.passes; +import dk.camelot64.kickc.model.ConstantNotLiteral; import dk.camelot64.kickc.model.ControlFlowBlock; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.operators.Operator; @@ -41,17 +42,21 @@ public class Pass2ConstantIfs extends Pass2SsaOptimization { ConstantValue constValue1 = Pass2ConstantIdentification.getConstant(conditional.getrValue1()); Operator operator = conditional.getOperator(); ConstantValue constValue2 = Pass2ConstantIdentification.getConstant(conditional.getrValue2()); - if(conditional.getrValue1() == null && operator == null && constValue2 != null) { - // Constant condition - literal = constValue2.calculateLiteral(getScope()); - } else if(conditional.getrValue1() == null && operator != null && constValue2 != null) { - // Constant unary condition - ConstantValue constVal = Pass2ConstantIdentification.createUnary((OperatorUnary) operator, constValue2); - literal = constVal.calculateLiteral(getScope()); - } else if(constValue1 != null && operator != null && constValue2 != null) { - // Constant binary condition - ConstantValue constVal = Pass2ConstantIdentification.createBinary( constValue1, (OperatorBinary) operator, constValue2, getScope()); - literal = constVal.calculateLiteral(getScope()); + try { + if(conditional.getrValue1() == null && operator == null && constValue2 != null) { + // Constant condition + literal = constValue2.calculateLiteral(getScope()); + } else if(conditional.getrValue1() == null && operator != null && constValue2 != null) { + // Constant unary condition + ConstantValue constVal = Pass2ConstantIdentification.createUnary((OperatorUnary) operator, constValue2); + literal = constVal.calculateLiteral(getScope()); + } else if(constValue1 != null && operator != null && constValue2 != null) { + // Constant binary condition + ConstantValue constVal = Pass2ConstantIdentification.createBinary(constValue1, (OperatorBinary) operator, constValue2, getScope()); + literal = constVal.calculateLiteral(getScope()); + } + } catch (ConstantNotLiteral e) { + // not literal - keep as null } if(literal!=null && literal instanceof ConstantBool) { // Condition is a constant boolean diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopHeadConstantIdentification.java b/src/main/java/dk/camelot64/kickc/passes/Pass2LoopHeadConstantIdentification.java index 796b495e0..3bfc3be19 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2LoopHeadConstantIdentification.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2LoopHeadConstantIdentification.java @@ -6,10 +6,7 @@ import dk.camelot64.kickc.model.statements.Statement; import dk.camelot64.kickc.model.statements.StatementConditionalJump; import dk.camelot64.kickc.model.statements.StatementPhiBlock; import dk.camelot64.kickc.model.symbols.Variable; -import dk.camelot64.kickc.model.values.ConstantValue; -import dk.camelot64.kickc.model.values.LabelRef; -import dk.camelot64.kickc.model.values.PointerDereference; -import dk.camelot64.kickc.model.values.VariableRef; +import dk.camelot64.kickc.model.values.*; import dk.camelot64.kickc.passes.utils.Unroller; import java.util.ArrayList; @@ -34,10 +31,9 @@ public class Pass2LoopHeadConstantIdentification extends Pass2SsaOptimization { for(NaturalLoop loop : loopSet.getLoops()) { LabelRef loopHeadRef = loop.getHead(); ControlFlowBlock loopHeadBlock = getGraph().getBlock(loopHeadRef); - //TODO: Fix remaining errors! - //boolean modified = optimizeLoopHead(loopHeadBlock, loop, variableReferenceInfos); - boolean modified = false; + boolean modified = optimizeLoopHead(loopHeadBlock, loop, variableReferenceInfos); if(modified) { + getProgram().clearVariableReferenceInfos(); getProgram().clearStatementInfos(); getProgram().clearLoopSet(); getProgram().clearDominators(); @@ -45,7 +41,7 @@ public class Pass2LoopHeadConstantIdentification extends Pass2SsaOptimization { } } // TODO: Move to Program - new PassNStatementIndices(getProgram()).clearStatementIndices(); + getProgram().clearStatementIndices(); return false; } @@ -74,8 +70,19 @@ public class Pass2LoopHeadConstantIdentification extends Pass2SsaOptimization { // Predecessor it outside the loop if(value.getrValue() instanceof ConstantValue) { // The value is constant in the predecessor!! - // Optimization of the loop head is a good idea for this variable! - optimizeVars.add(phiVariable.getVariable()); + // Make sure it can be calculated as a literal + boolean isLiteral = true; + try { + ConstantValue constantValue = (ConstantValue) value.getrValue(); + constantValue.calculateLiteral(getProgram().getScope()); + } catch (ConstantNotLiteral e) { + // Not literal + isLiteral = false; + } + if(isLiteral) { + // Optimization of the loop head is a good idea for this variable! + optimizeVars.add(phiVariable.getVariable()); + } } } } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java b/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java index 43c03fe2a..3ede77f92 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNEliminateUnusedVars.java @@ -140,7 +140,7 @@ public class PassNEliminateUnusedVars extends Pass2SsaOptimization { } getProgram().clearVariableReferenceInfos(); - new PassNStatementIndices(getProgram()).clearStatementIndices(); + getProgram().clearStatementIndices(); return modified; } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNStatementIndices.java b/src/main/java/dk/camelot64/kickc/passes/PassNStatementIndices.java index 53a355118..1a5881a16 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNStatementIndices.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNStatementIndices.java @@ -29,16 +29,4 @@ public class PassNStatementIndices extends Pass2SsaOptimization { return false; } - /** - * Clear index numbers for all statements in the control flow graph. - */ - public void clearStatementIndices() { - for(ControlFlowBlock block : getProgram().getGraph().getAllBlocks()) { - for(Statement statement : block.getStatements()) { - statement.setIndex(null); - } - } - } - - } diff --git a/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableReferenceInfos.java b/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableReferenceInfos.java index 832bf4357..b33cfe26b 100644 --- a/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableReferenceInfos.java +++ b/src/main/java/dk/camelot64/kickc/passes/calcs/PassNCalcVariableReferenceInfos.java @@ -183,32 +183,19 @@ public class PassNCalcVariableReferenceInfos extends PassNCalcBase getDefinedVars(Statement stmt) { - if(stmt instanceof StatementAssignment) { - StatementAssignment assignment = (StatementAssignment) stmt; - LValue lValue = assignment.getlValue(); - if(lValue instanceof VariableRef) { - return Collections.singletonList((VariableRef) lValue); - } - } else if(stmt instanceof StatementPhiBlock) { + public static Collection getDefinedVars(Statement stmt) { + if(stmt instanceof StatementPhiBlock) { List defined = new ArrayList<>(); StatementPhiBlock phi = (StatementPhiBlock) stmt; for(StatementPhiBlock.PhiVariable phiVariable : phi.getPhiVariables()) { defined.add(phiVariable.getVariable()); } return defined; - } else if(stmt instanceof StatementCall) { - List defined = new ArrayList<>(); - if(((StatementCall) stmt).getlValue() instanceof VariableRef) { - defined.add((VariableRef) ((StatementCall) stmt).getlValue()); + } else if(stmt instanceof StatementLValue) { + LValue lValue = ((StatementLValue) stmt).getlValue(); + if(lValue instanceof VariableRef) { + return Collections.singletonList((VariableRef) lValue); } - return defined; - } else if(stmt instanceof StatementCallPointer) { - List defined = new ArrayList<>(); - if(((StatementCallPointer) stmt).getlValue() instanceof VariableRef) { - defined.add((VariableRef) ((StatementCallPointer) stmt).getlValue()); - } - return defined; } return new ArrayList<>(); } diff --git a/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java b/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java index 06d2fedf7..538b1e534 100644 --- a/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java +++ b/src/main/java/dk/camelot64/kickc/passes/utils/Unroller.java @@ -10,8 +10,11 @@ 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 dk.camelot64.kickc.passes.Pass1GenerateSingleStaticAssignmentForm; +import dk.camelot64.kickc.passes.calcs.PassNCalcVariableReferenceInfos; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; /** * Utility for copying blocks in a program - typically to unroll loops or conditions. @@ -57,12 +60,10 @@ public class Unroller { 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(); - - if(program.getLog().isVerboseSSAOptimize()) { - program.getLog().append("CONTROL FLOW GRAPH (PREPARED)"); + if(program.getLog().isVerboseLoopUnroll()) { + program.getLog().append("CONTROL FLOW GRAPH (PREPARED FOR LOOP HEAD UNROLL)"); program.getLog().append(program.getGraph().toString(program)); } - // 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 @@ -72,70 +73,144 @@ public class Unroller { } /** - * Ensure that all variables defined inside the blocks to be copied has a PHI in successor blocks. + * Ensure that variables defined inside and used outside the blocks to be copied has different versions in different successors blocks. */ private void prepare() { for(VariableRef origVarRef : getVarsDefinedIn(unrollBlocks, program)) { // Find out if the variable is ever referenced outside the loop if(isReferencedOutside(origVarRef, 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 origVar = program.getScope().getVariable(origVarRef); - Variable newVar; - if(origVar instanceof VariableVersion) { - newVar = ((VariableVersion) origVar).getVersionOf().createVersion(); - } else { - newVar = origVar.getScope().addVariableIntermediate(); - } - // Replace all references from the new phi and forward - forwardReplaceAllUsages(successorTransition.successor, origVarRef, newVar.getRef(), new LinkedHashSet<>()); - // Create the new phi-variable in the successor phi block - StatementPhiBlock.PhiVariable newPhiVar = phiBlock.addPhiVariable(newVar.getRef()); - newPhiVar.setrValue(successorTransition.predecessor, origVarRef); - program.getLog().append("Creating PHI for " + origVarRef.getFullName() + " in block " + successorBlock.getLabel() + " - " + phiBlock.toString(program, false)); - + // Re-version all usages of the specific variable version + Map newPhis = new LinkedHashMap<>(); + Map varVersions = new LinkedHashMap<>(); + reVersionAllUsages(origVarRef, newPhis, varVersions); + if(program.getLog().isVerboseLoopUnroll()) { + program.getLog().append("Created new versions for " + origVarRef + ")"); + //program.getLog().append(program.getGraph().toString(program)); } + // Recursively fill out & add PHI-functions until they have propagated everywhere needed + completePhiFunctions(newPhis, varVersions); } } } /** - * Introduces a new version of a variable - and replaces all uses of the old variable with the new one from a specific point in the control flow graph and forward until the old variable is defined. - * @param blockRef The block to replace the usage from - * @param origVarRef The original variable - * @param newVarRef The new variable replacing the original - * @param visited All blocks that have already been visited. + * Find all usages of a variable and create new versions for each usage. + * + * @param origVarRef The original variable where all usages must have new versions + * @param newPhis Map that will be populated with all new (empty) PHI-variables for the new versionw - these will be populated later. + * @param varVersions Map that will be populated with the version of the origVariable at the end of each block where it has a defined version. */ - private void forwardReplaceAllUsages(LabelRef blockRef, VariableRef origVarRef, VariableRef newVarRef, Set visited) { - VariableReferenceInfos variableReferenceInfos = program.getVariableReferenceInfos(); - LinkedHashMap aliases = new LinkedHashMap<>(); - aliases.put(origVarRef, newVarRef); - AliasReplacer aliasReplacer = new AliasReplacer(aliases); - ControlFlowBlock block = program.getGraph().getBlock(blockRef); - if(block!=null) { + private void reVersionAllUsages(VariableRef origVarRef, Map newPhis, Map varVersions) { + + // First add the definition of origVar to varVersions + for(ControlFlowBlock block : program.getGraph().getAllBlocks()) { for(Statement statement : block.getStatements()) { - Collection definedVars = variableReferenceInfos.getDefinedVars(statement); - if(definedVars!=null && definedVars.contains(origVarRef)) { - // Found definition of the original variable - don't replace any more - return; + Collection definedVars = PassNCalcVariableReferenceInfos.getDefinedVars(statement); + if(definedVars.contains(origVarRef)) { + varVersions.put(block.getLabel(), origVarRef); } - // Replace any usage in the statement - ProgramValueIterator.execute(statement, aliasReplacer, null, block); } } - visited.add(blockRef); - if(block!=null) { - if(block.getConditionalSuccessor() != null && !visited.contains(block.getConditionalSuccessor())) { - forwardReplaceAllUsages(block.getConditionalSuccessor(), origVarRef, newVarRef, visited); + // Next iterate the entire graph ensuring that all usages create new versions (except usages right after the definition) + for(ControlFlowBlock block : program.getGraph().getAllBlocks()) { + AtomicReference currentVersion = new AtomicReference<>(); + // Set current version from map + currentVersion.set(varVersions.get(block.getLabel())); + for(Statement statement : block.getStatements()) { + ProgramValueIterator.execute(statement, (programValue, currentStmt, stmtIt, currentBlock) -> { + Value value = programValue.get(); + if(origVarRef.equals(value)) { + // Found a reference! + if(statement instanceof StatementPhiBlock && programValue instanceof ProgramValue.PhiVariable) { + // This is the definition - don't replace it + currentVersion.set(origVarRef); + varVersions.put(block.getLabel(), origVarRef); + } else if(statement instanceof StatementLValue && programValue instanceof ProgramValue.ProgramValueLValue) { + // This is the definition - don't replace it + currentVersion.set(origVarRef); + varVersions.put(block.getLabel(), origVarRef); + } else if(statement instanceof StatementPhiBlock && programValue instanceof ProgramValue.PhiValue) { + // The reference is inside a PHI-value - we need a version in the predecessor + LabelRef predecessor = ((ProgramValue.PhiValue) programValue).getPredecessor(); + VariableRef predecessorVersion = varVersions.get(predecessor); + if(predecessorVersion == null) { + // Add a new PHI to the predecessor + predecessorVersion = createNewVersion(origVarRef); + varVersions.put(predecessor, predecessorVersion); + newPhis.put(predecessor, predecessorVersion); + } + // Use the definition + programValue.set(predecessorVersion); + } else if(currentVersion.get() == null) { + // Found a reference - no definition - create a new version + VariableRef newVarRef = createNewVersion(origVarRef); + currentVersion.set(newVarRef); + varVersions.put(block.getLabel(), newVarRef); + newPhis.put(block.getLabel(), currentVersion.get()); + // Use the definition + programValue.set(newVarRef); + } else { + programValue.set(currentVersion.get()); + } + } + }, null, null); } - if(block.getDefaultSuccessor() != null && !visited.contains(block.getDefaultSuccessor())) { - forwardReplaceAllUsages(block.getDefaultSuccessor(), origVarRef, newVarRef, visited); - } - if(block.getCallSuccessor() != null && !visited.contains(block.getCallSuccessor())) { - forwardReplaceAllUsages(block.getCallSuccessor(), origVarRef, newVarRef, visited); + } + // Add the new empty PHI-blocks() + for(LabelRef blockRef : newPhis.keySet()) { + ControlFlowBlock block = program.getGraph().getBlock(blockRef); + VariableRef newVersion = newPhis.get(blockRef); + block.getPhiBlock().addPhiVariable(newVersion); + } + + } + + /** + * Create a new version of a variable + * @param origVarRef The original variable + * @return The new version + */ + private VariableRef createNewVersion(VariableRef origVarRef) { + Variable origVar = program.getScope().getVariable(origVarRef); + Scope scope = origVar.getScope(); + VariableRef newVarRef; + if(origVarRef.isIntermediate()) { + newVarRef = scope.addVariableIntermediate().getRef(); + } else { + newVarRef = ((VariableVersion) origVar).getVersionOf().createVersion().getRef(); + } + return newVarRef; + } + + /** + * Look through all new phi-functions and fill out their parameters. + * Both passed maps are modified + * + * @param newPhis New (empty) PHI-variables for the new versions that need to be populated + * @param varVersions Map with the version of the origVariable at the end of each block where it has a defined version. + */ + private void completePhiFunctions(Map newPhis, Map varVersions) { + Map todo = newPhis; + while(todo.size() > 0) { + Map doing = todo; + todo = new LinkedHashMap<>(); + for(LabelRef blockRef : doing.keySet()) { + VariableRef doingVarRef = doing.get(blockRef); + ControlFlowBlock block = program.getGraph().getBlock(blockRef); + StatementPhiBlock.PhiVariable doingPhiVariable = block.getPhiBlock().getPhiVariable(doingVarRef); + List predecessors = Pass1GenerateSingleStaticAssignmentForm.getPhiPredecessors(block, program); + for(ControlFlowBlock predecessor : predecessors) { + VariableRef predecessorVarRef = varVersions.get(predecessor.getLabel()); + if(predecessorVarRef == null) { + // Variable has no version in the predecessor block - add a new PHI and populate later! + VariableRef newVarRef = createNewVersion(doingVarRef); + predecessor.getPhiBlock().addPhiVariable(newVarRef); + varVersions.put(predecessor.getLabel(), newVarRef); + todo.put(predecessor.getLabel(), newVarRef); + predecessorVarRef = newVarRef; + } + doingPhiVariable.setrValue(predecessor.getLabel(), predecessorVarRef); + } } } } @@ -272,7 +347,7 @@ public class Unroller { } /** - * Patch the PHI-block of an external successor block. Ensures that the PHI-block also receives data from the new coped block. + * Patch the PHI-block of an external successor block. Ensures that the PHI-block also receives data from the new copied block. * * @param successor The successor block's label * @param origBlock The label of the original block diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 25409372b..c50a7f03e 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -1952,8 +1952,8 @@ public class TestPrograms { } @Test - public void testBoolIfs() throws IOException, URISyntaxException { - compileAndCompare("bool-ifs"); + public void testBoolIfsMin() throws IOException, URISyntaxException { + compileAndCompare("bool-ifs-min"); } @Test diff --git a/src/test/kc/bool-ifs-min.kc b/src/test/kc/bool-ifs-min.kc new file mode 100644 index 000000000..b5ee3cc2a --- /dev/null +++ b/src/test/kc/bool-ifs-min.kc @@ -0,0 +1,12 @@ +// A test of boolean conditions using && || and ! + +void main() { + const char* screen = 0x400; + for( char i : 0..20) { + if( (i<10) && ((i&1)==0) ) { + screen[i] = '*'; + } + } +} + + diff --git a/src/test/ref/bool-ifs-min.asm b/src/test/ref/bool-ifs-min.asm new file mode 100644 index 000000000..cd01e9f93 --- /dev/null +++ b/src/test/ref/bool-ifs-min.asm @@ -0,0 +1,22 @@ +// A test of boolean conditions using && || and ! +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +main: { + .label screen = $400 + ldx #0 + b1: + txa + and #1 + cpx #$a + bcs b2 + cmp #0 + bne b2 + lda #'*' + sta screen,x + b2: + inx + cpx #$15 + bne b1 + rts +} diff --git a/src/test/ref/bool-ifs-min.cfg b/src/test/ref/bool-ifs-min.cfg new file mode 100644 index 000000000..a3fd52baf --- /dev/null +++ b/src/test/ref/bool-ifs-min.cfg @@ -0,0 +1,30 @@ +@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 main::@2 + [5] (byte) main::i#2 ← phi( main/(byte) 0 main::@2/(byte) main::i#1 ) + [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 + [7] if((byte) main::i#2>=(byte) $a) goto main::@2 + to:main::@4 +main::@4: scope:[main] from main::@1 + [8] if((byte~) main::$1!=(byte) 0) goto main::@2 + to:main::@3 +main::@3: scope:[main] from main::@4 + [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' + to:main::@2 +main::@2: scope:[main] from main::@1 main::@3 main::@4 + [10] (byte) main::i#1 ← ++ (byte) main::i#2 + [11] if((byte) main::i#1!=(byte) $15) goto main::@1 + to:main::@return +main::@return: scope:[main] from main::@2 + [12] return + to:@return diff --git a/src/test/ref/bool-ifs-min.log b/src/test/ref/bool-ifs-min.log new file mode 100644 index 000000000..622e7cff4 --- /dev/null +++ b/src/test/ref/bool-ifs-min.log @@ -0,0 +1,458 @@ +Culled Empty Block (label) main::@4 + +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::@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) $a + (number~) main::$1 ← (byte) main::i#2 & (number) 1 + (bool~) main::$2 ← (number~) main::$1 == (number) 0 + (bool~) main::$3 ← (bool~) main::$0 && (bool~) main::$2 + (bool~) main::$4 ← ! (bool~) main::$3 + if((bool~) main::$4) goto main::@2 + to:main::@3 +main::@2: scope:[main] from main::@1 main::@3 + (byte) main::i#3 ← phi( main::@1/(byte) main::i#2 main::@3/(byte) main::i#4 ) + (byte) main::i#1 ← (byte) main::i#3 + rangenext(0,$14) + (bool~) main::$5 ← (byte) main::i#1 != rangelast(0,$14) + if((bool~) main::$5) goto main::@1 + to:main::@return +main::@3: scope:[main] from main::@1 + (byte) main::i#4 ← phi( main::@1/(byte) main::i#2 ) + *((byte*) main::screen#0 + (byte) main::i#4) ← (byte) '*' + to:main::@2 +main::@return: scope:[main] from main::@2 + 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 +(number~) main::$1 +(bool~) main::$2 +(bool~) main::$3 +(bool~) main::$4 +(bool~) main::$5 +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@return +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 +(byte) main::i#3 +(byte) main::i#4 +(byte*) main::screen +(byte*) main::screen#0 + +Adding number conversion cast (unumber) $a in (bool~) main::$0 ← (byte) main::i#2 < (number) $a +Adding number conversion cast (unumber) 1 in (number~) main::$1 ← (byte) main::i#2 & (number) 1 +Adding number conversion cast (unumber) main::$1 in (number~) main::$1 ← (byte) main::i#2 & (unumber)(number) 1 +Adding number conversion cast (unumber) 0 in (bool~) main::$2 ← (unumber~) main::$1 == (number) 0 +Successful SSA optimization PassNAddNumberTypeConversions +Inlining cast (byte*) main::screen#0 ← (byte*)(number) $400 +Successful SSA optimization Pass2InlineCast +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast $a +Simplifying constant integer cast 1 +Simplifying constant integer cast 0 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) $a +Finalized unsigned number type (byte) 1 +Finalized unsigned number type (byte) 0 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Inferred type updated to byte in (unumber~) main::$1 ← (byte) main::i#2 & (byte) 1 +Alias (byte) main::i#2 = (byte) main::i#4 +Successful SSA optimization Pass2AliasElimination +Alias (byte) main::i#2 = (byte) main::i#3 +Successful SSA optimization Pass2AliasElimination +Simple Condition (bool~) main::$5 [12] if((byte) main::i#1!=rangelast(0,$14)) goto main::@1 +Successful SSA optimization Pass2ConditionalJumpSimplification +Rewriting ! if()-condition to reversed if() [7] (bool~) main::$4 ← ! (bool~) main::$3 +Successful SSA optimization Pass2ConditionalAndOrRewriting +Rewriting && if()-condition to two if()s [6] (bool~) main::$3 ← (bool~) main::$0 && (bool~) main::$2 +Successful SSA optimization Pass2ConditionalAndOrRewriting +Constant (const byte*) main::screen#0 = (byte*) 1024 +Constant (const byte) main::i#0 = 0 +Successful SSA optimization Pass2ConstantIdentification +Resolved ranged next value [10] main::i#1 ← ++ main::i#2 to ++ +Resolved ranged comparison value [12] if(main::i#1!=rangelast(0,$14)) goto main::@1 to (number) $15 +Adding number conversion cast (unumber) $15 in if((byte) main::i#1!=(number) $15) goto main::@1 +Successful SSA optimization PassNAddNumberTypeConversions +Simplifying constant integer cast $15 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) $15 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Simple Condition (bool~) main::$0 [4] if((byte) main::i#2<(byte) $a) goto main::@5 +Simple Condition (bool~) main::$2 [10] if((byte~) main::$1==(byte) 0) goto main::@3 +Successful SSA optimization Pass2ConditionalJumpSimplification +Negating conditional jump and destination [4] if((byte) main::i#2>=(byte) $a) goto main::@2 +Negating conditional jump and destination [10] if((byte~) main::$1!=(byte) 0) goto main::@2 +Successful SSA optimization Pass2ConditionalJumpSequenceImprovement +Inlining constant with var siblings (const byte) main::i#0 +Constant inlined main::i#0 = (byte) 0 +Successful SSA optimization Pass2ConstantInlining +Added new block during phi lifting main::@6(between main::@2 and main::@1) +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 1 initial phi equivalence classes +Coalesced [14] main::i#5 ← main::i#1 +Coalesced down to 1 phi equivalence classes +Culled Empty Block (label) @2 +Culled Empty Block (label) main::@6 +Renumbering block main::@5 to main::@4 +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 main::@2 + [5] (byte) main::i#2 ← phi( main/(byte) 0 main::@2/(byte) main::i#1 ) + [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 + [7] if((byte) main::i#2>=(byte) $a) goto main::@2 + to:main::@4 +main::@4: scope:[main] from main::@1 + [8] if((byte~) main::$1!=(byte) 0) goto main::@2 + to:main::@3 +main::@3: scope:[main] from main::@4 + [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' + to:main::@2 +main::@2: scope:[main] from main::@1 main::@3 main::@4 + [10] (byte) main::i#1 ← ++ (byte) main::i#2 + [11] if((byte) main::i#1!=(byte) $15) goto main::@1 + to:main::@return +main::@return: scope:[main] from main::@2 + [12] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte~) main::$1 11.0 +(byte) main::i +(byte) main::i#1 16.5 +(byte) main::i#2 11.0 +(byte*) main::screen + +Initial phi equivalence classes +[ main::i#2 main::i#1 ] +Added variable main::$1 to zero page equivalence class [ main::$1 ] +Complete equivalence classes +[ main::i#2 main::i#1 ] +[ main::$1 ] +Allocated zp ZP_BYTE:2 [ main::i#2 main::i#1 ] +Allocated zp ZP_BYTE:3 [ main::$1 ] + +INITIAL ASM +Target platform is c64basic + // File Comments +// A test of boolean conditions using && || and ! + // 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 + .label _1 = 3 + .label i = 2 + // [5] phi from main to main::@1 [phi:main->main::@1] + b1_from_main: + // [5] phi (byte) main::i#2 = (byte) 0 [phi:main->main::@1#0] -- vbuz1=vbuc1 + lda #0 + sta i + jmp b1 + // [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + b1_from_b2: + // [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + jmp b1 + // main::@1 + b1: + // [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 -- vbuz1=vbuz2_band_vbuc1 + lda #1 + and i + sta _1 + // [7] if((byte) main::i#2>=(byte) $a) goto main::@2 -- vbuz1_ge_vbuc1_then_la1 + lda i + cmp #$a + bcs b2 + jmp b4 + // main::@4 + b4: + // [8] if((byte~) main::$1!=(byte) 0) goto main::@2 -- vbuz1_neq_0_then_la1 + lda _1 + cmp #0 + bne b2 + jmp b3 + // main::@3 + b3: + // [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' -- pbuc1_derefidx_vbuz1=vbuc2 + lda #'*' + ldy i + sta screen,y + jmp b2 + // main::@2 + b2: + // [10] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuz1=_inc_vbuz1 + inc i + // [11] if((byte) main::i#1!=(byte) $15) goto main::@1 -- vbuz1_neq_vbuc1_then_la1 + lda #$15 + cmp i + bne b1_from_b2 + jmp breturn + // main::@return + breturn: + // [12] return + rts +} + // File Data + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' [ main::i#2 ] ( main:2 [ main::i#2 ] ) always clobbers reg byte a +Removing always clobbered register reg byte a as potential for zp ZP_BYTE:2 [ main::i#2 main::i#1 ] +Statement [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 [ main::i#2 main::$1 ] ( main:2 [ main::i#2 main::$1 ] ) always clobbers reg byte a +Statement [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' [ main::i#2 ] ( main:2 [ main::i#2 ] ) always clobbers reg byte a +Potential registers zp ZP_BYTE:2 [ main::i#2 main::i#1 ] : zp ZP_BYTE:2 , reg byte x , reg byte y , +Potential registers zp ZP_BYTE:3 [ main::$1 ] : zp ZP_BYTE:3 , reg byte a , reg byte x , reg byte y , + +REGISTER UPLIFT SCOPES +Uplift Scope [main] 27.5: zp ZP_BYTE:2 [ main::i#2 main::i#1 ] 11: zp ZP_BYTE:3 [ main::$1 ] +Uplift Scope [] + +Uplifting [main] best 483 combination reg byte x [ main::i#2 main::i#1 ] reg byte a [ main::$1 ] +Uplifting [] best 483 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// A test of boolean conditions using && || and ! + // 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 + // [5] phi from main to main::@1 [phi:main->main::@1] + b1_from_main: + // [5] phi (byte) main::i#2 = (byte) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + jmp b1 + // [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + b1_from_b2: + // [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + jmp b1 + // main::@1 + b1: + // [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 -- vbuaa=vbuxx_band_vbuc1 + txa + and #1 + // [7] if((byte) main::i#2>=(byte) $a) goto main::@2 -- vbuxx_ge_vbuc1_then_la1 + cpx #$a + bcs b2 + jmp b4 + // main::@4 + b4: + // [8] if((byte~) main::$1!=(byte) 0) goto main::@2 -- vbuaa_neq_0_then_la1 + cmp #0 + bne b2 + jmp b3 + // main::@3 + b3: + // [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' -- pbuc1_derefidx_vbuxx=vbuc2 + lda #'*' + sta screen,x + jmp b2 + // main::@2 + b2: + // [10] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [11] if((byte) main::i#1!=(byte) $15) goto main::@1 -- vbuxx_neq_vbuc1_then_la1 + cpx #$15 + bne b1_from_b2 + jmp breturn + // main::@return + breturn: + // [12] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp b1 +Removing instruction jmp b4 +Removing instruction jmp b3 +Removing instruction jmp b2 +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Replacing label b1_from_b2 with b1 +Removing instruction b1_from_bbegin: +Removing instruction b1: +Removing instruction main_from_b1: +Removing instruction bend_from_b1: +Removing instruction b1_from_b2: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction bend: +Removing instruction b1_from_main: +Removing instruction b4: +Removing instruction b3: +Removing instruction breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Updating BasicUpstart to call main directly +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin +Removing instruction jmp b1 +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction bbegin: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(byte~) main::$1 reg byte a 11.0 +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 16.5 +(byte) main::i#2 reg byte x 11.0 +(byte*) main::screen +(const byte*) main::screen#0 screen = (byte*) 1024 + +reg byte x [ main::i#2 main::i#1 ] +reg byte a [ main::$1 ] + + +FINAL ASSEMBLER +Score: 291 + + // File Comments +// A test of boolean conditions using && || and ! + // 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 + // [5] phi from main to main::@1 [phi:main->main::@1] + // [5] phi (byte) main::i#2 = (byte) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + // [5] phi from main::@2 to main::@1 [phi:main::@2->main::@1] + // [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@2->main::@1#0] -- register_copy + // main::@1 + b1: + // i&1 + // [6] (byte~) main::$1 ← (byte) main::i#2 & (byte) 1 -- vbuaa=vbuxx_band_vbuc1 + txa + and #1 + // if( (i<10) && ((i&1)==0) ) + // [7] if((byte) main::i#2>=(byte) $a) goto main::@2 -- vbuxx_ge_vbuc1_then_la1 + cpx #$a + bcs b2 + // main::@4 + // [8] if((byte~) main::$1!=(byte) 0) goto main::@2 -- vbuaa_neq_0_then_la1 + cmp #0 + bne b2 + // main::@3 + // screen[i] = '*' + // [9] *((const byte*) main::screen#0 + (byte) main::i#2) ← (byte) '*' -- pbuc1_derefidx_vbuxx=vbuc2 + lda #'*' + sta screen,x + // main::@2 + b2: + // for( char i : 0..20) + // [10] (byte) main::i#1 ← ++ (byte) main::i#2 -- vbuxx=_inc_vbuxx + inx + // [11] if((byte) main::i#1!=(byte) $15) goto main::@1 -- vbuxx_neq_vbuc1_then_la1 + cpx #$15 + bne b1 + // main::@return + // } + // [12] return + rts +} + // File Data + diff --git a/src/test/ref/bool-ifs-min.sym b/src/test/ref/bool-ifs-min.sym new file mode 100644 index 000000000..77d304fc3 --- /dev/null +++ b/src/test/ref/bool-ifs-min.sym @@ -0,0 +1,18 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(byte~) main::$1 reg byte a 11.0 +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 16.5 +(byte) main::i#2 reg byte x 11.0 +(byte*) main::screen +(const byte*) main::screen#0 screen = (byte*) 1024 + +reg byte x [ main::i#2 main::i#1 ] +reg byte a [ main::$1 ]