diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmFile.java b/src/main/java/dk/camelot64/kickc/asm/AsmFile.java new file mode 100644 index 000000000..13e1c7e8c --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/asm/AsmFile.java @@ -0,0 +1,60 @@ +package dk.camelot64.kickc.asm; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** Define a KickAss output file */ +public class AsmFile implements AsmLine { + + private final String name; + private final Map parameters; + private int index; + + public AsmFile(String name, Map parameters) { + this.name = name; + this.parameters = parameters; + } + + public AsmFile(String name) { + this(name, new LinkedHashMap<>()); + } + + public AsmFile param(String paramName, String paramValue) { + parameters.put(paramName, paramValue); + return this; + } + + @Override + public int getLineBytes() { + return 0; + } + + @Override + public double getLineCycles() { + return 0; + } + + @Override + public String getAsm() { + StringBuffer asm = new StringBuffer(); + asm.append(".file ").append(" [ "); + asm.append("name=\"").append(name).append("\""); + for(String paramName : parameters.keySet()) { + asm.append(","); + String paramValue = parameters.get(paramName); + asm.append(paramName).append("=").append(paramValue); + } + asm.append(" ]"); + return asm.toString(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } +} diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java b/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java new file mode 100644 index 000000000..600b6d51c --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/asm/AsmSegment.java @@ -0,0 +1,64 @@ +package dk.camelot64.kickc.asm; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** Selects a KickAss segment (defined using AsmSegmentDef)*/ +public class AsmSegment implements AsmLine { + + private final String name; + private final Map parameters; + private int index; + + public AsmSegment(String name, Map parameters) { + this.name = name; + this.parameters = parameters; + } + + public AsmSegment(String name) { + this(name, new LinkedHashMap<>()); + } + + public AsmSegment param(String paramName, String paramValue) { + parameters.put(paramName, paramValue); + return this; + } + + @Override + public int getLineBytes() { + return 0; + } + + @Override + public double getLineCycles() { + return 0; + } + + @Override + public String getAsm() { + StringBuffer asm = new StringBuffer(); + asm.append(".segment ").append(name); + if(parameters!=null && parameters.size()>0) { + asm.append(" [ "); + boolean first = true; + for(String paramName : parameters.keySet()) { + if(!first) asm.append(","); + first = false; + String paramValue = parameters.get(paramName); + asm.append(paramName).append("=").append(paramValue); + } + asm.append(" ]"); + } + return asm.toString(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } +} diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmSegmentDef.java b/src/main/java/dk/camelot64/kickc/asm/AsmSegmentDef.java new file mode 100644 index 000000000..69fe9bf7d --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/asm/AsmSegmentDef.java @@ -0,0 +1,61 @@ +package dk.camelot64.kickc.asm; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** Define a KickAss segment */ +public class AsmSegmentDef implements AsmLine { + + private final String name; + private final Map parameters; + private int index; + + public AsmSegmentDef(String name, Map parameters) { + this.name = name; + this.parameters = parameters; + } + + public AsmSegmentDef(String name) { + this(name, new LinkedHashMap<>()); + } + + public AsmSegmentDef param(String paramName, String paramValue) { + parameters.put(paramName, paramValue); + return this; + } + + @Override + public int getLineBytes() { + return 0; + } + + @Override + public double getLineCycles() { + return 0; + } + + @Override + public String getAsm() { + StringBuffer asm = new StringBuffer(); + asm.append(".segmentdef ").append(name).append(" [ "); + boolean first = true; + for(String paramName : parameters.keySet()) { + if(!first) asm.append(","); + first = false; + String paramValue = parameters.get(paramName); + asm.append(paramName).append("=").append(paramValue); + } + asm.append(" ]"); + return asm.toString(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public void setIndex(int index) { + this.index = index; + } +} diff --git a/src/main/java/dk/camelot64/kickc/asm/AsmSetPc.java b/src/main/java/dk/camelot64/kickc/asm/AsmSetPc.java index c159a06f8..d7098fc84 100644 --- a/src/main/java/dk/camelot64/kickc/asm/AsmSetPc.java +++ b/src/main/java/dk/camelot64/kickc/asm/AsmSetPc.java @@ -1,7 +1,5 @@ package dk.camelot64.kickc.asm; -import dk.camelot64.kickc.fragment.AsmFormat; - /** Set the program counter */ public class AsmSetPc implements AsmLine { diff --git a/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java b/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java index 33e6ba41d..a0c639514 100644 --- a/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java +++ b/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java @@ -6,8 +6,12 @@ package dk.camelot64.kickc.model; public enum TargetPlatform { /** Commodore 64 with BASIC upstart SYS-command. */ C64BASIC("c64basic"), + /** Commodore 64 with BASIC upstart SYS-command - using Code and Data segments. */ + C64BASIC_SEGMENTS("c64basic_segments"), /** 6502 assembler (with no upstart code.)*/ - ASM6502("asm6502"); + ASM6502("asm6502"), + /** 6502 assembler (with no upstart code.)*/ + ASM6502_SEGMENTS("asm6502_segments"); /** The default target platform. */ public static final TargetPlatform DEFAULT = C64BASIC; diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java index 0362931ff..4490cd32a 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java @@ -84,16 +84,41 @@ public class Pass4CodeGeneration { if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) { programPc = 0x080d; } else { - programPc = 0x1000; + programPc = 0x2000; } } asm.startChunk(currentScope, null, "Upstart"); - if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) { + if(TargetPlatform.C64BASIC_SEGMENTS.equals(program.getTargetPlatform())) { + useSegments = true; + currentCodeSegmentName = "Code"; + currentDataSegmentName = "Data"; + asm.addLine(new AsmFile(program.getFileName() + ".prg").param("type", "\"prg\"").param("segments", "\"Program\"")); + asm.addLine(new AsmSegmentDef("Program").param("segments", "\"Basic,Code,Data\"")); + asm.addLine(new AsmSegmentDef("Basic").param("start", "$0801")); + asm.addLine(new AsmSegmentDef("Code").param("start", AsmFormat.getAsmNumber(programPc))); + asm.addLine(new AsmSegmentDef("Data").param("startAfter", "\"Code\"")); + asm.addLine(new AsmSegment("Basic")); + asm.addLine(new AsmBasicUpstart("bbegin")); + setCurrentSegment(currentCodeSegmentName, asm); + } else if(TargetPlatform.ASM6502_SEGMENTS.equals(program.getTargetPlatform())) { + useSegments = true; + currentCodeSegmentName = "Code"; + currentDataSegmentName = "Data"; + asm.addLine(new AsmFile(program.getFileName() + ".prg").param("type", "\"prg\"").param("segments", "\"Program\"")); + asm.addLine(new AsmSegmentDef("Program").param("segments", "\"Code,Data\"")); + asm.addLine(new AsmSegmentDef("Code").param("start", AsmFormat.getAsmNumber(programPc))); + asm.addLine(new AsmSegmentDef("Data").param("startAfter", "\"Code\"")); + setCurrentSegment(currentCodeSegmentName, asm); + } else if(TargetPlatform.ASM6502.equals(program.getTargetPlatform())) { + useSegments = false; + asm.addLine(new AsmSetPc("Program", AsmFormat.getAsmNumber(programPc))); + } else if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) { + useSegments = false; asm.addLine(new AsmSetPc("Basic", AsmFormat.getAsmNumber(0x0801))); asm.addLine(new AsmBasicUpstart("bbegin")); + asm.addLine(new AsmSetPc("Program", AsmFormat.getAsmNumber(programPc))); } - asm.addLine(new AsmSetPc("Program", AsmFormat.getAsmNumber(programPc))); // Generate global ZP labels asm.startChunk(currentScope, null, "Global Constants & labels"); @@ -104,6 +129,7 @@ public class Pass4CodeGeneration { // The current block is in a different scope. End the old scope. generateScopeEnding(asm, currentScope); currentScope = block.getScope(); + setCurrentSegment(currentCodeSegmentName, asm); asm.startChunk(currentScope, null, block.getLabel().getFullName()); // Add any procedure comments if(block.isProcedureEntry(program)) { @@ -120,7 +146,6 @@ public class Pass4CodeGeneration { } generateComments(asm, block.getComments()); - // Generate entry points (if needed) genBlockEntryPoints(asm, block); @@ -160,6 +185,9 @@ public class Pass4CodeGeneration { currentScope = ScopeRef.ROOT; asm.startChunk(currentScope, null, "File Data"); + if(hasData(currentScope)) { + setCurrentSegment(currentDataSegmentName, asm); + } addData(asm, ScopeRef.ROOT); // Add all absolutely placed inline KickAsm for(ControlFlowBlock block : getGraph().getAllBlocks()) { @@ -181,6 +209,28 @@ public class Pass4CodeGeneration { program.setAsm(asm); } + // Should the generated program use segments? + boolean useSegments = false; + // Name of the current data segment + private String currentCodeSegmentName = "Code"; + // Name of the current code segment + private String currentDataSegmentName = "Data"; + // Name of the current active segment + private String currentSegmentName = ""; + + /** + * Set the current ASM segment - if needed + * + * @param segmentName The segment name we want + * @param asm The ASM program (where a .segment line is added if needed) + */ + private void setCurrentSegment(String segmentName, AsmProgram asm) { + if(useSegments && !currentSegmentName.equals(segmentName)) { + asm.addLine(new AsmSegment(segmentName)); + currentSegmentName = segmentName; + } + } + /** * ASM names of variables being used for indirect calls in the current scope (procedure). * These will all be added as indirect JMP's at the end of the procedure scope. @@ -201,6 +251,9 @@ public class Pass4CodeGeneration { asm.addInstruction("jmp", AsmAddressingMode.IND, indirectCallAsmName, false); } indirectCallAsmNames = new ArrayList<>(); + if(hasData(currentScope)) { + setCurrentSegment(currentDataSegmentName, asm); + } addData(asm, currentScope); asm.addScopeEnd(); } @@ -391,6 +444,23 @@ public class Pass4CodeGeneration { return useLabel; } + + /** + * Examine whether there are any data directives to be added + * + * @param scopeRef The scope + */ + private boolean hasData(ScopeRef scopeRef) { + Scope scope = program.getScope().getScope(scopeRef); + Collection scopeConstants = scope.getAllConstants(false); + for(ConstantVar constantVar : scopeConstants) { + if(hasData(constantVar)) { + return true; + } + } + return false; + } + /** * Add data directives for constants declarations * @@ -952,8 +1022,8 @@ public class Pass4CodeGeneration { * @param encodings The encodings to ensure */ private void ensureEncoding(AsmProgram asm, Collection encodings) { - if(encodings == null || encodings.size()==0) return; - if(encodings.size()>1) { + if(encodings == null || encodings.size() == 0) return; + if(encodings.size() > 1) { throw new CompileError("Different character encodings in one ASM statement not supported!"); } // Size is 1 - grab it! @@ -1002,7 +1072,6 @@ public class Pass4CodeGeneration { } - /** * Get phi transitions for a specific to-block. * diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java b/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java index 9ea912686..68a0f3791 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass5DoubleJumpElimination.java @@ -1,6 +1,7 @@ package dk.camelot64.kickc.passes; import dk.camelot64.kickc.asm.*; +import dk.camelot64.kickc.fragment.AsmFormat; import dk.camelot64.kickc.model.Program; import java.util.LinkedHashMap; @@ -34,7 +35,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 || line instanceof AsmInlineKickAsm|| line instanceof AsmSetEncoding|| line instanceof AsmDataKickAsm) { + } else if(line instanceof AsmBasicUpstart || line instanceof AsmDataNumeric || line instanceof AsmDataFill || line instanceof AsmDataString || line instanceof AsmDataAlignment || line instanceof AsmSetPc || line instanceof AsmInlineKickAsm|| line instanceof AsmSetEncoding|| line instanceof AsmDataKickAsm|| line instanceof AsmSegmentDef|| line instanceof AsmSegment|| line instanceof AsmFile) { currentLabel = null; } else if(line instanceof AsmInstruction) { if(currentLabel != null) { diff --git a/src/test/kc/fibmem.kc b/src/test/kc/fibmem.kc index 11d387c84..177f2a586 100644 --- a/src/test/kc/fibmem.kc +++ b/src/test/kc/fibmem.kc @@ -1,4 +1,4 @@ -byte[15] fibs = $1100; +byte[15] fibs; void main() { fibs[0] = 0; diff --git a/src/test/ref/fibmem.asm b/src/test/ref/fibmem.asm index 54c5d466c..4cdd03100 100644 --- a/src/test/ref/fibmem.asm +++ b/src/test/ref/fibmem.asm @@ -1,7 +1,6 @@ .pc = $801 "Basic" :BasicUpstart(main) .pc = $80d "Program" - .label fibs = $1100 main: { lda #0 sta fibs @@ -18,3 +17,4 @@ main: { bcc b1 rts } + fibs: .fill $f, 0 diff --git a/src/test/ref/fibmem.log b/src/test/ref/fibmem.log index 833778f20..2e22a75f0 100644 --- a/src/test/ref/fibmem.log +++ b/src/test/ref/fibmem.log @@ -2,7 +2,7 @@ Culled Empty Block (label) main::@2 CONTROL FLOW GRAPH SSA @begin: scope:[] from - (byte[$f]) fibs#0 ← ((byte[$f])) (number) $1100 + (byte[$f]) fibs#0 ← { fill( $f, 0) } to:@1 main: scope:[main] from @1 *((byte[$f]) fibs#0 + (number) 0) ← (number) 0 @@ -59,12 +59,10 @@ Adding number conversion cast (unumber) 1 in (number~) main::$1 ← (byte) main: Adding number conversion cast (unumber) main::$1 in (number~) main::$1 ← (byte) main::i#2 + (unumber)(number) 1 Adding number conversion cast (unumber) $f in (bool~) main::$3 ← (byte) main::i#1 < (number) $f Successful SSA optimization PassNAddNumberTypeConversions -Inlining cast (byte[$f]) fibs#0 ← (byte[$f])(number) $1100 Inlining cast *((byte[$f]) fibs#0 + (unumber)(number) 0) ← (unumber)(number) 0 Inlining cast *((byte[$f]) fibs#0 + (unumber)(number) 1) ← (unumber)(number) 1 Inlining cast (byte) main::i#0 ← (unumber)(number) 0 Successful SSA optimization Pass2InlineCast -Simplifying constant pointer cast (byte*) 4352 Simplifying constant integer cast 0 Simplifying constant integer cast 0 Simplifying constant integer cast 1 @@ -87,7 +85,9 @@ Inferred type updated to byte in (unumber~) main::$0 ← (byte) main::i#2 + (byt Inferred type updated to byte in (unumber~) main::$1 ← (byte) main::i#2 + (byte) 1 Simple Condition (bool~) main::$3 [11] if((byte) main::i#1<(byte) $f) goto main::@1 Successful SSA optimization Pass2ConditionalJumpSimplification -Constant (const byte[$f]) fibs#0 = (byte*) 4352 +Constant right-side identified [0] (byte[$f]) fibs#0 ← { fill( $f, 0) } +Successful SSA optimization Pass2ConstantRValueConsolidation +Constant (const byte[$f]) fibs#0 = { fill( $f, 0) } Constant (const byte) main::i#0 = 0 Successful SSA optimization Pass2ConstantIdentification Simplifying expression containing zero fibs#0 in [1] *((const byte[$f]) fibs#0 + (byte) 0) ← (byte) 0 @@ -169,7 +169,6 @@ Target platform is c64basic :BasicUpstart(bbegin) .pc = $80d "Program" // Global Constants & labels - .label fibs = $1100 // @begin bbegin: // [1] phi from @begin to @1 [phi:@begin->@1] @@ -229,6 +228,7 @@ main: { rts } // File Data + fibs: .fill $f, 0 REGISTER UPLIFT POTENTIAL REGISTERS Statement [4] *((const byte[$f]) fibs#0) ← (byte) 0 [ ] ( main:2 [ ] ) always clobbers reg byte a @@ -255,7 +255,6 @@ ASSEMBLER BEFORE OPTIMIZATION :BasicUpstart(bbegin) .pc = $80d "Program" // Global Constants & labels - .label fibs = $1100 // @begin bbegin: // [1] phi from @begin to @1 [phi:@begin->@1] @@ -307,6 +306,7 @@ main: { rts } // File Data + fibs: .fill $f, 0 ASSEMBLER OPTIMIZATIONS Removing instruction jmp b1 @@ -337,7 +337,7 @@ FINAL SYMBOL TABLE (label) @begin (label) @end (byte[$f]) fibs -(const byte[$f]) fibs#0 fibs = (byte*) 4352 +(const byte[$f]) fibs#0 fibs = { fill( $f, 0) } (void()) main() (byte~) main::$2 reg byte a 22.0 (label) main::@1 @@ -359,7 +359,6 @@ Score: 263 :BasicUpstart(main) .pc = $80d "Program" // Global Constants & labels - .label fibs = $1100 // @begin // [1] phi from @begin to @1 [phi:@begin->@1] // @1 @@ -403,4 +402,5 @@ main: { rts } // File Data + fibs: .fill $f, 0 diff --git a/src/test/ref/fibmem.sym b/src/test/ref/fibmem.sym index 680252902..38b77a3c5 100644 --- a/src/test/ref/fibmem.sym +++ b/src/test/ref/fibmem.sym @@ -2,7 +2,7 @@ (label) @begin (label) @end (byte[$f]) fibs -(const byte[$f]) fibs#0 fibs = (byte*) 4352 +(const byte[$f]) fibs#0 fibs = { fill( $f, 0) } (void()) main() (byte~) main::$2 reg byte a 22.0 (label) main::@1 diff --git a/src/test/ref/platform-asm6502.asm b/src/test/ref/platform-asm6502.asm index 04604e81c..9ad4f9050 100644 --- a/src/test/ref/platform-asm6502.asm +++ b/src/test/ref/platform-asm6502.asm @@ -1,5 +1,5 @@ // Tests the target platform ASM6502 -.pc = $1000 "Program" +.pc = $2000 "Program" main: { ldx #0 b2: diff --git a/src/test/ref/platform-asm6502.log b/src/test/ref/platform-asm6502.log index 9cfd1b969..f82d05add 100644 --- a/src/test/ref/platform-asm6502.log +++ b/src/test/ref/platform-asm6502.log @@ -143,7 +143,7 @@ Target platform is asm6502 // File Comments // Tests the target platform ASM6502 // Upstart -.pc = $1000 "Program" +.pc = $2000 "Program" // Global Constants & labels // @begin bbegin: @@ -212,7 +212,7 @@ ASSEMBLER BEFORE OPTIMIZATION // File Comments // Tests the target platform ASM6502 // Upstart -.pc = $1000 "Program" +.pc = $2000 "Program" // Global Constants & labels // @begin bbegin: @@ -311,7 +311,7 @@ Score: 161 // File Comments // Tests the target platform ASM6502 // Upstart -.pc = $1000 "Program" +.pc = $2000 "Program" // Global Constants & labels // @begin // [1] phi from @begin to @1 [phi:@begin->@1]