diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFormat.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFormat.java index f91320481..4612af26f 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFormat.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFormat.java @@ -1,14 +1,16 @@ package dk.camelot64.kickc.fragment; -import dk.camelot64.kickc.model.*; +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.operators.Operator; +import dk.camelot64.kickc.model.operators.OperatorBinary; import dk.camelot64.kickc.model.operators.Operators; -import dk.camelot64.kickc.model.values.*; import dk.camelot64.kickc.model.symbols.ConstantVar; import dk.camelot64.kickc.model.symbols.Variable; import dk.camelot64.kickc.model.types.SymbolType; import dk.camelot64.kickc.model.types.SymbolTypeInference; import dk.camelot64.kickc.model.types.SymbolTypePointer; +import dk.camelot64.kickc.model.values.*; /** Formatting of numbers, constants, names and more for KickAssembler */ public class AsmFormat { @@ -43,13 +45,11 @@ public class AsmFormat { (parenthesis ? ")" : ""); } else if(value instanceof ConstantBinary) { ConstantBinary binary = (ConstantBinary) value; - Operator operator = binary.getOperator(); + OperatorBinary operator = binary.getOperator(); boolean parenthesis = operator.getPrecedence() > precedence; return (parenthesis ? "(" : "") + - getAsmConstant(program, binary.getLeft(), operator.getPrecedence(), codeScope) + - operator.getOperator() + - getAsmConstant(program, binary.getRight(), operator.getPrecedence(), codeScope) + + getAsmConstantBinary(program, binary.getLeft(), operator, binary.getRight(), codeScope) + (parenthesis ? ")" : ""); } else if(value instanceof ConstantVarPointer) { VariableRef toVar = ((ConstantVarPointer) value).getToVar(); @@ -60,6 +60,30 @@ public class AsmFormat { } } + /** + * Get ASM for a binary constant expression + * @param program The program + * @param left The left operand of the expression + * @param operator The binary operator + * @param left The left operand of the expression + * @param codeScope The scope containing the code being generated. + * @return + */ + private static String getAsmConstantBinary(Program program, ConstantValue left, OperatorBinary operator, ConstantValue right, ScopeRef codeScope) { + if(Operators.REMAINDER.equals(operator)) { + // Remainder operator % not supported by KickAss - use modulo function instead + return "mod("+ + getAsmConstant(program, left, operator.getPrecedence(), codeScope) + + "," + + getAsmConstant(program, right, operator.getPrecedence(), codeScope)+ + ")"; + } else { + return getAsmConstant(program, left, operator.getPrecedence(), codeScope) + + operator.getOperator() + + getAsmConstant(program, right, operator.getPrecedence(), codeScope); + } + } + /** * Get ASM code for a constant unary expression * diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorDivide.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorDivide.java index affbc45c7..1e1ce9cd6 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/OperatorDivide.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorDivide.java @@ -6,7 +6,7 @@ import dk.camelot64.kickc.model.values.ConstantInteger; import dk.camelot64.kickc.model.values.ConstantLiteral; import dk.camelot64.kickc.model.values.ConstantPointer; -/** Binary division Operator ( x / y ) */ +/** Numeric division Operator ( x / y ) */ public class OperatorDivide extends OperatorBinary { public OperatorDivide(int precedence) { diff --git a/src/main/java/dk/camelot64/kickc/model/operators/OperatorRemainder.java b/src/main/java/dk/camelot64/kickc/model/operators/OperatorRemainder.java new file mode 100644 index 000000000..4c31b2d2f --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/model/operators/OperatorRemainder.java @@ -0,0 +1,38 @@ +package dk.camelot64.kickc.model.operators; + +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.types.*; +import dk.camelot64.kickc.model.values.ConstantInteger; +import dk.camelot64.kickc.model.values.ConstantLiteral; +import dk.camelot64.kickc.model.values.ConstantPointer; + +/** Numeric division Operator ( x % y ) */ +public class OperatorRemainder extends OperatorBinary { + + public OperatorRemainder(int precedence) { + super("%", "_rem_", precedence); + } + + @Override + public ConstantLiteral calculateLiteral(ConstantLiteral left, ConstantLiteral right) { + if(left instanceof ConstantInteger && right instanceof ConstantInteger) { + return new ConstantInteger(((ConstantInteger) left).getInteger() % ((ConstantInteger) right).getInteger()); + } else if(left instanceof ConstantPointer && right instanceof ConstantInteger) { + return new ConstantInteger(((ConstantPointer) left).getLocation() % ((ConstantInteger) right).getInteger()); + } + throw new CompileError("Calculation not implemented " + left + " " + getOperator() + " " + right); + } + + @Override + public SymbolType inferType(SymbolTypeSimple left, SymbolTypeSimple right) { + // Handle numeric types through proper promotion + if(SymbolType.isInteger(left) && SymbolType.isInteger(right)) { + return SymbolType.promotedMathType((SymbolTypeInteger) left, (SymbolTypeInteger) right); + } + if(left instanceof ConstantPointer && right instanceof ConstantInteger) { + return ((ConstantInteger) right).getType(); + } + throw new RuntimeException("Type inference case not handled " + left + " " + getOperator() + " " + right); + } + +} diff --git a/src/main/java/dk/camelot64/kickc/model/operators/Operators.java b/src/main/java/dk/camelot64/kickc/model/operators/Operators.java index ff88c7e89..887a45ec4 100644 --- a/src/main/java/dk/camelot64/kickc/model/operators/Operators.java +++ b/src/main/java/dk/camelot64/kickc/model/operators/Operators.java @@ -30,6 +30,7 @@ public class Operators { public static final OperatorUnary CAST_BOOL= new OperatorCastBool(2); public static final OperatorBinary MULTIPLY = new OperatorMultiply(3); public static final OperatorBinary DIVIDE = new OperatorDivide(3); + public static final OperatorBinary REMAINDER = new OperatorRemainder(3); public static final OperatorBinary PLUS = new OperatorPlus(4); public static final OperatorBinary MINUS = new OperatorMinus(4); public static final OperatorBinary SHIFT_LEFT = new OperatorShiftLeft(5); @@ -58,6 +59,8 @@ public class Operators { return MULTIPLY; case "/": return DIVIDE; + case "%": + return REMAINDER; case "==": return EQ; case "!=": diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java index 6032ffa5c..1839cb80a 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java @@ -261,6 +261,7 @@ public class Pass2ConstantIdentification extends Pass2SsaOptimization { } case "*": case "/": + case "%": case "&": case "|": case "&&": diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 6f6cc86fa..6cb5d618a 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -44,6 +44,16 @@ public class TestPrograms { AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false); } + @Test + public void testConcatChar() throws IOException, URISyntaxException { + compileAndCompare("concat-char"); + } + + @Test + public void testConstMultDiv() throws IOException, URISyntaxException { + compileAndCompare("const-mult-div"); + } + @Test public void testDoubleAssignment() throws IOException, URISyntaxException { compileAndCompare("double-assignment"); @@ -769,6 +779,22 @@ public class TestPrograms { assertError("const-pointer-modify", "Constants can not be modified"); } + @Test + public void testNoMulRuntime() throws IOException, URISyntaxException { + assertError("no-mul-runtime", "No runtime support"); + } + + @Test + public void testNoDivRuntime() throws IOException, URISyntaxException { + assertError("no-div-runtime", "No runtime support"); + } + + @Test + public void testNoRemRuntime() throws IOException, URISyntaxException { + assertError("no-rem-runtime", "No runtime support"); + } + + private void assertError(String kcFile, String expectError) throws IOException, URISyntaxException { try { compileAndCompare(kcFile); diff --git a/src/test/java/dk/camelot64/kickc/test/kc/concat-char.kc b/src/test/java/dk/camelot64/kickc/test/kc/concat-char.kc new file mode 100644 index 000000000..d047a8c00 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/concat-char.kc @@ -0,0 +1,10 @@ +// Concatenate a char to a string + +void main() { + byte* screen = $400; + byte l = 'l'; + byte[] msg = "cm"+l; + for( byte i: 0..2 ) { + screen[i] = msg[i]; + } +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/kc/const-mult-div.kc b/src/test/java/dk/camelot64/kickc/test/kc/const-mult-div.kc new file mode 100644 index 000000000..7a6a50242 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/const-mult-div.kc @@ -0,0 +1,7 @@ +// Test a constant with multiplication and division + +void main() { + byte b = 6*(14/3) + 22%3; + byte* screen = $400; + screen[0] = b; +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/kc/no-div-runtime.kc b/src/test/java/dk/camelot64/kickc/test/kc/no-div-runtime.kc new file mode 100644 index 000000000..16983f520 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/no-div-runtime.kc @@ -0,0 +1,8 @@ +// Test that division at runtime gives a proper error + +void main() { + byte* screen = $400; + for (byte i: 2..5) { + screen[i] = 100/i; + } +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/kc/no-mul-runtime.kc b/src/test/java/dk/camelot64/kickc/test/kc/no-mul-runtime.kc new file mode 100644 index 000000000..4673617d3 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/no-mul-runtime.kc @@ -0,0 +1,8 @@ +// Test that division at runtime gives a proper error + +void main() { + byte* screen = $400; + for (byte i: 2..5) { + screen[i] = 5*i; + } +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/kc/no-rem-runtime.kc b/src/test/java/dk/camelot64/kickc/test/kc/no-rem-runtime.kc new file mode 100644 index 000000000..c3812f1e1 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/no-rem-runtime.kc @@ -0,0 +1,8 @@ +// Test that remainder at runtime gives a proper error + +void main() { + byte* screen = $400; + for (byte i: 2..5) { + screen[i] = 100%i; + } +} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/ref/concat-char.asm b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.asm new file mode 100644 index 000000000..132322c86 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.asm @@ -0,0 +1,16 @@ +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + jsr main +main: { + .label screen = $400 + ldx #0 + b1: + lda msg,x + sta screen,x + inx + cpx #3 + bne b1 + rts + msg: .text "cm"+'l' +} diff --git a/src/test/java/dk/camelot64/kickc/test/ref/concat-char.cfg b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.cfg new file mode 100644 index 000000000..1c2e36280 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.cfg @@ -0,0 +1,21 @@ +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + [1] phi() [ ] ( ) + [2] call main param-assignment [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] phi() [ ] ( main:2 [ ] ) + to:main::@1 +main::@1: scope:[main] from main main::@1 + [5] (byte) main::i#2 ← phi( main/(byte/signed byte/word/signed word/dword/signed dword) 0 main::@1/(byte) main::i#1 ) [ main::i#2 ] ( main:2 [ main::i#2 ] ) + [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] ) + [7] (byte) main::i#1 ← ++ (byte) main::i#2 [ main::i#1 ] ( main:2 [ main::i#1 ] ) + [8] if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 [ main::i#1 ] ( main:2 [ main::i#1 ] ) + to:main::@return +main::@return: scope:[main] from main::@1 + [9] return [ ] ( main:2 [ ] ) + to:@return diff --git a/src/test/java/dk/camelot64/kickc/test/ref/concat-char.log b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.log new file mode 100644 index 000000000..05ca15761 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.log @@ -0,0 +1,418 @@ +PARSING src/test/java/dk/camelot64/kickc/test/kc/concat-char.kc +// Concatenate a char to a string + +void main() { + byte* screen = $400; + byte l = 'l'; + byte[] msg = "cm"+l; + for( byte i: 0..2 ) { + screen[i] = msg[i]; + } +} +SYMBOLS +(label) @1 +(label) @begin +(label) @end +(void()) main() +(string~) main::$0 +(boolean~) main::$1 +(label) main::@1 +(label) main::@2 +(label) main::@return +(byte) main::i +(byte) main::l +(byte[]) main::msg +(byte*) main::screen + +Promoting word/signed word/dword/signed dword to byte* in main::screen ← ((byte*)) 1024 +INITIAL CONTROL FLOW GRAPH +@begin: scope:[] from + to:@1 +main: scope:[main] from + (byte*) main::screen ← ((byte*)) (word/signed word/dword/signed dword) 1024 + (byte) main::l ← (byte) 'l' + (string~) main::$0 ← (string) "cm" + (byte) main::l + (byte[]) main::msg ← (string~) main::$0 + (byte) main::i ← (byte/signed byte/word/signed word/dword/signed dword) 0 + to:main::@1 +main::@1: scope:[main] from main main::@1 + *((byte*) main::screen + (byte) main::i) ← *((byte[]) main::msg + (byte) main::i) + (byte) main::i ← ++ (byte) main::i + (boolean~) main::$1 ← (byte) main::i != (byte/signed byte/word/signed word/dword/signed dword) 3 + if((boolean~) main::$1) goto main::@1 + to:main::@2 +main::@2: scope:[main] from main::@1 + to:main::@return +main::@return: scope:[main] from main::@2 + return + to:@return +@1: scope:[] from @begin + call main + to:@end +@end: scope:[] from @1 + +Creating constant string variable for inline (const string) main::$2 "cm" +Removing empty block main::@2 +PROCEDURE MODIFY VARIABLE ANALYSIS + +Completing Phi functions... + +CONTROL FLOW GRAPH SSA WITH ASSIGNMENT CALL & RETURN +@begin: scope:[] from + to:@1 +main: scope:[main] from @1 + (byte*) main::screen#0 ← ((byte*)) (word/signed word/dword/signed dword) 1024 + (byte) main::l#0 ← (byte) 'l' + (string~) main::$0 ← (const string) main::$2 + (byte) main::l#0 + (byte[]) main::msg#0 ← (string~) main::$0 + (byte) main::i#0 ← (byte/signed byte/word/signed word/dword/signed dword) 0 + to:main::@1 +main::@1: scope:[main] from main main::@1 + (byte*) main::screen#1 ← phi( main/(byte*) main::screen#0 main::@1/(byte*) main::screen#1 ) + (byte) main::i#2 ← phi( main/(byte) main::i#0 main::@1/(byte) main::i#1 ) + *((byte*) main::screen#1 + (byte) main::i#2) ← *((byte[]) main::msg#0 + (byte) main::i#2) + (byte) main::i#1 ← ++ (byte) main::i#2 + (boolean~) main::$1 ← (byte) main::i#1 != (byte/signed byte/word/signed word/dword/signed dword) 3 + if((boolean~) main::$1) goto main::@1 + to:main::@return +main::@return: scope:[main] from main::@1 + return + to:@return +@1: scope:[] from @begin + call main param-assignment + 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() +(string~) main::$0 +(boolean~) main::$1 +(const string) main::$2 = (string) "cm" +(label) main::@1 +(label) main::@return +(byte) main::i +(byte) main::i#0 +(byte) main::i#1 +(byte) main::i#2 +(byte) main::l +(byte) main::l#0 +(byte[]) main::msg +(byte[]) main::msg#0 +(byte*) main::screen +(byte*) main::screen#0 +(byte*) main::screen#1 + +OPTIMIZING CONTROL FLOW GRAPH +Culled Empty Block (label) @2 +Succesful SSA optimization Pass2CullEmptyBlocks +Alias (byte[]) main::msg#0 = (string~) main::$0 +Succesful SSA optimization Pass2AliasElimination +Self Phi Eliminated (byte*) main::screen#1 +Succesful SSA optimization Pass2SelfPhiElimination +Redundant Phi (byte*) main::screen#1 (byte*) main::screen#0 +Succesful SSA optimization Pass2RedundantPhiElimination +Simple Condition (boolean~) main::$1 if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 +Succesful SSA optimization Pass2ConditionalJumpSimplification +Constant (const byte*) main::screen#0 = ((byte*))1024 +Constant (const byte) main::l#0 = 'l' +Constant (const byte) main::i#0 = 0 +Succesful SSA optimization Pass2ConstantIdentification +Constant (const string) main::msg#0 = "cm"+'l' +Succesful SSA optimization Pass2ConstantIdentification +Eliminating unused constant (const string) main::$2 +Eliminating unused constant (const byte) main::l#0 +Succesful SSA optimization PassNEliminateUnusedVars +OPTIMIZING CONTROL FLOW GRAPH +Inlining constant with var siblings (const byte) main::i#0 +Inlining constant with var siblings (const byte) main::i#0 +Constant inlined main::i#0 = (byte/signed byte/word/signed word/dword/signed dword) 0 +Succesful SSA optimization Pass2ConstantInlining +Block Sequence Planned @begin @1 @end main main::@1 main::@return +Added new block during phi lifting main::@3(between main::@1 and main::@1) +Block Sequence Planned @begin @1 @end main main::@1 main::@return main::@3 +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 +CALL GRAPH +Calls in [] to main:2 + +Propagating live ranges... +Propagating live ranges... +Created 1 initial phi equivalence classes +Coalesced [10] main::i#3 ← main::i#1 +Coalesced down to 1 phi equivalence classes +Culled Empty Block (label) main::@3 +Block Sequence Planned @begin @1 @end main main::@1 main::@return +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 +Propagating live ranges... +Propagating live ranges... + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + [1] phi() [ ] ( ) + [2] call main param-assignment [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] phi() [ ] ( main:2 [ ] ) + to:main::@1 +main::@1: scope:[main] from main main::@1 + [5] (byte) main::i#2 ← phi( main/(byte/signed byte/word/signed word/dword/signed dword) 0 main::@1/(byte) main::i#1 ) [ main::i#2 ] ( main:2 [ main::i#2 ] ) + [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] ) + [7] (byte) main::i#1 ← ++ (byte) main::i#2 [ main::i#1 ] ( main:2 [ main::i#1 ] ) + [8] if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 [ main::i#1 ] ( main:2 [ main::i#1 ] ) + to:main::@return +main::@return: scope:[main] from main::@1 + [9] return [ ] ( main:2 [ ] ) + to:@return + +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 + +NATURAL LOOPS +Found back edge: Loop head: main::@1 tails: main::@1 blocks: null +Populated: Loop head: main::@1 tails: main::@1 blocks: main::@1 +Loop head: main::@1 tails: main::@1 blocks: main::@1 + +NATURAL LOOPS WITH DEPTH +Found 0 loops in scope [] +Found 1 loops in scope [main] + Loop head: main::@1 tails: main::@1 blocks: main::@1 +Loop head: main::@1 tails: main::@1 blocks: main::@1 depth: 1 + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte) main::i +(byte) main::i#1 16.5 +(byte) main::i#2 22.0 +(byte) main::l +(byte[]) main::msg +(byte*) main::screen + +Initial phi equivalence classes +[ main::i#2 main::i#1 ] +Complete equivalence classes +[ main::i#2 main::i#1 ] +Allocated zp ZP_BYTE:2 [ main::i#2 main::i#1 ] + +INITIAL ASM +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +bbegin: +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG4 @1 +b1: +//SEG5 [2] call main param-assignment [ ] ( ) +//SEG6 [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG8 @end +bend: +//SEG9 main +main: { + .label screen = $400 + .label i = 2 + //SEG10 [5] phi from main to main::@1 [phi:main->main::@1] + b1_from_main: + //SEG11 [5] phi (byte) main::i#2 = (byte/signed byte/word/signed word/dword/signed dword) 0 [phi:main->main::@1#0] -- vbuz1=vbuc1 + lda #0 + sta i + jmp b1 + //SEG12 [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + b1_from_b1: + //SEG13 [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@1->main::@1#0] -- register_copy + jmp b1 + //SEG14 main::@1 + b1: + //SEG15 [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] ) -- pbuc1_derefidx_vbuz1=pbuc2_derefidx_vbuz1 + ldy i + lda msg,y + sta screen,y + //SEG16 [7] (byte) main::i#1 ← ++ (byte) main::i#2 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuz1=_inc_vbuz1 + inc i + //SEG17 [8] if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuz1_neq_vbuc1_then_la1 + lda i + cmp #3 + bne b1_from_b1 + jmp breturn + //SEG18 main::@return + breturn: + //SEG19 [9] return [ ] ( main:2 [ ] ) + rts + msg: .text "cm"+'l' +} + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ 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] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ 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 , + +REGISTER UPLIFT SCOPES +Uplift Scope [main] 38.5: zp ZP_BYTE:2 [ main::i#2 main::i#1 ] +Uplift Scope [] + +Uplifting [main] best 288 combination reg byte x [ main::i#2 main::i#1 ] +Uplifting [] best 288 combination + +ASSEMBLER BEFORE OPTIMIZATION +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +bbegin: +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG4 @1 +b1: +//SEG5 [2] call main param-assignment [ ] ( ) +//SEG6 [4] phi from @1 to main [phi:@1->main] +main_from_b1: + jsr main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG8 @end +bend: +//SEG9 main +main: { + .label screen = $400 + //SEG10 [5] phi from main to main::@1 [phi:main->main::@1] + b1_from_main: + //SEG11 [5] phi (byte) main::i#2 = (byte/signed byte/word/signed word/dword/signed dword) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + jmp b1 + //SEG12 [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + b1_from_b1: + //SEG13 [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@1->main::@1#0] -- register_copy + jmp b1 + //SEG14 main::@1 + b1: + //SEG15 [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] ) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda msg,x + sta screen,x + //SEG16 [7] (byte) main::i#1 ← ++ (byte) main::i#2 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuxx=_inc_vbuxx + inx + //SEG17 [8] if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuxx_neq_vbuc1_then_la1 + cpx #3 + bne b1_from_b1 + jmp breturn + //SEG18 main::@return + breturn: + //SEG19 [9] return [ ] ( main:2 [ ] ) + rts + msg: .text "cm"+'l' +} + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp b1 +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Replacing label b1_from_b1 with b1 +Removing instruction bbegin: +Removing instruction b1_from_bbegin: +Removing instruction main_from_b1: +Removing instruction bend_from_b1: +Removing instruction b1_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction b1: +Removing instruction bend: +Removing instruction b1_from_main: +Removing instruction breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Removing instruction jmp b1 +Succesful ASM optimization Pass5NextJumpElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 16.5 +(byte) main::i#2 reg byte x 22.0 +(byte) main::l +(byte[]) main::msg +(const string) main::msg#0 msg = (string) "cm"+(byte) 'l' +(byte*) main::screen +(const byte*) main::screen#0 screen = ((byte*))(word/signed word/dword/signed dword) 1024 + +reg byte x [ main::i#2 main::i#1 ] + + +FINAL ASSEMBLER +Score: 192 + +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +//SEG4 @1 +//SEG5 [2] call main param-assignment [ ] ( ) +//SEG6 [4] phi from @1 to main [phi:@1->main] + jsr main +//SEG7 [3] phi from @1 to @end [phi:@1->@end] +//SEG8 @end +//SEG9 main +main: { + .label screen = $400 + //SEG10 [5] phi from main to main::@1 [phi:main->main::@1] + //SEG11 [5] phi (byte) main::i#2 = (byte/signed byte/word/signed word/dword/signed dword) 0 [phi:main->main::@1#0] -- vbuxx=vbuc1 + ldx #0 + //SEG12 [5] phi from main::@1 to main::@1 [phi:main::@1->main::@1] + //SEG13 [5] phi (byte) main::i#2 = (byte) main::i#1 [phi:main::@1->main::@1#0] -- register_copy + //SEG14 main::@1 + b1: + //SEG15 [6] *((const byte*) main::screen#0 + (byte) main::i#2) ← *((const string) main::msg#0 + (byte) main::i#2) [ main::i#2 ] ( main:2 [ main::i#2 ] ) -- pbuc1_derefidx_vbuxx=pbuc2_derefidx_vbuxx + lda msg,x + sta screen,x + //SEG16 [7] (byte) main::i#1 ← ++ (byte) main::i#2 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuxx=_inc_vbuxx + inx + //SEG17 [8] if((byte) main::i#1!=(byte/signed byte/word/signed word/dword/signed dword) 3) goto main::@1 [ main::i#1 ] ( main:2 [ main::i#1 ] ) -- vbuxx_neq_vbuc1_then_la1 + cpx #3 + bne b1 + //SEG18 main::@return + //SEG19 [9] return [ ] ( main:2 [ ] ) + rts + msg: .text "cm"+'l' +} + diff --git a/src/test/java/dk/camelot64/kickc/test/ref/concat-char.sym b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.sym new file mode 100644 index 000000000..d406f535f --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/concat-char.sym @@ -0,0 +1,16 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@return +(byte) main::i +(byte) main::i#1 reg byte x 16.5 +(byte) main::i#2 reg byte x 22.0 +(byte) main::l +(byte[]) main::msg +(const string) main::msg#0 msg = (string) "cm"+(byte) 'l' +(byte*) main::screen +(const byte*) main::screen#0 screen = ((byte*))(word/signed word/dword/signed dword) 1024 + +reg byte x [ main::i#2 main::i#1 ] diff --git a/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.asm b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.asm new file mode 100644 index 000000000..9fcaac85b --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.asm @@ -0,0 +1,11 @@ +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + jsr main +main: { + .label screen = $400 + .const b = 6*$e/3+mod($16,3) + lda #b + sta screen+0 + rts +} diff --git a/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.cfg b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.cfg new file mode 100644 index 000000000..c77dda3bf --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.cfg @@ -0,0 +1,15 @@ +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + [1] phi() [ ] ( ) + [2] call main param-assignment [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) + to:main::@return +main::@return: scope:[main] from main + [5] return [ ] ( main:2 [ ] ) + to:@return diff --git a/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.log b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.log new file mode 100644 index 000000000..6c140736d --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.log @@ -0,0 +1,292 @@ +PARSING src/test/java/dk/camelot64/kickc/test/kc/const-mult-div.kc +// Test a constant with multiplication and division + +void main() { + byte b = 6*(14/3) + 22%3; + byte* screen = $400; + screen[0] = b; +} +SYMBOLS +(label) @1 +(label) @begin +(label) @end +(void()) main() +(byte/signed byte/word/signed word/dword/signed dword~) main::$0 +(byte/signed word/word/dword/signed dword/signed byte~) main::$1 +(byte/signed byte/word/signed word/dword/signed dword~) main::$2 +(byte/signed word/word/dword/signed dword/signed byte~) main::$3 +(label) main::@return +(byte) main::b +(byte*) main::screen + +Promoting word/signed word/dword/signed dword to byte* in main::screen ← ((byte*)) 1024 +INITIAL CONTROL FLOW GRAPH +@begin: scope:[] from + to:@1 +main: scope:[main] from + (byte/signed byte/word/signed word/dword/signed dword~) main::$0 ← (byte/signed byte/word/signed word/dword/signed dword) 14 / (byte/signed byte/word/signed word/dword/signed dword) 3 + (byte/signed word/word/dword/signed dword/signed byte~) main::$1 ← (byte/signed byte/word/signed word/dword/signed dword) 6 * (byte/signed byte/word/signed word/dword/signed dword~) main::$0 + (byte/signed byte/word/signed word/dword/signed dword~) main::$2 ← (byte/signed byte/word/signed word/dword/signed dword) 22 % (byte/signed byte/word/signed word/dword/signed dword) 3 + (byte/signed word/word/dword/signed dword/signed byte~) main::$3 ← (byte/signed word/word/dword/signed dword/signed byte~) main::$1 + (byte/signed byte/word/signed word/dword/signed dword~) main::$2 + (byte) main::b ← (byte/signed word/word/dword/signed dword/signed byte~) main::$3 + (byte*) main::screen ← ((byte*)) (word/signed word/dword/signed dword) 1024 + *((byte*) main::screen + (byte/signed byte/word/signed word/dword/signed dword) 0) ← (byte) main::b + to:main::@return +main::@return: scope:[main] from main + return + to:@return +@1: scope:[] from @begin + call main + to:@end +@end: scope:[] from @1 + +PROCEDURE MODIFY VARIABLE ANALYSIS + +Completing Phi functions... + +CONTROL FLOW GRAPH SSA WITH ASSIGNMENT CALL & RETURN +@begin: scope:[] from + to:@1 +main: scope:[main] from @1 + (byte/signed byte/word/signed word/dword/signed dword~) main::$0 ← (byte/signed byte/word/signed word/dword/signed dword) 14 / (byte/signed byte/word/signed word/dword/signed dword) 3 + (byte/signed word/word/dword/signed dword/signed byte~) main::$1 ← (byte/signed byte/word/signed word/dword/signed dword) 6 * (byte/signed byte/word/signed word/dword/signed dword~) main::$0 + (byte/signed byte/word/signed word/dword/signed dword~) main::$2 ← (byte/signed byte/word/signed word/dword/signed dword) 22 % (byte/signed byte/word/signed word/dword/signed dword) 3 + (byte/signed word/word/dword/signed dword/signed byte~) main::$3 ← (byte/signed word/word/dword/signed dword/signed byte~) main::$1 + (byte/signed byte/word/signed word/dword/signed dword~) main::$2 + (byte) main::b#0 ← (byte/signed word/word/dword/signed dword/signed byte~) main::$3 + (byte*) main::screen#0 ← ((byte*)) (word/signed word/dword/signed dword) 1024 + *((byte*) main::screen#0 + (byte/signed byte/word/signed word/dword/signed dword) 0) ← (byte) main::b#0 + to:main::@return +main::@return: scope:[main] from main + return + to:@return +@1: scope:[] from @begin + call main param-assignment + 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() +(byte/signed byte/word/signed word/dword/signed dword~) main::$0 +(byte/signed word/word/dword/signed dword/signed byte~) main::$1 +(byte/signed byte/word/signed word/dword/signed dword~) main::$2 +(byte/signed word/word/dword/signed dword/signed byte~) main::$3 +(label) main::@return +(byte) main::b +(byte) main::b#0 +(byte*) main::screen +(byte*) main::screen#0 + +OPTIMIZING CONTROL FLOW GRAPH +Culled Empty Block (label) @2 +Succesful SSA optimization Pass2CullEmptyBlocks +Alias (byte) main::b#0 = (byte/signed word/word/dword/signed dword/signed byte~) main::$3 +Succesful SSA optimization Pass2AliasElimination +Constant (const byte/signed byte/word/signed word/dword/signed dword) main::$0 = 14/3 +Constant (const byte/signed byte/word/signed word/dword/signed dword) main::$2 = 22%3 +Constant (const byte*) main::screen#0 = ((byte*))1024 +Succesful SSA optimization Pass2ConstantIdentification +Constant (const byte/signed byte/word/signed word/dword/signed dword) main::$1 = 6*main::$0 +Succesful SSA optimization Pass2ConstantIdentification +Constant (const byte) main::b#0 = main::$1+main::$2 +Succesful SSA optimization Pass2ConstantIdentification +Consolidated array index constant in *(main::screen#0+0) +Succesful SSA optimization Pass2ConstantAdditionElimination +OPTIMIZING CONTROL FLOW GRAPH +Constant inlined main::$1 = (byte/signed byte/word/signed word/dword/signed dword) 6*(byte/signed byte/word/signed word/dword/signed dword) 14/(byte/signed byte/word/signed word/dword/signed dword) 3 +Constant inlined main::$2 = (byte/signed byte/word/signed word/dword/signed dword) 22%(byte/signed byte/word/signed word/dword/signed dword) 3 +Constant inlined main::$0 = (byte/signed byte/word/signed word/dword/signed dword) 14/(byte/signed byte/word/signed word/dword/signed dword) 3 +Succesful SSA optimization Pass2ConstantInlining +Block Sequence Planned @begin @1 @end main main::@return +Block Sequence Planned @begin @1 @end main main::@return +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end +CALL GRAPH +Calls in [] to main:2 + +Propagating live ranges... +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Block Sequence Planned @begin @1 @end main main::@return +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end +Propagating live ranges... + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + [1] phi() [ ] ( ) + [2] call main param-assignment [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) + to:main::@return +main::@return: scope:[main] from main + [5] return [ ] ( main:2 [ ] ) + to:@return + +DOMINATORS +@begin dominated by @begin +@1 dominated by @1 @begin +@end dominated by @1 @begin @end +main dominated by @1 @begin main +main::@return dominated by main::@return @1 @begin main + +NATURAL LOOPS + +NATURAL LOOPS WITH DEPTH +Found 0 loops in scope [] +Found 0 loops in scope [main] + + +VARIABLE REGISTER WEIGHTS +(void()) main() +(byte) main::b +(byte*) main::screen + +Initial phi equivalence classes +Complete equivalence classes + +INITIAL ASM +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +bbegin: +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG4 @1 +b1: +//SEG5 [2] call main param-assignment [ ] ( ) + jsr main +//SEG6 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG7 @end +bend: +//SEG8 main +main: { + .label screen = $400 + .const b = 6*$e/3+mod($16,3) + //SEG9 [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) -- _deref_pbuc1=vbuc2 + lda #b + sta screen+0 + jmp breturn + //SEG10 main::@return + breturn: + //SEG11 [5] return [ ] ( main:2 [ ] ) + rts +} + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) always clobbers reg byte a + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 27 combination +Uplifting [] best 27 combination + +ASSEMBLER BEFORE OPTIMIZATION +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +bbegin: +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +b1_from_bbegin: + jmp b1 +//SEG4 @1 +b1: +//SEG5 [2] call main param-assignment [ ] ( ) + jsr main +//SEG6 [3] phi from @1 to @end [phi:@1->@end] +bend_from_b1: + jmp bend +//SEG7 @end +bend: +//SEG8 main +main: { + .label screen = $400 + .const b = 6*$e/3+mod($16,3) + //SEG9 [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) -- _deref_pbuc1=vbuc2 + lda #b + sta screen+0 + jmp breturn + //SEG10 main::@return + breturn: + //SEG11 [5] return [ ] ( main:2 [ ] ) + rts +} + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction bbegin: +Removing instruction b1_from_bbegin: +Removing instruction bend_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction b1: +Removing instruction bend: +Removing instruction breturn: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@return +(byte) main::b +(const byte) main::b#0 b = (byte/signed byte/word/signed word/dword/signed dword) 6*(byte/signed byte/word/signed word/dword/signed dword) 14/(byte/signed byte/word/signed word/dword/signed dword) 3+(byte/signed byte/word/signed word/dword/signed dword) 22%(byte/signed byte/word/signed word/dword/signed dword) 3 +(byte*) main::screen +(const byte*) main::screen#0 screen = ((byte*))(word/signed word/dword/signed dword) 1024 + + + +FINAL ASSEMBLER +Score: 18 + +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +//SEG3 [1] phi from @begin to @1 [phi:@begin->@1] +//SEG4 @1 +//SEG5 [2] call main param-assignment [ ] ( ) + jsr main +//SEG6 [3] phi from @1 to @end [phi:@1->@end] +//SEG7 @end +//SEG8 main +main: { + .label screen = $400 + .const b = 6*$e/3+mod($16,3) + //SEG9 [4] *((const byte*) main::screen#0+(byte/signed byte/word/signed word/dword/signed dword) 0) ← (const byte) main::b#0 [ ] ( main:2 [ ] ) -- _deref_pbuc1=vbuc2 + lda #b + sta screen+0 + //SEG10 main::@return + //SEG11 [5] return [ ] ( main:2 [ ] ) + rts +} + diff --git a/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.sym b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.sym new file mode 100644 index 000000000..edcb76933 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/const-mult-div.sym @@ -0,0 +1,10 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@return +(byte) main::b +(const byte) main::b#0 b = (byte/signed byte/word/signed word/dword/signed dword) 6*(byte/signed byte/word/signed word/dword/signed dword) 14/(byte/signed byte/word/signed word/dword/signed dword) 3+(byte/signed byte/word/signed word/dword/signed dword) 22%(byte/signed byte/word/signed word/dword/signed dword) 3 +(byte*) main::screen +(const byte*) main::screen#0 screen = ((byte*))(word/signed word/dword/signed dword) 1024 +