mirror of
https://gitlab.com/camelot/kickc.git
synced 2024-12-19 00:29:29 +00:00
Implemented support for inline KickAssembler code
This commit is contained in:
parent
198c941d85
commit
a82c5cf5b1
66
src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java
Normal file
66
src/main/java/dk/camelot64/kickc/asm/AsmInlineKickAsm.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -43,6 +43,8 @@ public class ControlFlowGraphBaseVisitor<T> {
|
||||
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<T> {
|
||||
public T visitAsm(StatementAsm asm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public T visitKickAsm(StatementKickAsm asm) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -193,4 +193,10 @@ public class ControlFlowGraphCopyVisitor extends ControlFlowGraphBaseVisitor<Obj
|
||||
public Object visitAsm(StatementAsm asm) {
|
||||
return new StatementAsm(asm.getAsmLines(), asm.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitKickAsm(StatementKickAsm kasm) {
|
||||
return new StatementKickAsm(kasm.getKickAsmCode(), kasm.getSource());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package dk.camelot64.kickc.model.statements;
|
||||
|
||||
import dk.camelot64.kickc.model.Program;
|
||||
|
||||
/** Inline KickAssembler code */
|
||||
public class StatementKickAsm extends StatementBase {
|
||||
|
||||
/** KickAssembler code. */
|
||||
private String kickAsmCode;
|
||||
|
||||
public StatementKickAsm(String kickAsmCode, StatementSource source) {
|
||||
super(null, source);
|
||||
this.kickAsmCode = kickAsmCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Program program, boolean aliveInfo) {
|
||||
StringBuilder txt = new StringBuilder();
|
||||
txt.append("kickasm {{ ");
|
||||
txt.append(kickAsmCode);
|
||||
txt.append(" }}");
|
||||
return txt.toString();
|
||||
}
|
||||
|
||||
public String getKickAsmCode() {
|
||||
return kickAsmCode;
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,8 @@ import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Generates program SSA form by visiting the ANTLR4 parse tree
|
||||
@ -139,6 +141,18 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
|
||||
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<Object> {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add declared directives to an lValue (typically a variable).
|
||||
*
|
||||
@ -224,7 +236,6 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Directive visitDirectiveConst(KickCParser.DirectiveConstContext ctx) {
|
||||
return new DirectiveConst();
|
||||
|
@ -386,6 +386,9 @@ public class Pass4CodeGeneration {
|
||||
HashMap<String, Value> 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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
|
13
src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc
Normal file
13
src/test/java/dk/camelot64/kickc/test/kc/test-kasm.kc
Normal file
@ -0,0 +1,13 @@
|
||||
// Test inline KickAssembler code
|
||||
|
||||
void main() {
|
||||
while(true) {
|
||||
kickasm {{
|
||||
inc $d020
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
kickasm {{
|
||||
.byte 1, 2, 3
|
||||
}}
|
12
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm
Normal file
12
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.asm
Normal file
@ -0,0 +1,12 @@
|
||||
.pc = $801 "Basic"
|
||||
:BasicUpstart(main)
|
||||
.pc = $80d "Program"
|
||||
.byte 1, 2, 3
|
||||
|
||||
jsr main
|
||||
main: {
|
||||
b2:
|
||||
inc $d020
|
||||
|
||||
jmp b2
|
||||
}
|
17
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg
Normal file
17
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.cfg
Normal file
@ -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
|
297
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log
Normal file
297
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.log
Normal file
@ -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
|
||||
}
|
||||
|
6
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym
Normal file
6
src/test/java/dk/camelot64/kickc/test/ref/test-kasm.sym
Normal file
@ -0,0 +1,6 @@
|
||||
(label) @1
|
||||
(label) @begin
|
||||
(label) @end
|
||||
(void()) main()
|
||||
(label) main::@2
|
||||
|
Loading…
Reference in New Issue
Block a user