From a82c5cf5b17acdbc54abb83df60d06f446c32d68 Mon Sep 17 00:00:00 2001 From: Jesper Gravgaard Date: Sat, 7 Jul 2018 13:55:15 +0200 Subject: [PATCH] Implemented support for inline KickAssembler code --- .../camelot64/kickc/asm/AsmInlineKickAsm.java | 66 ++++ .../dk/camelot64/kickc/asm/AsmProgram.java | 7 + .../dk/camelot64/kickc/asm/AsmSegment.java | 2 +- .../model/ControlFlowGraphBaseVisitor.java | 6 + .../model/ControlFlowGraphCopyVisitor.java | 6 + .../model/statements/StatementKickAsm.java | 29 ++ .../Pass0GenerateStatementSequence.java | 17 +- .../kickc/passes/Pass4CodeGeneration.java | 3 + .../passes/Pass5DoubleJumpElimination.java | 2 +- .../passes/PassNVariableReferenceInfos.java | 2 + .../dk/camelot64/kickc/test/TestPrograms.java | 5 + .../dk/camelot64/kickc/test/kc/test-kasm.kc | 13 + .../dk/camelot64/kickc/test/ref/test-kasm.asm | 12 + .../dk/camelot64/kickc/test/ref/test-kasm.cfg | 17 + .../dk/camelot64/kickc/test/ref/test-kasm.log | 297 ++++++++++++++++++ .../dk/camelot64/kickc/test/ref/test-kasm.sym | 6 + 16 files changed, 485 insertions(+), 5 deletions(-) create mode 100644 src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java create mode 100644 src/main/java/dk/camelot64/kickc/model/statements/StatementKickAsm.java create mode 100644 src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc create mode 100644 src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm create mode 100644 src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg create mode 100644 src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log create mode 100644 src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java b/src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java new file mode 100644 index 000000000..873dba453 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java @@ -0,0 +1,66 @@ +package dk.camelot64.kickc.asm; + +/** Inlined KickAssembler code. + * If no cycles/byte size is specified it defaults to 100/100. + * */ +public class AsmInlineKickAsm implements AsmLine { + + private String kickAsmCode; + + private int index; + + private int bytes = 100; + + private double cycles = 100; + + public AsmInlineKickAsm(String kickAsmCode) { + this.kickAsmCode= kickAsmCode; + } + + public String getKickAsmCode() { + return kickAsmCode; + } + + public void setKickAsmCode(String kickAsmCode) { + this.kickAsmCode = kickAsmCode; + } + + @Override + public int getLineBytes() { + return bytes; + } + + @Override + public double getLineCycles() { + return cycles; + } + + public void setBytes(int bytes) { + this.bytes = bytes; + } + + public void setCycles(double cycles) { + this.cycles = cycles; + } + + @Override + public String getAsm() { + return kickAsmCode; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } + + @Override + public String toString() { + return getAsm(); + } + +} diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmProgram.java b/src/main/java/dk/camelot64/kickc/asm/AsmProgram.java index b4a9c1794..781825824 100644 --- a/src/main/java/dk/camelot64/kickc/asm/AsmProgram.java +++ b/src/main/java/dk/camelot64/kickc/asm/AsmProgram.java @@ -131,6 +131,13 @@ public class AsmProgram { addLine(new AsmDataAlignment(alignment)); } + /** + * Add inlines kick assembler code + * @param kickAsmCode The kickassembler code + */ + public void addInlinedKickAsm(String kickAsmCode) { + addLine(new AsmInlineKickAsm(kickAsmCode)); + } /** * Get the number of bytes the segment occupies in memory. diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java b/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java index 48915db3a..44c7d981c 100644 --- a/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java +++ b/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java @@ -175,7 +175,7 @@ public class AsmSegment { if(printState.isComments()) { out.append(printState.getIndent()).append("//SEG").append(getIndex()); if(source != null) { - out.append(" ").append(source); + out.append(" ").append(source.replace('\r', ' ').replace('\n', ' ')); } if(phiTransitionId != null) { out.append(" [").append(phiTransitionId); diff --git a/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphBaseVisitor.java b/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphBaseVisitor.java index e51aad4c7..f371e8841 100644 --- a/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphBaseVisitor.java +++ b/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphBaseVisitor.java @@ -43,6 +43,8 @@ public class ControlFlowGraphBaseVisitor { return visitProcedureEnd((StatementProcedureEnd) statement); } else if(statement instanceof StatementAsm) { return visitAsm((StatementAsm) statement); + } else if(statement instanceof StatementKickAsm) { + return visitKickAsm((StatementKickAsm) statement); } else { throw new RuntimeException("Unhandled statement type " + statement); } @@ -87,4 +89,8 @@ public class ControlFlowGraphBaseVisitor { public T visitAsm(StatementAsm asm) { return null; } + + public T visitKickAsm(StatementKickAsm asm) { + return null; + } } diff --git a/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphCopyVisitor.java b/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphCopyVisitor.java index 1cd892f89..83fb75b1a 100644 --- a/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphCopyVisitor.java +++ b/src/main/java/dk/camelot64/kickc/model/ControlFlowGraphCopyVisitor.java @@ -193,4 +193,10 @@ public class ControlFlowGraphCopyVisitor extends ControlFlowGraphBaseVisitor { return param; } + @Override + public Object visitDeclKasm(KickCParser.DeclKasmContext ctx) { + String kasm = ctx.KICKASM().getText(); + Pattern p = Pattern.compile("\\{\\{[\\s]*(.*)[\\s]*\\}\\}", Pattern.DOTALL); + Matcher m = p.matcher(kasm); + if(m.find()) { + String kickAsmCode = m.group(1); + sequence.addStatement(new StatementKickAsm(kickAsmCode, new StatementSource(ctx))); + } + return null; + } + @Override public Object visitDeclVariable(KickCParser.DeclVariableContext ctx) { SymbolType type = (SymbolType) visit(ctx.typeDecl()); @@ -166,8 +180,6 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { return null; } - - /** * Add declared directives to an lValue (typically a variable). * @@ -224,7 +236,6 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { } } - @Override public Directive visitDirectiveConst(KickCParser.DirectiveConstContext ctx) { return new DirectiveConst(); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java index 68891cf54..74db7335e 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java @@ -386,6 +386,9 @@ public class Pass4CodeGeneration { HashMap bindings = new HashMap<>(); AsmFragmentInstance asmFragmentInstance = new AsmFragmentInstance(program, "inline", block.getScope(), new AsmFragmentTemplate(statementAsm.getAsmLines()), bindings); asmFragmentInstance.generate(asm); + } else if(statement instanceof StatementKickAsm) { + StatementKickAsm statementKasm = (StatementKickAsm) statement; + asm.addInlinedKickAsm(statementKasm.getKickAsmCode()); } else { throw new RuntimeException("Statement not supported " + statement); } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java b/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java index 183e4922f..b58931b3c 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java @@ -34,7 +34,7 @@ public class Pass5DoubleJumpElimination extends Pass5AsmOptimization { currentLabel = ((AsmLabel) line).getLabel(); } else if(line instanceof AsmComment || line instanceof AsmConstant || line instanceof AsmLabelDecl) { // ignore - } else if(line instanceof AsmBasicUpstart || line instanceof AsmDataNumeric || line instanceof AsmDataFill || line instanceof AsmDataString || line instanceof AsmDataAlignment || line instanceof AsmSetPc) { + } else if(line instanceof AsmBasicUpstart || line instanceof AsmDataNumeric || line instanceof AsmDataFill || line instanceof AsmDataString || line instanceof AsmDataAlignment || line instanceof AsmSetPc || line instanceof AsmInlineKickAsm) { currentLabel = null; } else if(line instanceof AsmInstruction) { if(currentLabel != null) { diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNVariableReferenceInfos.java b/src/main/java/dk/camelot64/kickc/passes/PassNVariableReferenceInfos.java index d3c443d7f..2659a28d7 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNVariableReferenceInfos.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNVariableReferenceInfos.java @@ -346,6 +346,8 @@ public class PassNVariableReferenceInfos extends Pass2Base { referenced.addAll(getReferenced(statementReturn.getValue())); } else if(statement instanceof StatementAsm) { // No references in ASM atm. + } else if(statement instanceof StatementKickAsm) { + // No references in ASM atm. } else { throw new RuntimeException("Unknown statement type " + statement); } diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 5a6bc3c98..f63879df8 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -44,6 +44,11 @@ public class TestPrograms { AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false); } + @Test + public void testKasm() throws IOException, URISyntaxException { + compileAndCompare("test-kasm"); + } + @Test public void testLineAnim() throws IOException, URISyntaxException { compileAndCompare("line-anim"); diff --git a/src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc b/src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc new file mode 100644 index 000000000..91737dec6 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc @@ -0,0 +1,13 @@ +// Test inline KickAssembler code + +void main() { + while(true) { + kickasm {{ + inc $d020 + }} + } +} + +kickasm {{ + .byte 1, 2, 3 +}} \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm new file mode 100644 index 000000000..c5af27a90 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm @@ -0,0 +1,12 @@ +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +.byte 1, 2, 3 + + jsr main +main: { + b2: + inc $d020 + + jmp b2 +} diff --git a/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg new file mode 100644 index 000000000..e4633a127 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg @@ -0,0 +1,17 @@ +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + kickasm {{ .byte 1, 2, 3 + }} + [2] call main [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] phi() [ ] ( main:2 [ ] ) + to:main::@2 +main::@2: scope:[main] from main main::@2 + kickasm {{ inc $d020 + }} + to:main::@2 diff --git a/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log new file mode 100644 index 000000000..06a383d1b --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log @@ -0,0 +1,297 @@ +PARSING src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc +// Test inline KickAssembler code + +void main() { + while(true) { + kickasm {{ + inc $d020 + }} + } +} + +kickasm {{ + .byte 1, 2, 3 +}} +SYMBOLS +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@1 +(label) main::@2 +(label) main::@3 +(label) main::@4 +(label) main::@5 +(label) main::@6 +(label) main::@return + +INITIAL CONTROL FLOW GRAPH +@begin: scope:[] from + to:@1 +main: scope:[main] from + to:main::@1 +main::@1: scope:[main] from main main::@2 + if(true) goto main::@2 + to:main::@4 +main::@2: scope:[main] from main::@1 main::@5 + kickasm {{ inc $d020 + }} + to:main::@1 +main::@4: scope:[main] from main::@1 + to:main::@3 +main::@3: scope:[main] from main::@4 main::@6 + to:main::@return +main::@5: scope:[main] from + to:main::@2 +main::@6: scope:[main] from + to:main::@3 +main::@return: scope:[main] from main::@3 + return + to:@return +@1: scope:[] from @begin + kickasm {{ .byte 1, 2, 3 + }} + call main + to:@end +@end: scope:[] from @1 + +Removing empty block main::@4 +Removing empty block main::@3 +Removing empty block main::@5 +Removing empty block main::@6 +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 + to:main::@1 +main::@1: scope:[main] from main main::@2 + if(true) goto main::@2 + to:main::@return +main::@2: scope:[main] from main::@1 + kickasm {{ inc $d020 + }} + to:main::@1 +main::@return: scope:[main] from main::@1 + return + to:@return +@1: scope:[] from @begin + kickasm {{ .byte 1, 2, 3 + }} + 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() +(label) main::@1 +(label) main::@2 +(label) main::@return + +OPTIMIZING CONTROL FLOW GRAPH +Culled Empty Block (label) @2 +Succesful SSA optimization Pass2CullEmptyBlocks +if() condition always true - replacing block destination if(true) goto main::@2 +Succesful SSA optimization Pass2ConstantIfs +Removing unused block main::@return +Succesful SSA optimization Pass2EliminateUnusedBlocks +Culled Empty Block (label) main::@1 +Succesful SSA optimization Pass2CullEmptyBlocks +OPTIMIZING CONTROL FLOW GRAPH +Block Sequence Planned @begin @1 @end main main::@2 +Block Sequence Planned @begin @1 @end main main::@2 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @end +Adding NOP phi() at start of main +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::@2 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @end +Adding NOP phi() at start of main +Propagating live ranges... + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() [ ] ( ) + to:@1 +@1: scope:[] from @begin + kickasm {{ .byte 1, 2, 3 + }} + [2] call main [ ] ( ) + to:@end +@end: scope:[] from @1 + [3] phi() [ ] ( ) +main: scope:[main] from @1 + [4] phi() [ ] ( main:2 [ ] ) + to:main::@2 +main::@2: scope:[main] from main main::@2 + kickasm {{ inc $d020 + }} + to:main::@2 + +DOMINATORS +@begin dominated by @begin +@1 dominated by @1 @begin +@end dominated by @1 @begin @end +main dominated by @1 @begin main +main::@2 dominated by @1 @begin main::@2 main + +NATURAL LOOPS +Found back edge: Loop head: main::@2 tails: main::@2 blocks: null +Populated: Loop head: main::@2 tails: main::@2 blocks: main::@2 +Loop head: main::@2 tails: main::@2 blocks: main::@2 + +NATURAL LOOPS WITH DEPTH +Found 0 loops in scope [] +Found 1 loops in scope [main] + Loop head: main::@2 tails: main::@2 blocks: main::@2 +Loop head: main::@2 tails: main::@2 blocks: main::@2 depth: 1 + + +VARIABLE REGISTER WEIGHTS +(void()) main() + +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: + jmp b1 +//SEG3 @1 +b1: +//SEG4 kickasm {{ .byte 1, 2, 3 }} +.byte 1, 2, 3 + +//SEG5 [2] call main [ ] ( ) +//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: { + jmp b2 + //SEG10 main::@2 + b2: + //SEG11 kickasm {{ inc $d020 }} + inc $d020 + + jmp b2 +} + +REGISTER UPLIFT POTENTIAL REGISTERS + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 1199 combination +Uplifting [] best 1199 combination + +ASSEMBLER BEFORE OPTIMIZATION +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +bbegin: + jmp b1 +//SEG3 @1 +b1: +//SEG4 kickasm {{ .byte 1, 2, 3 }} +.byte 1, 2, 3 + +//SEG5 [2] call main [ ] ( ) +//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: { + jmp b2 + //SEG10 main::@2 + b2: + //SEG11 kickasm {{ inc $d020 }} + inc $d020 + + jmp b2 +} + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp b1 +Removing instruction jmp bend +Removing instruction jmp b2 +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction bbegin: +Removing instruction bend_from_b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction b1: +Removing instruction main_from_b1: +Removing instruction bend: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@2 + + + +FINAL ASSEMBLER +Score: 1136 + +//SEG0 Basic Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" +//SEG1 Global Constants & labels +//SEG2 @begin +//SEG3 @1 +//SEG4 kickasm {{ .byte 1, 2, 3 }} +.byte 1, 2, 3 + +//SEG5 [2] call main [ ] ( ) +//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: { + //SEG10 main::@2 + b2: + //SEG11 kickasm {{ inc $d020 }} + inc $d020 + + jmp b2 +} + diff --git a/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym new file mode 100644 index 000000000..06f7e962d --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym @@ -0,0 +1,6 @@ +(label) @1 +(label) @begin +(label) @end +(void()) main() +(label) main::@2 +