From b52120139ccf8d8c3050296e23253d427166ba3f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 14 Oct 2018 01:55:39 +0200 Subject: [PATCH] 6502 start --- compiler/examples/test.p8 | 10 + compiler/src/prog8/CompilerMain.kt | 2 + compiler/src/prog8/ast/AST.kt | 2 + compiler/src/prog8/compiler/Compiler.kt | 4 +- .../intermediate/IntermediateProgram.kt | 18 +- .../src/prog8/compiler/target/c64/AsmGen.kt | 360 +++++++++++++++++- .../prog8/compiler/target/c64/Commodore64.kt | 3 + docs/source/syntaxreference.rst | 2 +- 8 files changed, 390 insertions(+), 11 deletions(-) diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 31aad21ae..6fb7ef9ae 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -16,6 +16,16 @@ sub start() { word wv2 uword uwv2 + X=X + X=X + Y=Y + X=A + A=Y + A=ubvar + AX=XY + XY=XY + AY=uwvar + XY=uwvar bv2 = ub2b(ubvar) ubv2 = b2ub(bvar) diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index da5744083..1ce6e49b4 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -42,6 +42,8 @@ fun main(args: Array) { as? Directive)?.args?.single()?.name?.toUpperCase() val launcherType = (moduleAst.statements.singleOrNull { it is Directive && it.directive=="%launcher"} as? Directive)?.args?.single()?.name?.toUpperCase() + moduleAst.loadAddress = (moduleAst.statements.singleOrNull { it is Directive && it.directive=="%address"} + as? Directive)?.args?.single()?.int ?: 0 val zpoption: String? = (moduleAst.statements.singleOrNull { it is Directive && it.directive=="%zeropage"} as? Directive)?.args?.single()?.name?.toUpperCase() val zpType: ZeropageType = diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 141c83d17..a364741d4 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -421,6 +421,8 @@ class Module(override val name: String, this.parent=parent } + var loadAddress: Int = 0 // can be set with the %address directive + fun linkParents() { parent = ParentSentinel statements.forEach {it.linkParents(this)} diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 0a9142439..a9fe854cd 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -159,7 +159,7 @@ class Compiler(private val options: CompilationOptions) { println("\nCreating stackVM code...") val namespace = module.definingScope() - val program = IntermediateProgram(module.name, heap) + val program = IntermediateProgram(module.name, module.loadAddress, heap) val translator = StatementTranslator(program, namespace, heap) translator.process(module) @@ -178,7 +178,7 @@ private class StatementTranslator(private val prog: IntermediateProgram, val continueStmtLabelStack : Stack = Stack() override fun process(block: Block): IStatement { - prog.newBlock(block.scopedname, block.address) + prog.newBlock(block.scopedname, block.name, block.address) processVariables(block) prog.label(block.scopedname) prog.line(block.position) diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index fe47e89de..dc3e22c6c 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -9,9 +9,9 @@ import prog8.compiler.HeapValues import java.io.PrintStream -class IntermediateProgram(val name: String, val heap: HeapValues) { +class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues) { - private class ProgramBlock(val scopedname: String, val address: Int?) { + class ProgramBlock(val scopedname: String, val shortname: String, var address: Int?) { val instructions = mutableListOf() val variables = mutableMapOf() val labels = mutableMapOf() @@ -20,10 +20,16 @@ class IntermediateProgram(val name: String, val heap: HeapValues) { get() { return variables.size } val numInstructions: Int get() { return instructions.filter { it.opcode!= Opcode.LINE }.size } + + fun getIns(idx: Int): Instruction { + if(idx>=0 && idx () - private val memory = mutableMapOf>() + val blocks = mutableListOf() + val memory = mutableMapOf>() private lateinit var currentBlock: ProgramBlock val numVariables: Int @@ -273,8 +279,8 @@ class IntermediateProgram(val name: String, val heap: HeapValues) { currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}")) } - fun newBlock(scopedname: String, address: Int?) { - currentBlock = ProgramBlock(scopedname, address) + fun newBlock(scopedname: String, shortname: String, address: Int?) { + currentBlock = ProgramBlock(scopedname, shortname, address) blocks.add(currentBlock) } diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index d95d5d6a1..76de0e79e 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -1,15 +1,371 @@ package prog8.compiler.target.c64 import prog8.compiler.CompilationOptions +import prog8.compiler.LauncherType +import prog8.compiler.OutputType +import prog8.compiler.intermediate.Instruction import prog8.compiler.intermediate.IntermediateProgram +import prog8.compiler.intermediate.LabelInstr +import prog8.compiler.intermediate.Opcode +import prog8.compiler.toHex +import java.io.File +import java.util.* + + +class AssemblyError(msg: String) : RuntimeException(msg) class AsmGen(val options: CompilationOptions) { fun compileToAssembly(program: IntermediateProgram): AssemblyProgram { println("\nGenerating assembly code from intermediate code... ") // todo generate 6502 assembly + val out = File("${program.name}.asm").printWriter() + out.use { + header(out::println, program) + for(block in program.blocks) + block2asm(out::println, block) + } + return AssemblyProgram(program.name) } + + private fun header(out: (String)->Unit, program: IntermediateProgram) { + val ourName = this.javaClass.name + out("; 6502 assembly code for '${program.name}'") + out("; generated by $ourName on ${Date()}") + out("; assembler syntax is for the 64tasm cross-assembler") + out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") + out("\n.cpu '6502'\n.enc 'none'\n") + + if(program.loadAddress==0) // fix load address + program.loadAddress = if(options.launcher==LauncherType.BASIC) BASIC_LOAD_ADDRESS else RAW_LOAD_ADDRESS + + when { + options.launcher == LauncherType.BASIC -> { + if (program.loadAddress != 0x0801) + throw AssemblyError("BASIC output must have load address $0801") + out("; ---- basic program with sys call ----") + out("* = ${program.loadAddress.toHex()}") + val year = Calendar.getInstance().get(Calendar.YEAR) + out("\t.word (+), $year") + out("\t.null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'") + out("+\t.word 0") + out("_prog8_entrypoint\t; assembly code starts here\n") + } + options.output == OutputType.PRG -> { + out("; ---- program without sys call ----") + out("* = ${program.loadAddress.toHex()}\n") + } + options.output == OutputType.RAW -> { + out("; ---- raw assembler program ----") + out("* = ${program.loadAddress.toHex()}\n") + } + } + + // call the init methods of each block and then jump to the main.start entrypoint + out("\t; initialize all blocks(reset vars)") + // todo zeropage if it's there + for(block in program.blocks) + out("\tjsr ${block.scopedname}._prog8_init") + out("\tjmp main.start\t; jump to program entrypoint") + } + + private fun block2asm(out: (String)->Unit, block: IntermediateProgram.ProgramBlock) { + out("\n; ---- block: '${block.shortname}' ----") + if(block.address!=null) { + out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") + out("* = ${block.address?.toHex()}") + } + out("${block.shortname}\t.proc\n") + + // init the variables + out("_prog8_init\t; (re)set vars to initial values") + // todo init vars + + var skip = 0 + for(ins in block.instructions.withIndex()) { + if(skip==0) + skip = ins2asm(out, ins.index, ins.value, block) + else + skip-- + } + out("\n\t.pend\n") + } + + private val registerStrings = setOf("A", "X", "Y", "AX", "AY", "XY") + + private fun ins2asm(out: (String) -> Unit, insIdx: Int, ins: Instruction, block: IntermediateProgram.ProgramBlock): Int { + if(ins is LabelInstr) { + if(ins.name==block.shortname) + return 0 + if(ins.name.startsWith("${block.shortname}.")) + out(ins.name.substring(block.shortname.length+1)) + else + out(ins.name) + out("\trts") // todo weg + return 0 + } + when(ins.opcode) { + Opcode.LINE -> out("\t; src line: ${ins.callLabel}") + Opcode.NOP -> out("\tnop") // shouldn't be present anymore though + Opcode.SEC -> out("\tsec") + Opcode.CLC -> out("\tclc") + Opcode.SEI -> out("\tsei") + Opcode.CLI -> out("\tcli") + Opcode.TERMINATE -> out("\tbrk\t; terminate!") + Opcode.B2UB -> {} // is a no-op, just carry on with the byte as-is + Opcode.UB2B -> {} // is a no-op, just carry on with the byte as-is + Opcode.PUSH_BYTE -> { + // check if we load a register (X, Y) with constant value + val nextIns = block.getIns(insIdx+1) + if(nextIns.opcode==Opcode.POP_VAR_BYTE && nextIns.callLabel in registerStrings) { + out("\tld${nextIns.callLabel!!.toLowerCase()} #${ins.arg!!.integerValue().toHex()}") + return 1 // skip 1 + } + // todo push_byte + } + Opcode.PUSH_WORD -> { + // check if we load a register (AX, AY, XY) with constant value + val nextIns = block.getIns(insIdx+1) + if(nextIns.opcode==Opcode.POP_VAR_WORD && nextIns.callLabel in registerStrings) { + val regs = nextIns.callLabel!!.toLowerCase() + val value = ins.arg!!.integerValue().toHex() + out("\tld${regs[0]} #<$value") + out("\tld${regs[1]} #>$value") + return 1 // skip 1 + } + // todo push_word + } + Opcode.COPY_VAR_BYTE -> { + if(ins.callLabel2 in registerStrings) { + if(ins.callLabel in registerStrings) { + // copying register -> register + when { + ins.callLabel == "A" -> out("\tta${ins.callLabel2!!.toLowerCase()}") + ins.callLabel == "X" -> if (ins.callLabel2 == "Y") { + // 6502 doesn't have txy + out("\ttxa\n\ttay") + } else out("\ttxa") + ins.callLabel == "Y" -> if (ins.callLabel2 == "X") { + // 6502 doesn't have tyx + out("\ttya\n\ttax") + } else out("\ttya") + } + return 0 + } + } + // todo copy_var_byte + } + Opcode.COPY_VAR_WORD -> { + if(ins.callLabel2 in registerStrings) { + if(ins.callLabel in registerStrings) { + // copying registerpair -> registerpair + when { + ins.callLabel == "AX" -> when (ins.callLabel2) { + "AY" -> out("\ttxy") + "XY" -> out("\tpha\n\ttxa\n\ttay\n\tpla\n\ttax") + } + ins.callLabel == "AY" -> when (ins.callLabel2) { + "AX" -> out("\tpha\n\ttya\n\ttax\n\tpla") + "XY" -> out("\ttax") + } + ins.callLabel == "XY" -> when (ins.callLabel2) { + "AX" -> out("\ttxa\n\tpha\n\ttya\n\ttax\n\tpla") + "AY" -> out("\ttxa") + } + } + return 0 + } + } + // todo copy_var_byte + } + else-> {} +// Opcode.PUSH_FLOAT -> TODO() +// Opcode.PUSH_MEM_B -> TODO() +// Opcode.PUSH_MEM_UB -> TODO() +// Opcode.PUSH_MEM_W -> TODO() +// Opcode.PUSH_MEM_UW -> TODO() +// Opcode.PUSH_MEM_FLOAT -> TODO() +// Opcode.PUSH_VAR_BYTE -> TODO() +// Opcode.PUSH_VAR_WORD -> TODO() +// Opcode.PUSH_VAR_FLOAT -> TODO() +// Opcode.DISCARD_BYTE -> TODO() +// Opcode.DISCARD_WORD -> TODO() +// Opcode.DISCARD_FLOAT -> TODO() +// Opcode.POP_MEM_B -> TODO() +// Opcode.POP_MEM_UB -> TODO() +// Opcode.POP_MEM_W -> TODO() +// Opcode.POP_MEM_UW -> TODO() +// Opcode.POP_MEM_FLOAT -> TODO() +// Opcode.POP_VAR_BYTE -> TODO() +// Opcode.POP_VAR_WORD -> TODO() +// Opcode.POP_VAR_FLOAT -> TODO() +// Opcode.COPY_VAR_BYTE -> TODO() +// Opcode.COPY_VAR_WORD -> TODO() +// Opcode.COPY_VAR_FLOAT -> TODO() +// Opcode.ADD_UB -> TODO() +// Opcode.ADD_B -> TODO() +// Opcode.ADD_UW -> TODO() +// Opcode.ADD_W -> TODO() +// Opcode.ADD_F -> TODO() +// Opcode.SUB_UB -> TODO() +// Opcode.SUB_B -> TODO() +// Opcode.SUB_UW -> TODO() +// Opcode.SUB_W -> TODO() +// Opcode.SUB_F -> TODO() +// Opcode.MUL_UB -> TODO() +// Opcode.MUL_B -> TODO() +// Opcode.MUL_UW -> TODO() +// Opcode.MUL_W -> TODO() +// Opcode.MUL_F -> TODO() +// Opcode.DIV_UB -> TODO() +// Opcode.DIV_B -> TODO() +// Opcode.DIV_UW -> TODO() +// Opcode.DIV_W -> TODO() +// Opcode.DIV_F -> TODO() +// Opcode.FLOORDIV_UB -> TODO() +// Opcode.FLOORDIV_B -> TODO() +// Opcode.FLOORDIV_UW -> TODO() +// Opcode.FLOORDIV_W -> TODO() +// Opcode.FLOORDIV_F -> TODO() +// Opcode.REMAINDER_UB -> TODO() +// Opcode.REMAINDER_B -> TODO() +// Opcode.REMAINDER_UW -> TODO() +// Opcode.REMAINDER_W -> TODO() +// Opcode.REMAINDER_F -> TODO() +// Opcode.POW_UB -> TODO() +// Opcode.POW_B -> TODO() +// Opcode.POW_UW -> TODO() +// Opcode.POW_W -> TODO() +// Opcode.POW_F -> TODO() +// Opcode.NEG_B -> TODO() +// Opcode.NEG_W -> TODO() +// Opcode.NEG_F -> TODO() +// Opcode.SHL_BYTE -> TODO() +// Opcode.SHL_WORD -> TODO() +// Opcode.SHL_MEM_BYTE -> TODO() +// Opcode.SHL_MEM_WORD -> TODO() +// Opcode.SHL_VAR_BYTE -> TODO() +// Opcode.SHL_VAR_WORD -> TODO() +// Opcode.SHR_BYTE -> TODO() +// Opcode.SHR_WORD -> TODO() +// Opcode.SHR_MEM_BYTE -> TODO() +// Opcode.SHR_MEM_WORD -> TODO() +// Opcode.SHR_VAR_BYTE -> TODO() +// Opcode.SHR_VAR_WORD -> TODO() +// Opcode.ROL_BYTE -> TODO() +// Opcode.ROL_WORD -> TODO() +// Opcode.ROL_MEM_BYTE -> TODO() +// Opcode.ROL_MEM_WORD -> TODO() +// Opcode.ROL_VAR_BYTE -> TODO() +// Opcode.ROL_VAR_WORD -> TODO() +// Opcode.ROR_BYTE -> TODO() +// Opcode.ROR_WORD -> TODO() +// Opcode.ROR_MEM_BYTE -> TODO() +// Opcode.ROR_MEM_WORD -> TODO() +// Opcode.ROR_VAR_BYTE -> TODO() +// Opcode.ROR_VAR_WORD -> TODO() +// Opcode.ROL2_BYTE -> TODO() +// Opcode.ROL2_WORD -> TODO() +// Opcode.ROL2_MEM_BYTE -> TODO() +// Opcode.ROL2_MEM_WORD -> TODO() +// Opcode.ROL2_VAR_BYTE -> TODO() +// Opcode.ROL2_VAR_WORD -> TODO() +// Opcode.ROR2_BYTE -> TODO() +// Opcode.ROR2_WORD -> TODO() +// Opcode.ROR2_MEM_BYTE -> TODO() +// Opcode.ROR2_MEM_WORD -> TODO() +// Opcode.ROR2_VAR_BYTE -> TODO() +// Opcode.ROR2_VAR_WORD -> TODO() +// Opcode.BITAND_BYTE -> TODO() +// Opcode.BITAND_WORD -> TODO() +// Opcode.BITOR_BYTE -> TODO() +// Opcode.BITOR_WORD -> TODO() +// Opcode.BITXOR_BYTE -> TODO() +// Opcode.BITXOR_WORD -> TODO() +// Opcode.INV_BYTE -> TODO() +// Opcode.INV_WORD -> TODO() +// Opcode.LSB -> TODO() +// Opcode.MSB -> TODO() +// Opcode.B2WORD -> TODO() +// Opcode.UB2UWORD -> TODO() +// Opcode.MSB2WORD -> TODO() +// Opcode.B2FLOAT -> TODO() +// Opcode.UB2FLOAT -> TODO() +// Opcode.W2FLOAT -> TODO() +// Opcode.UW2FLOAT -> TODO() +// Opcode.AND_BYTE -> TODO() +// Opcode.AND_WORD -> TODO() +// Opcode.OR_BYTE -> TODO() +// Opcode.OR_WORD -> TODO() +// Opcode.XOR_BYTE -> TODO() +// Opcode.XOR_WORD -> TODO() +// Opcode.NOT_BYTE -> TODO() +// Opcode.NOT_WORD -> TODO() +// Opcode.INC_B -> TODO() +// Opcode.INC_UB -> TODO() +// Opcode.INC_W -> TODO() +// Opcode.INC_UW -> TODO() +// Opcode.INC_F -> TODO() +// Opcode.INC_VAR_B -> TODO() +// Opcode.INC_VAR_UB -> TODO() +// Opcode.INC_VAR_W -> TODO() +// Opcode.INC_VAR_UW -> TODO() +// Opcode.INC_VAR_F -> TODO() +// Opcode.DEC_B -> TODO() +// Opcode.DEC_UB -> TODO() +// Opcode.DEC_W -> TODO() +// Opcode.DEC_UW -> TODO() +// Opcode.DEC_F -> TODO() +// Opcode.DEC_VAR_B -> TODO() +// Opcode.DEC_VAR_UB -> TODO() +// Opcode.DEC_VAR_W -> TODO() +// Opcode.DEC_VAR_UW -> TODO() +// Opcode.DEC_VAR_F -> TODO() +// Opcode.LESS_B -> TODO() +// Opcode.LESS_UB -> TODO() +// Opcode.LESS_W -> TODO() +// Opcode.LESS_UW -> TODO() +// Opcode.LESS_F -> TODO() +// Opcode.GREATER_B -> TODO() +// Opcode.GREATER_UB -> TODO() +// Opcode.GREATER_W -> TODO() +// Opcode.GREATER_UW -> TODO() +// Opcode.GREATER_F -> TODO() +// Opcode.LESSEQ_B -> TODO() +// Opcode.LESSEQ_UB -> TODO() +// Opcode.LESSEQ_W -> TODO() +// Opcode.LESSEQ_UW -> TODO() +// Opcode.LESSEQ_F -> TODO() +// Opcode.GREATEREQ_B -> TODO() +// Opcode.GREATEREQ_UB -> TODO() +// Opcode.GREATEREQ_W -> TODO() +// Opcode.GREATEREQ_UW -> TODO() +// Opcode.GREATEREQ_F -> TODO() +// Opcode.EQUAL_BYTE -> TODO() +// Opcode.EQUAL_WORD -> TODO() +// Opcode.EQUAL_F -> TODO() +// Opcode.NOTEQUAL_BYTE -> TODO() +// Opcode.NOTEQUAL_WORD -> TODO() +// Opcode.NOTEQUAL_F -> TODO() +// Opcode.READ_INDEXED_VAR_BYTE -> TODO() +// Opcode.READ_INDEXED_VAR_WORD -> TODO() +// Opcode.READ_INDEXED_VAR_FLOAT -> TODO() +// Opcode.WRITE_INDEXED_VAR_BYTE -> TODO() +// Opcode.WRITE_INDEXED_VAR_WORD -> TODO() +// Opcode.WRITE_INDEXED_VAR_FLOAT -> TODO() +// Opcode.JUMP -> TODO() +// Opcode.BCS -> TODO() +// Opcode.BCC -> TODO() +// Opcode.BZ -> TODO() +// Opcode.BNZ -> TODO() +// Opcode.BNEG -> TODO() +// Opcode.BPOS -> TODO() +// Opcode.CALL -> TODO() +// Opcode.RETURN -> TODO() +// Opcode.SYSCALL -> TODO() +// Opcode.BREAKPOINT -> TODO() + } + return 0 + } } - - diff --git a/compiler/src/prog8/compiler/target/c64/Commodore64.kt b/compiler/src/prog8/compiler/target/c64/Commodore64.kt index aeedbf413..bc1e6e1d1 100644 --- a/compiler/src/prog8/compiler/target/c64/Commodore64.kt +++ b/compiler/src/prog8/compiler/target/c64/Commodore64.kt @@ -12,6 +12,9 @@ import kotlin.math.pow const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 +const val BASIC_LOAD_ADDRESS = 0x0801 +const val RAW_LOAD_ADDRESS = 0xc000 + class C64Zeropage(options: CompilationOptions) : Zeropage(options) { diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index e4dcc4612..e72afa422 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -88,7 +88,7 @@ Directives - default for ``raw`` output is ``$c000`` - default for ``prg`` output is ``$0801`` - cannot be changed if you select ``prg`` with a ``basic`` launcher; - then it is always ``$081d`` (immediately after the BASIC program), and the BASIC program itself is always at ``$0801``. + then it is always ``$081e`` (immediately after the BASIC program), and the BASIC program itself is always at ``$0801``. This is because the C64 expects BASIC programs to start at this address.