1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-11-27 19:50:10 +00:00

Implemented initial KickAsm segment support. #113

This commit is contained in:
jespergravgaard 2019-08-09 11:31:08 +02:00
parent 9a54c0f814
commit 8f0b9c886f
13 changed files with 283 additions and 26 deletions

View File

@ -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<String,String> parameters;
private int index;
public AsmFile(String name, Map<String, String> 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;
}
}

View File

@ -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<String,String> parameters;
private int index;
public AsmSegment(String name, Map<String, String> 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;
}
}

View File

@ -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<String,String> parameters;
private int index;
public AsmSegmentDef(String name, Map<String, String> 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;
}
}

View File

@ -1,7 +1,5 @@
package dk.camelot64.kickc.asm; package dk.camelot64.kickc.asm;
import dk.camelot64.kickc.fragment.AsmFormat;
/** Set the program counter */ /** Set the program counter */
public class AsmSetPc implements AsmLine { public class AsmSetPc implements AsmLine {

View File

@ -6,8 +6,12 @@ package dk.camelot64.kickc.model;
public enum TargetPlatform { public enum TargetPlatform {
/** Commodore 64 with BASIC upstart SYS-command. */ /** Commodore 64 with BASIC upstart SYS-command. */
C64BASIC("c64basic"), C64BASIC("c64basic"),
/** Commodore 64 with BASIC upstart SYS-command - using Code and Data segments. */
C64BASIC_SEGMENTS("c64basic_segments"),
/** 6502 assembler (with no upstart code.)*/ /** 6502 assembler (with no upstart code.)*/
ASM6502("asm6502"); ASM6502("asm6502"),
/** 6502 assembler (with no upstart code.)*/
ASM6502_SEGMENTS("asm6502_segments");
/** The default target platform. */ /** The default target platform. */
public static final TargetPlatform DEFAULT = C64BASIC; public static final TargetPlatform DEFAULT = C64BASIC;

View File

@ -84,16 +84,41 @@ public class Pass4CodeGeneration {
if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) { if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) {
programPc = 0x080d; programPc = 0x080d;
} else { } else {
programPc = 0x1000; programPc = 0x2000;
} }
} }
asm.startChunk(currentScope, null, "Upstart"); 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 AsmSetPc("Basic", AsmFormat.getAsmNumber(0x0801)));
asm.addLine(new AsmBasicUpstart("bbegin")); 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 // Generate global ZP labels
asm.startChunk(currentScope, null, "Global Constants & 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. // The current block is in a different scope. End the old scope.
generateScopeEnding(asm, currentScope); generateScopeEnding(asm, currentScope);
currentScope = block.getScope(); currentScope = block.getScope();
setCurrentSegment(currentCodeSegmentName, asm);
asm.startChunk(currentScope, null, block.getLabel().getFullName()); asm.startChunk(currentScope, null, block.getLabel().getFullName());
// Add any procedure comments // Add any procedure comments
if(block.isProcedureEntry(program)) { if(block.isProcedureEntry(program)) {
@ -120,7 +146,6 @@ public class Pass4CodeGeneration {
} }
generateComments(asm, block.getComments()); generateComments(asm, block.getComments());
// Generate entry points (if needed) // Generate entry points (if needed)
genBlockEntryPoints(asm, block); genBlockEntryPoints(asm, block);
@ -160,6 +185,9 @@ public class Pass4CodeGeneration {
currentScope = ScopeRef.ROOT; currentScope = ScopeRef.ROOT;
asm.startChunk(currentScope, null, "File Data"); asm.startChunk(currentScope, null, "File Data");
if(hasData(currentScope)) {
setCurrentSegment(currentDataSegmentName, asm);
}
addData(asm, ScopeRef.ROOT); addData(asm, ScopeRef.ROOT);
// Add all absolutely placed inline KickAsm // Add all absolutely placed inline KickAsm
for(ControlFlowBlock block : getGraph().getAllBlocks()) { for(ControlFlowBlock block : getGraph().getAllBlocks()) {
@ -181,6 +209,28 @@ public class Pass4CodeGeneration {
program.setAsm(asm); 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). * 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. * 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); asm.addInstruction("jmp", AsmAddressingMode.IND, indirectCallAsmName, false);
} }
indirectCallAsmNames = new ArrayList<>(); indirectCallAsmNames = new ArrayList<>();
if(hasData(currentScope)) {
setCurrentSegment(currentDataSegmentName, asm);
}
addData(asm, currentScope); addData(asm, currentScope);
asm.addScopeEnd(); asm.addScopeEnd();
} }
@ -391,6 +444,23 @@ public class Pass4CodeGeneration {
return useLabel; 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<ConstantVar> scopeConstants = scope.getAllConstants(false);
for(ConstantVar constantVar : scopeConstants) {
if(hasData(constantVar)) {
return true;
}
}
return false;
}
/** /**
* Add data directives for constants declarations * Add data directives for constants declarations
* *
@ -952,8 +1022,8 @@ public class Pass4CodeGeneration {
* @param encodings The encodings to ensure * @param encodings The encodings to ensure
*/ */
private void ensureEncoding(AsmProgram asm, Collection<ConstantString.Encoding> encodings) { private void ensureEncoding(AsmProgram asm, Collection<ConstantString.Encoding> encodings) {
if(encodings == null || encodings.size()==0) return; if(encodings == null || encodings.size() == 0) return;
if(encodings.size()>1) { if(encodings.size() > 1) {
throw new CompileError("Different character encodings in one ASM statement not supported!"); throw new CompileError("Different character encodings in one ASM statement not supported!");
} }
// Size is 1 - grab it! // Size is 1 - grab it!
@ -1002,7 +1072,6 @@ public class Pass4CodeGeneration {
} }
/** /**
* Get phi transitions for a specific to-block. * Get phi transitions for a specific to-block.
* *

View File

@ -1,6 +1,7 @@
package dk.camelot64.kickc.passes; package dk.camelot64.kickc.passes;
import dk.camelot64.kickc.asm.*; import dk.camelot64.kickc.asm.*;
import dk.camelot64.kickc.fragment.AsmFormat;
import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.Program;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -34,7 +35,7 @@ public class Pass5DoubleJumpElimination extends Pass5AsmOptimization {
currentLabel = ((AsmLabel) line).getLabel(); currentLabel = ((AsmLabel) line).getLabel();
} else if(line instanceof AsmComment || line instanceof AsmConstant || line instanceof AsmLabelDecl) { } else if(line instanceof AsmComment || line instanceof AsmConstant || line instanceof AsmLabelDecl) {
// ignore // 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; currentLabel = null;
} else if(line instanceof AsmInstruction) { } else if(line instanceof AsmInstruction) {
if(currentLabel != null) { if(currentLabel != null) {

View File

@ -1,4 +1,4 @@
byte[15] fibs = $1100; byte[15] fibs;
void main() { void main() {
fibs[0] = 0; fibs[0] = 0;

View File

@ -1,7 +1,6 @@
.pc = $801 "Basic" .pc = $801 "Basic"
:BasicUpstart(main) :BasicUpstart(main)
.pc = $80d "Program" .pc = $80d "Program"
.label fibs = $1100
main: { main: {
lda #0 lda #0
sta fibs sta fibs
@ -18,3 +17,4 @@ main: {
bcc b1 bcc b1
rts rts
} }
fibs: .fill $f, 0

View File

@ -2,7 +2,7 @@ Culled Empty Block (label) main::@2
CONTROL FLOW GRAPH SSA CONTROL FLOW GRAPH SSA
@begin: scope:[] from @begin: scope:[] from
(byte[$f]) fibs#0 ← ((byte[$f])) (number) $1100 (byte[$f]) fibs#0 ← { fill( $f, 0) }
to:@1 to:@1
main: scope:[main] from @1 main: scope:[main] from @1
*((byte[$f]) fibs#0 + (number) 0) ← (number) 0 *((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) 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 Adding number conversion cast (unumber) $f in (bool~) main::$3 ← (byte) main::i#1 < (number) $f
Successful SSA optimization PassNAddNumberTypeConversions 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) 0) ← (unumber)(number) 0
Inlining cast *((byte[$f]) fibs#0 + (unumber)(number) 1) ← (unumber)(number) 1 Inlining cast *((byte[$f]) fibs#0 + (unumber)(number) 1) ← (unumber)(number) 1
Inlining cast (byte) main::i#0 ← (unumber)(number) 0 Inlining cast (byte) main::i#0 ← (unumber)(number) 0
Successful SSA optimization Pass2InlineCast Successful SSA optimization Pass2InlineCast
Simplifying constant pointer cast (byte*) 4352
Simplifying constant integer cast 0 Simplifying constant integer cast 0
Simplifying constant integer cast 0 Simplifying constant integer cast 0
Simplifying constant integer cast 1 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 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 Simple Condition (bool~) main::$3 [11] if((byte) main::i#1<(byte) $f) goto main::@1
Successful SSA optimization Pass2ConditionalJumpSimplification 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 Constant (const byte) main::i#0 = 0
Successful SSA optimization Pass2ConstantIdentification Successful SSA optimization Pass2ConstantIdentification
Simplifying expression containing zero fibs#0 in [1] *((const byte[$f]) fibs#0 + (byte) 0) ← (byte) 0 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) :BasicUpstart(bbegin)
.pc = $80d "Program" .pc = $80d "Program"
// Global Constants & labels // Global Constants & labels
.label fibs = $1100
// @begin // @begin
bbegin: bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1] // [1] phi from @begin to @1 [phi:@begin->@1]
@ -229,6 +228,7 @@ main: {
rts rts
} }
// File Data // File Data
fibs: .fill $f, 0
REGISTER UPLIFT POTENTIAL REGISTERS REGISTER UPLIFT POTENTIAL REGISTERS
Statement [4] *((const byte[$f]) fibs#0) ← (byte) 0 [ ] ( main:2 [ ] ) always clobbers reg byte a 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) :BasicUpstart(bbegin)
.pc = $80d "Program" .pc = $80d "Program"
// Global Constants & labels // Global Constants & labels
.label fibs = $1100
// @begin // @begin
bbegin: bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1] // [1] phi from @begin to @1 [phi:@begin->@1]
@ -307,6 +306,7 @@ main: {
rts rts
} }
// File Data // File Data
fibs: .fill $f, 0
ASSEMBLER OPTIMIZATIONS ASSEMBLER OPTIMIZATIONS
Removing instruction jmp b1 Removing instruction jmp b1
@ -337,7 +337,7 @@ FINAL SYMBOL TABLE
(label) @begin (label) @begin
(label) @end (label) @end
(byte[$f]) fibs (byte[$f]) fibs
(const byte[$f]) fibs#0 fibs = (byte*) 4352 (const byte[$f]) fibs#0 fibs = { fill( $f, 0) }
(void()) main() (void()) main()
(byte~) main::$2 reg byte a 22.0 (byte~) main::$2 reg byte a 22.0
(label) main::@1 (label) main::@1
@ -359,7 +359,6 @@ Score: 263
:BasicUpstart(main) :BasicUpstart(main)
.pc = $80d "Program" .pc = $80d "Program"
// Global Constants & labels // Global Constants & labels
.label fibs = $1100
// @begin // @begin
// [1] phi from @begin to @1 [phi:@begin->@1] // [1] phi from @begin to @1 [phi:@begin->@1]
// @1 // @1
@ -403,4 +402,5 @@ main: {
rts rts
} }
// File Data // File Data
fibs: .fill $f, 0

View File

@ -2,7 +2,7 @@
(label) @begin (label) @begin
(label) @end (label) @end
(byte[$f]) fibs (byte[$f]) fibs
(const byte[$f]) fibs#0 fibs = (byte*) 4352 (const byte[$f]) fibs#0 fibs = { fill( $f, 0) }
(void()) main() (void()) main()
(byte~) main::$2 reg byte a 22.0 (byte~) main::$2 reg byte a 22.0
(label) main::@1 (label) main::@1

View File

@ -1,5 +1,5 @@
// Tests the target platform ASM6502 // Tests the target platform ASM6502
.pc = $1000 "Program" .pc = $2000 "Program"
main: { main: {
ldx #0 ldx #0
b2: b2:

View File

@ -143,7 +143,7 @@ Target platform is asm6502
// File Comments // File Comments
// Tests the target platform ASM6502 // Tests the target platform ASM6502
// Upstart // Upstart
.pc = $1000 "Program" .pc = $2000 "Program"
// Global Constants & labels // Global Constants & labels
// @begin // @begin
bbegin: bbegin:
@ -212,7 +212,7 @@ ASSEMBLER BEFORE OPTIMIZATION
// File Comments // File Comments
// Tests the target platform ASM6502 // Tests the target platform ASM6502
// Upstart // Upstart
.pc = $1000 "Program" .pc = $2000 "Program"
// Global Constants & labels // Global Constants & labels
// @begin // @begin
bbegin: bbegin:
@ -311,7 +311,7 @@ Score: 161
// File Comments // File Comments
// Tests the target platform ASM6502 // Tests the target platform ASM6502
// Upstart // Upstart
.pc = $1000 "Program" .pc = $2000 "Program"
// Global Constants & labels // Global Constants & labels
// @begin // @begin
// [1] phi from @begin to @1 [phi:@begin->@1] // [1] phi from @begin to @1 [phi:@begin->@1]