diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 96d4c2f7b..ff95aec7c 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -1,21 +1,16 @@ package prog8.codegen.cpu6502 import com.github.michaelbull.result.fold -import com.github.michaelbull.result.onSuccess import prog8.ast.* -import prog8.ast.antlr.escape import prog8.ast.base.* import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.codegen.cpu6502.assignment.* import prog8.compilerinterface.* import prog8.parser.SourceCode -import java.time.LocalDate -import java.time.LocalDateTime import java.util.* import kotlin.io.path.Path import kotlin.io.path.writeLines -import kotlin.math.absoluteValue const val generatedLabelPrefix = "prog8_label_" @@ -28,15 +23,13 @@ class AsmGen(internal val program: Program, internal val variables: IVariablesAndConsts, internal val options: CompilationOptions): IAssemblyGenerator { - // for expressions and augmented assignments: - val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100) - val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640) - private val callGraph = CallGraph(program) - private val compTarget = options.compTarget - internal val zeropage = compTarget.machine.zeropage - + internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100) + internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640) + internal val zeropage = options.compTarget.machine.zeropage + internal val globalFloatConsts = mutableMapOf() // all float values in the entire program (value -> varname) + internal val loopEndLabels = ArrayDeque() + private val memorySlabs = mutableMapOf>() private val assemblyLines = mutableListOf() - private val globalFloatConsts = mutableMapOf() // all float values in the entire program (value -> varname) private val breakpointLabels = mutableListOf() private val forloopsAsmGen = ForLoopsAsmGen(program, this) private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) @@ -44,39 +37,17 @@ class AsmGen(internal val program: Program, private val expressionsAsmGen = ExpressionsAsmGen(program, this, functioncallAsmGen) private val assignmentAsmGen = AssignmentAsmGen(program, this) private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen) - internal val loopEndLabels = ArrayDeque() - internal val slabs = mutableMapOf>() - internal val removals = mutableListOf>() - private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance() } + private val programGen = ProgramGen(program, variables, options, errors, functioncallAsmGen, this) + internal val allMemorySlabs: Map> = memorySlabs override fun compileToAssembly(): IAssemblyProgram? { assemblyLines.clear() loopEndLabels.clear() println("Generating assembly code... ") + programGen.generate() - // TODO variables.dump(program.memsizer) - - val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value } - require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"} - - header() - val allBlocks = program.allBlocks - if(allBlocks.first().name != "main") - throw AssemblyError("first block should be 'main'") - - allocateAllZeropageVariables() - if(errors.noErrors()) { - program.allBlocks.forEach { block2asm(it) } - - for(removal in removals.toList()) { - removal.second.remove(removal.first) - removals.remove(removal) - } - - slaballocations() - footer() - + if(errors.noErrors()) { val output = options.outputDir.resolve("${program.name}.asm") if(options.optimize) { val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') } @@ -88,219 +59,25 @@ class AsmGen(internal val program: Program, } else { output.writeLines(assemblyLines) } - } - - return if(errors.noErrors()) - AssemblyProgram(program.name, options.outputDir, compTarget.name) - else { + return AssemblyProgram(program.name, options.outputDir, options.compTarget) + } else { errors.report() return null } } - private val varsInZeropage = mutableSetOf() - - private fun allocateAllZeropageVariables() { - if(options.zeropage==ZeropageType.DONTUSE) - return - val allVariables = this.callGraph.allIdentifiers.asSequence() - .map { it.value } - .filterIsInstance() - .filter { it.type==VarDeclType.VAR } - .toSet() - .map { it to it.scopedName } - val varsRequiringZp = allVariables.filter { it.first.zeropage==ZeropageWish.REQUIRE_ZEROPAGE } - val varsPreferringZp = allVariables - .filter { it.first.zeropage==ZeropageWish.PREFER_ZEROPAGE } - .sortedBy { options.compTarget.memorySize(it.first.datatype) } // allocate the smallest DT first - - for ((vardecl, scopedname) in varsRequiringZp) { - val numElements: Int? = when(vardecl.datatype) { - DataType.STR -> { - (vardecl.value as StringLiteralValue).value.length - } - in ArrayDatatypes -> { - vardecl.arraysize!!.constIndex() - } - else -> null - } - val result = zeropage.allocate(scopedname, vardecl.datatype, numElements, vardecl.position, errors) - result.fold( - success = { varsInZeropage.add(vardecl) }, - failure = { errors.err(it.message!!, vardecl.position) } - ) - } - if(errors.noErrors()) { - varsPreferringZp.forEach { (vardecl, scopedname) -> - val arraySize: Int? = when (vardecl.datatype) { - DataType.STR -> { - (vardecl.value as StringLiteralValue).value.length - } - in ArrayDatatypes -> { - vardecl.arraysize!!.constIndex() - } - else -> null - } - val result = zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors) - result.onSuccess { varsInZeropage.add(vardecl) } - // no need to check for error, if there is one, just allocate in normal system ram later. - } - } + internal fun getMemorySlab(name: String) = memorySlabs[name] + internal fun allocateMemorySlab(name: String, size: UInt, align: UInt) { + memorySlabs[name] = Pair(size, align) } - internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu - internal fun haveFPWRcall() = compTarget.name=="cx16" + internal fun isTargetCpu(cpu: CpuType) = options.compTarget.machine.cpu == cpu + internal fun haveFPWRcall() = options.compTarget.name=="cx16" internal fun asmsubArgsEvalOrder(sub: Subroutine) = - compTarget.asmsubArgsEvalOrder(sub) + options.compTarget.asmsubArgsEvalOrder(sub) internal fun asmsubArgsHaveRegisterClobberRisk(args: List, paramRegisters: List) = - compTarget.asmsubArgsHaveRegisterClobberRisk(args, paramRegisters) - - private fun header() { - val ourName = this.javaClass.name - val cpu = when(compTarget.machine.cpu) { - CpuType.CPU6502 -> "6502" - CpuType.CPU65c02 -> "w65c02" - else -> "unsupported" - } - - out("; $cpu assembly code for '${program.name}'") - out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}") - out("; assembler syntax is for the 64tasm cross-assembler") - out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") - out("\n.cpu '$cpu'\n.enc 'none'\n") - - program.actualLoadAddress = program.definedLoadAddress - if (program.actualLoadAddress == 0u) // fix load address - program.actualLoadAddress = if (options.launcher == LauncherType.BASIC) - compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS - - // the global prog8 variables needed - val zp = compTarget.machine.zeropage - out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}") - out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}") - out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word") - out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word") - out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}") - out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}") - - when { - options.launcher == LauncherType.BASIC -> { - if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS) - throw AssemblyError("BASIC output must have correct load address") - out("; ---- basic program with sys call ----") - out("* = ${program.actualLoadAddress.toHex()}") - val year = LocalDate.now().year - out(" .word (+), $year") - out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") - out("+\t.word 0") - out("prog8_entrypoint\t; assembly code starts here\n") - if(!options.noSysInit) - out(" jsr ${compTarget.name}.init_system") - out(" jsr ${compTarget.name}.init_system_phase2") - } - options.output == OutputType.PRG -> { - out("; ---- program without basic sys call ----") - out("* = ${program.actualLoadAddress.toHex()}\n") - if(!options.noSysInit) - out(" jsr ${compTarget.name}.init_system") - out(" jsr ${compTarget.name}.init_system_phase2") - } - options.output == OutputType.RAW -> { - out("; ---- raw assembler program ----") - out("* = ${program.actualLoadAddress.toHex()}\n") - } - } - - if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) { - out(""" - ; zeropage is clobbered so we need to reset the machine at exit - lda #>sys.reset_system - pha - lda # { - if(options.floats) - out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in - out(" jsr main.start | lda #4 | sta $01 | rts") - } - "c64" -> out(" jsr main.start | lda #31 | sta $01 | rts") - else -> jmp("main.start") - } - } - - private fun slaballocations() { - out("; memory slabs") - out("prog8_slabs\t.block") - for((name, info) in slabs) { - if(info.second>1u) - out("\t.align ${info.second.toHex()}") - out("$name\t.fill ${info.first}") - } - out("\t.bend") - } - - private fun footer() { - // the global list of all floating point constants for the whole program - out("; global float constants") - for (flt in globalFloatConsts) { - val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm() - val floatvalue = flt.key - out("${flt.value}\t.byte $floatFill ; float $floatvalue") - } - out("prog8_program_end\t; end of program label for progend()") - } - - private fun block2asm(block: Block) { - // no longer output the initialization assignments as regular statements in the block, - // they will be part of the prog8_init_vars init routine generated below. - val initializers = blockVariableInitializers.getValue(block) - val statements = block.statements.filterNot { it in initializers } - - out("\n\n; ---- block: '${block.name}' ----") - if(block.address!=null) - out("* = ${block.address!!.toHex()}") - else { - if("align_word" in block.options()) - out("\t.align 2") - else if("align_page" in block.options()) - out("\t.align $100") - } - - out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n")) - - outputSourceLine(block) - zeropagevars2asm(statements, block) - memdefs2asm(statements, block) - vardecls2asm(statements, block) - - statements.asSequence().filterIsInstance().forEach { - if(it.type==VarDeclType.VAR && it.datatype in NumericDatatypes) - it.value=null // make sure every var has no init value anymore (could be set due to 'noreinit' option) because initialization is done via explicit assignment - } - - out("\n; subroutines in this block") - - // first translate regular statements, and then put the subroutines at the end. - val (subroutine, stmts) = statements.partition { it is Subroutine } - stmts.forEach { translate(it) } - subroutine.forEach { translate(it) } - - if(!options.dontReinitGlobals) { - // generate subroutine to initialize block-level (global) variables - if (initializers.isNotEmpty()) { - out("prog8_init_vars\t.proc\n") - initializers.forEach { assign -> translate(assign) } - out(" rts\n .pend") - } - } - - out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n") - } + options.compTarget.asmsubArgsHaveRegisterClobberRisk(args, paramRegisters) private var generatedLabelSequenceNumber: Int = 0 @@ -309,7 +86,7 @@ class AsmGen(internal val program: Program, return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix" } - private fun outputSourceLine(node: Node) { + internal fun outputSourceLine(node: Node) { out(" ;\tsrc line: ${node.position.file}:${node.position.line}") } @@ -324,284 +101,6 @@ class AsmGen(internal val program: Program, } else assemblyLines.add(fragment) } - private fun zeropagevars2asm(statements: List, inBlock: Block?) { - out("; vars allocated on zeropage") - val variables = statements.asSequence().filterIsInstance().filter { it.type==VarDeclType.VAR } - val blockname = inBlock?.name - for(variable in variables) { - if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_")) - continue // the "hooks" to the temp vars are not generated as new variables - val scopedName = variable.scopedName - val zpAlloc = zeropage.variables[scopedName] - if (zpAlloc == null) { - // This var is not on the ZP yet. Attempt to move it there if it's an integer type - if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE && - variable.datatype in IntegerDatatypes - && options.zeropage != ZeropageType.DONTUSE) { - val result = zeropage.allocate(scopedName, variable.datatype, null, null, errors) - errors.report() - result.fold( - success = { (address, _) -> out("${variable.name} = $address\t; zp ${variable.datatype}") }, - failure = { /* leave it as it is, not on zeropage. */ } - ) - } - } else { - // Var has been placed in ZP, just output the address - val lenspec = when(zpAlloc.second.first) { - DataType.FLOAT, - DataType.STR, - in ArrayDatatypes -> " ${zpAlloc.second.second} bytes" - else -> "" - } - out("${variable.name} = ${zpAlloc.first}\t; zp ${variable.datatype} $lenspec") - } - } - } - - private fun vardecl2asm(decl: VarDecl, nameOverride: String?=null) { - val name = nameOverride ?: decl.name - val value = decl.value - val staticValue: Number = - if(value!=null) { - if(value is NumericLiteralValue) { - if(value.type==DataType.FLOAT) - value.number - else - value.number.toInt() - } else { - if(decl.datatype in NumericDatatypes) - throw AssemblyError("can only deal with constant numeric values for global vars $value at ${decl.position}") - else 0 - } - } else 0 - - when (decl.datatype) { - DataType.UBYTE -> out("$name\t.byte ${staticValue.toHex()}") - DataType.BYTE -> out("$name\t.char $staticValue") - DataType.UWORD -> out("$name\t.word ${staticValue.toHex()}") - DataType.WORD -> out("$name\t.sint $staticValue") - DataType.FLOAT -> { - if(staticValue==0) { - out("$name\t.byte 0,0,0,0,0 ; float") - } else { - val floatFill = compTarget.machine.getFloat(staticValue).makeFloatFillAsm() - out("$name\t.byte $floatFill ; float $staticValue") - } - } - DataType.STR -> { - throw AssemblyError("all string vars should have been interned into prog") - } - DataType.ARRAY_UB -> { - val data = makeArrayFillDataUnsigned(decl) - if (data.size <= 16) - out("$name\t.byte ${data.joinToString()}") - else { - out(name) - for (chunk in data.chunked(16)) - out(" .byte " + chunk.joinToString()) - } - } - DataType.ARRAY_B -> { - val data = makeArrayFillDataSigned(decl) - if (data.size <= 16) - out("$name\t.char ${data.joinToString()}") - else { - out(name) - for (chunk in data.chunked(16)) - out(" .char " + chunk.joinToString()) - } - } - DataType.ARRAY_UW -> { - val data = makeArrayFillDataUnsigned(decl) - if (data.size <= 16) - out("$name\t.word ${data.joinToString()}") - else { - out(name) - for (chunk in data.chunked(16)) - out(" .word " + chunk.joinToString()) - } - } - DataType.ARRAY_W -> { - val data = makeArrayFillDataSigned(decl) - if (data.size <= 16) - out("$name\t.sint ${data.joinToString()}") - else { - out(name) - for (chunk in data.chunked(16)) - out(" .sint " + chunk.joinToString()) - } - } - DataType.ARRAY_F -> { - val array = - if(decl.value!=null) - (decl.value as ArrayLiteralValue).value - else { - // no init value, use zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } - val floatFills = array.map { - val number = (it as NumericLiteralValue).number - compTarget.machine.getFloat(number).makeFloatFillAsm() - } - out(name) - for (f in array.zip(floatFills)) - out(" .byte ${f.second} ; float ${f.first}") - } - else -> { - throw AssemblyError("weird dt") - } - } - } - - private fun memdefs2asm(statements: List, inBlock: Block?) { - val blockname = inBlock?.name - - out("\n; memdefs and kernal subroutines") - val memvars = statements.asSequence().filterIsInstance().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST } - for(m in memvars) { - if(blockname!="prog8_lib" || !m.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables - if(m.value is NumericLiteralValue) - out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}") - else - out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}") - } - val asmSubs = statements.asSequence().filterIsInstance().filter { it.isAsmSubroutine } - for(sub in asmSubs) { - val addr = sub.asmAddress - if(addr!=null) { - if(sub.statements.isNotEmpty()) - throw AssemblyError("kernal subroutine cannot have statements") - out(" ${sub.name} = ${addr.toHex()}") - } - } - } - - private fun vardecls2asm(statements: List, inBlock: Block?) { - out("\n; non-zeropage variables") - val vars = statements.asSequence() - .filterIsInstance() - .filter { - it.type==VarDeclType.VAR - && it.zeropage!=ZeropageWish.REQUIRE_ZEROPAGE - && it.scopedName !in zeropage.variables - } - - vars.filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) } - .forEach { outputStringvar(it) } - - // non-string variables - val blockname = inBlock?.name - - vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach { - require(it.zeropage!=ZeropageWish.REQUIRE_ZEROPAGE) - if(!isZpVar(it.scopedName)) { - if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables - vardecl2asm(it) - } - } - } - - private fun shouldActuallyOutputStringVar(strvar: VarDecl): Boolean { - if(strvar.sharedWithAsm) - return true - val uses = callGraph.usages(strvar) - val onlyInMemoryFuncs = uses.all { - val builtinfunc = (it.parent as? IFunctionCall)?.target?.targetStatement(program) as? BuiltinFunctionPlaceholder - builtinfunc?.name=="memory" - } - return !onlyInMemoryFuncs - } - - private fun outputStringvar(strdecl: VarDecl, nameOverride: String?=null) { - val varname = nameOverride ?: strdecl.name - val sv = strdecl.value as StringLiteralValue - out("$varname\t; ${strdecl.datatype} ${sv.encoding}:\"${escape(sv.value).replace("\u0000", "")}\"") - val bytes = compTarget.encodeString(sv.value, sv.encoding).plus(0.toUByte()) - val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } - for (chunk in outputBytes.chunked(16)) - out(" .byte " + chunk.joinToString()) - } - - private fun makeArrayFillDataUnsigned(decl: VarDecl): List { - val array = - if(decl.value!=null) - (decl.value as ArrayLiteralValue).value - else { - // no array init value specified, use a list of zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } - return when (decl.datatype) { - DataType.ARRAY_UB -> - // byte array can never contain pointer-to types, so treat values as all integers - array.map { - val number = (it as NumericLiteralValue).number.toInt() - "$"+number.toString(16).padStart(2, '0') - } - DataType.ARRAY_UW -> array.map { - when (it) { - is NumericLiteralValue -> { - "$" + it.number.toInt().toString(16).padStart(4, '0') - } - is AddressOf -> { - asmSymbolName(it.identifier) - } - is IdentifierReference -> { - asmSymbolName(it) - } - else -> throw AssemblyError("weird array elt dt") - } - } - else -> throw AssemblyError("invalid arraysize type") - } - } - - private fun makeArrayFillDataSigned(decl: VarDecl): List { - val array = - if(decl.value!=null) { - if(decl.value !is ArrayLiteralValue) - throw AssemblyError("can only use array literal values as array initializer value") - (decl.value as ArrayLiteralValue).value - } - else { - // no array init value specified, use a list of zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } - return when (decl.datatype) { - DataType.ARRAY_UB -> - // byte array can never contain pointer-to types, so treat values as all integers - array.map { - val number = (it as NumericLiteralValue).number.toInt() - "$"+number.toString(16).padStart(2, '0') - } - DataType.ARRAY_B -> - // byte array can never contain pointer-to types, so treat values as all integers - array.map { - val number = (it as NumericLiteralValue).number.toInt() - val hexnum = number.absoluteValue.toString(16).padStart(2, '0') - if(number>=0) - "$$hexnum" - else - "-$$hexnum" - } - DataType.ARRAY_UW -> array.map { - val number = (it as NumericLiteralValue).number.toInt() - "$" + number.toString(16).padStart(4, '0') - } - DataType.ARRAY_W -> array.map { - val number = (it as NumericLiteralValue).number.toInt() - val hexnum = number.absoluteValue.toString(16).padStart(4, '0') - if(number>=0) - "$$hexnum" - else - "-$$hexnum" - } - else -> throw AssemblyError("invalid arraysize type ${decl.datatype}") - } - } - internal fun getFloatAsmConst(number: Double): String { val asmName = globalFloatConsts[number] if(asmName!=null) @@ -834,7 +333,7 @@ class AsmGen(internal val program: Program, is VarDecl -> translate(stmt) is Directive -> translate(stmt) is Return -> translate(stmt) - is Subroutine -> translateSubroutine(stmt) + is Subroutine -> programGen.translateSubroutine(stmt) is InlineAssembly -> translate(stmt) is FunctionCallStatement -> { val functionName = stmt.target.nameInSource.last() @@ -878,7 +377,7 @@ class AsmGen(internal val program: Program, val reg = register.toString().lowercase() val indexnum = expr.indexer.constIndex() if (indexnum != null) { - val indexValue = indexnum * compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0 + val indexValue = indexnum * options.compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0 out(" ld$reg #$indexValue") return } @@ -909,7 +408,7 @@ class AsmGen(internal val program: Program, } } DataType.FLOAT -> { - require(compTarget.memorySize(DataType.FLOAT) == 5) + require(options.compTarget.memorySize(DataType.FLOAT) == 5) out( """ lda $indexName @@ -940,7 +439,7 @@ class AsmGen(internal val program: Program, } } DataType.FLOAT -> { - require(compTarget.memorySize(DataType.FLOAT) == 5) + require(options.compTarget.memorySize(DataType.FLOAT) == 5) out( """ lda $indexName @@ -1040,158 +539,6 @@ class AsmGen(internal val program: Program, } } - - private fun translateSubroutine(sub: Subroutine) { - var onlyVariables = false - - if(sub.inline) { - if(options.optimize) { - if(sub.isAsmSubroutine || callGraph.unused(sub)) - return - - // from an inlined subroutine only the local variables are generated, - // all other code statements are omitted in the subroutine itself - // (they've been inlined at the call site, remember?) - onlyVariables = true - } - } - - out("") - outputSourceLine(sub) - - - if(sub.isAsmSubroutine) { - if(sub.asmAddress!=null) - return // already done at the memvars section - - // asmsub with most likely just an inline asm in it - out("${sub.name}\t.proc") - sub.statements.forEach { translate(it) } - out(" .pend\n") - } else { - // regular subroutine - out("${sub.name}\t.proc") - zeropagevars2asm(sub.statements, null) - memdefs2asm(sub.statements, null) - - // the main.start subroutine is the program's entrypoint and should perform some initialization logic - if(sub.name=="start" && sub.definingBlock.name=="main") - entrypointInitialization() - - if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) { - out("; simple int arg(s) passed via register(s)") - if(sub.parameters.size==1) { - val dt = sub.parameters[0].type - val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, dt, sub, variableAsmName = sub.parameters[0].name) - if(dt in ByteDatatypes) - assignRegister(RegisterOrPair.A, target) - else - assignRegister(RegisterOrPair.AY, target) - } else { - require(sub.parameters.size==2) - // 2 simple byte args, first in A, second in Y - val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name) - val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name) - assignRegister(RegisterOrPair.A, target1) - assignRegister(RegisterOrPair.Y, target2) - } - } - - if(!onlyVariables) { - out("; statements") - sub.statements.forEach { translate(it) } - } - - for(removal in removals.toList()) { - if(removal.second==sub) { - removal.second.remove(removal.first) - removals.remove(removal) - } - } - - out("; variables") - for((dt, name, addr) in sub.asmGenInfo.extraVars) { - if(addr!=null) - out("$name = $addr") - else when(dt) { - DataType.UBYTE -> out("$name .byte 0") - DataType.UWORD -> out("$name .word 0") - else -> throw AssemblyError("weird dt") - } - } - if(sub.asmGenInfo.usedRegsaveA) // will probably never occur - out("prog8_regsaveA .byte 0") - if(sub.asmGenInfo.usedRegsaveX) - out("prog8_regsaveX .byte 0") - if(sub.asmGenInfo.usedRegsaveY) - out("prog8_regsaveY .byte 0") - if(sub.asmGenInfo.usedFloatEvalResultVar1) - out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0") - if(sub.asmGenInfo.usedFloatEvalResultVar2) - out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0") - vardecls2asm(sub.statements, null) - out(" .pend\n") - } - } - - private fun entrypointInitialization() { - out("; program startup initialization") - out(" cld") - if(!options.dontReinitGlobals) { - blockVariableInitializers.forEach { - if (it.value.isNotEmpty()) - out(" jsr ${it.key.name}.prog8_init_vars") - } - } - - // string and array variables in zeropage that have initializer value, should be initialized - val stringVarsInZp = varsInZeropage.filter { it.datatype==DataType.STR && it.value!=null } - val arrayVarsInZp = varsInZeropage.filter { it.datatype in ArrayDatatypes && it.value!=null } - if(stringVarsInZp.isNotEmpty() || arrayVarsInZp.isNotEmpty()) { - out("; zp str and array initializations") - stringVarsInZp.forEach { - out(""" - lda #<${it.name} - ldy #>${it.name} - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${it.name}_init_value - ldy #>${it.name}_init_value - jsr prog8_lib.strcpy""") - } - arrayVarsInZp.forEach { - val numelements = (it.value as ArrayLiteralValue).value.size - val size = numelements * program.memsizer.memorySize(ArrayToElementTypes.getValue(it.datatype)) - out(""" - lda #<${it.name}_init_value - ldy #>${it.name}_init_value - sta cx16.r0L - sty cx16.r0H - lda #<${it.name} - ldy #>${it.name} - sta cx16.r1L - sty cx16.r1H - lda #<$size - ldy #>$size - jsr sys.memcopy""") - } - out(" jmp +") - } - - stringVarsInZp.forEach { - outputStringvar(it, it.name+"_init_value") - } - arrayVarsInZp.forEach { - vardecl2asm(it, it.name+"_init_value") - } - - out("""+ tsx - stx prog8_lib.orig_stackpointer ; required for sys.exit() - ldx #255 ; init estack ptr - clv - clc""") - } - private fun branchInstruction(condition: BranchCondition, complement: Boolean) = if(complement) { when (condition) { @@ -1314,39 +661,21 @@ class AsmGen(internal val program: Program, val repeatLabel = makeLabel("repeat") if(isTargetCpu(CpuType.CPU65c02)) { val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt) - if(counterVar!=null) { - out(""" - lda #<$count - ldy #>$count - sta $counterVar - sty $counterVar+1 + out(""" + lda #<$count + ldy #>$count + sta $counterVar + sty $counterVar+1 $repeatLabel""") - translate(stmt.body) - out(""" - lda $counterVar - bne + - dec $counterVar+1 -+ dec $counterVar - lda $counterVar - ora $counterVar+1 - bne $repeatLabel""") - } else { - out(""" - lda #<$count - ldy #>$count -$repeatLabel pha - phy""") - translate(stmt.body) - out(""" - ply - pla - bne + - dey -+ dea - bne $repeatLabel - cpy #0 - bne $repeatLabel""") - } + translate(stmt.body) + out(""" + lda $counterVar + bne + + dec $counterVar+1 ++ dec $counterVar + lda $counterVar + ora $counterVar+1 + bne $repeatLabel""") } else { val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt) out(""" @@ -1371,7 +700,7 @@ $repeatLabel""") // note: A/Y must have been loaded with the number of iterations! // no need to explicitly test for 0 iterations as this is done in the countdown logic below val repeatLabel = makeLabel("repeat") - val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)!! + val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt) out(""" sta $counterVar sty $counterVar+1 @@ -1394,17 +723,10 @@ $repeatLabel lda $counterVar val repeatLabel = makeLabel("repeat") if(isTargetCpu(CpuType.CPU65c02)) { val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt) - if(counterVar!=null) { - out(" lda #${count and 255} | sta $counterVar") - out(repeatLabel) - translate(stmt.body) - out(" dec $counterVar | bne $repeatLabel") - } else { - out(" ldy #${count and 255}") - out("$repeatLabel phy") - translate(stmt.body) - out(" ply | dey | bne $repeatLabel") - } + out(" lda #${count and 255} | sta $counterVar") + out(repeatLabel) + translate(stmt.body) + out(" dec $counterVar | bne $repeatLabel") } else { val counterVar = createRepeatCounterVar(DataType.UBYTE, false, stmt) out(" lda #${count and 255} | sta $counterVar") @@ -1419,19 +741,12 @@ $repeatLabel lda $counterVar val repeatLabel = makeLabel("repeat") if(isTargetCpu(CpuType.CPU65c02)) { val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt) - if(counterVar!=null) { - out(" beq $endLabel | sty $counterVar") - out(repeatLabel) - translate(stmt.body) - out(" dec $counterVar | bne $repeatLabel") - } else { - out(" beq $endLabel") - out("$repeatLabel phy") - translate(stmt.body) - out(" ply | dey | bne $repeatLabel") - } + out(" beq $endLabel | sty $counterVar") + out(repeatLabel) + translate(stmt.body) + out(" dec $counterVar | bne $repeatLabel") } else { - val counterVar = createRepeatCounterVar(DataType.UBYTE, false, stmt)!! + val counterVar = createRepeatCounterVar(DataType.UBYTE, false, stmt) out(" beq $endLabel | sty $counterVar") out(repeatLabel) translate(stmt.body) @@ -1440,7 +755,7 @@ $repeatLabel lda $counterVar out(endLabel) } - private fun createRepeatCounterVar(dt: DataType, mustBeInZeropage: Boolean, stmt: RepeatLoop): String? { + private fun createRepeatCounterVar(dt: DataType, preferZeropage: Boolean, stmt: RepeatLoop): String { val asmInfo = stmt.definingSubroutine!!.asmGenInfo var parent = stmt.parent while(parent !is ParentSentinel) { @@ -1454,7 +769,7 @@ $repeatLabel lda $counterVar // we can re-use a counter var from the subroutine if it already has one for that datatype val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt } if(existingVar!=null) { - if(!mustBeInZeropage || existingVar.third!=null) + if(!preferZeropage || existingVar.third!=null) return existingVar.second } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt index 8398c63f7..0922ddbe0 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt @@ -3,10 +3,7 @@ package prog8.codegen.cpu6502 import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import com.github.michaelbull.result.mapError -import prog8.compilerinterface.CompilationOptions -import prog8.compilerinterface.IAssemblyProgram -import prog8.compilerinterface.OutputType -import prog8.compilerinterface.viceMonListName +import prog8.compilerinterface.* import prog8.parser.SourceCode import java.io.File import java.nio.file.Path @@ -17,7 +14,7 @@ import kotlin.io.path.isRegularFile class AssemblyProgram( override val name: String, outputDir: Path, - private val compTarget: String) : IAssemblyProgram { + private val compTarget: ICompilationTarget) : IAssemblyProgram { private val assemblyFile = outputDir.resolve("$name.asm") private val prgFile = outputDir.resolve("$name.prg") @@ -41,12 +38,12 @@ class AssemblyProgram( val outFile = when (options.output) { OutputType.PRG -> { command.add("--cbm-prg") - println("\nCreating prg for target $compTarget.") + println("\nCreating prg for target ${compTarget.name}.") prgFile } OutputType.RAW -> { command.add("--nostart") - println("\nCreating raw binary for target $compTarget.") + println("\nCreating raw binary for target ${compTarget.name}.") binFile } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index 33946d83e..0441deabb 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -454,7 +454,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val val size = (fcall.args[1] as NumericLiteralValue).number.toUInt() val align = (fcall.args[2] as NumericLiteralValue).number.toUInt() - val existing = asmgen.slabs[name] + val existing = asmgen.getMemorySlab(name) if(existing!=null && (existing.first!=size || existing.second!=align)) throw AssemblyError("memory slab '$name' already exists with a different size or alignment at ${fcall.position}") @@ -468,7 +468,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen) val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position) asmgen.translateNormalAssignment(assign) - asmgen.slabs[name] = Pair(size, align) + asmgen.allocateMemorySlab(name, size, align) } private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt new file mode 100644 index 000000000..d24892171 --- /dev/null +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt @@ -0,0 +1,683 @@ +package prog8.codegen.cpu6502 + +import com.github.michaelbull.result.fold +import com.github.michaelbull.result.onSuccess +import prog8.ast.IFunctionCall +import prog8.ast.IStatementContainer +import prog8.ast.Program +import prog8.ast.antlr.escape +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.* +import prog8.ast.toHex +import prog8.codegen.cpu6502.assignment.AsmAssignTarget +import prog8.codegen.cpu6502.assignment.TargetStorageKind +import prog8.compilerinterface.* +import java.time.LocalDate +import java.time.LocalDateTime +import kotlin.math.absoluteValue + + +internal class ProgramGen( + val program: Program, + val variables: IVariablesAndConsts, + val options: CompilationOptions, + val errors: IErrorReporter, + private val functioncallAsmGen: FunctionCallAsmGen, + private val asmgen: AsmGen +) { + private val compTarget = options.compTarget + private val removals = mutableListOf>() + private val varsInZeropage = mutableSetOf() + private val callGraph = CallGraph(program) + private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance() } + + internal fun generate() { + // variables.dump(program.memsizer) // TODO + + val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value } + require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"} + + header() + val allBlocks = program.allBlocks + if(allBlocks.first().name != "main") + throw AssemblyError("first block should be 'main'") + + allocateAllZeropageVariables() + if(errors.noErrors()) { + program.allBlocks.forEach { block2asm(it) } + + for(removal in removals.toList()) { + removal.second.remove(removal.first) + removals.remove(removal) + } + + slaballocations() + footer() + } + } + + private fun header() { + val ourName = this.javaClass.name + val cpu = when(compTarget.machine.cpu) { + CpuType.CPU6502 -> "6502" + CpuType.CPU65c02 -> "w65c02" + else -> "unsupported" + } + + asmgen.out("; $cpu assembly code for '${program.name}'") + asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}") + asmgen.out("; assembler syntax is for the 64tasm cross-assembler") + asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") + asmgen.out("\n.cpu '$cpu'\n.enc 'none'\n") + + program.actualLoadAddress = program.definedLoadAddress + if (program.actualLoadAddress == 0u) // fix load address + program.actualLoadAddress = if (options.launcher == LauncherType.BASIC) + compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS + + // the global prog8 variables needed + val zp = compTarget.machine.zeropage + asmgen.out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}") + asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}") + asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word") + asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word") + asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}") + asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}") + + when { + options.launcher == LauncherType.BASIC -> { + if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS) + throw AssemblyError("BASIC output must have correct load address") + asmgen.out("; ---- basic program with sys call ----") + asmgen.out("* = ${program.actualLoadAddress.toHex()}") + val year = LocalDate.now().year + asmgen.out(" .word (+), $year") + asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") + asmgen.out("+\t.word 0") + asmgen.out("prog8_entrypoint\t; assembly code starts here\n") + if(!options.noSysInit) + asmgen.out(" jsr ${compTarget.name}.init_system") + asmgen.out(" jsr ${compTarget.name}.init_system_phase2") + } + options.output == OutputType.PRG -> { + asmgen.out("; ---- program without basic sys call ----") + asmgen.out("* = ${program.actualLoadAddress.toHex()}\n") + if(!options.noSysInit) + asmgen.out(" jsr ${compTarget.name}.init_system") + asmgen.out(" jsr ${compTarget.name}.init_system_phase2") + } + options.output == OutputType.RAW -> { + asmgen.out("; ---- raw assembler program ----") + asmgen.out("* = ${program.actualLoadAddress.toHex()}\n") + } + } + + if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) { + asmgen.out(""" + ; zeropage is clobbered so we need to reset the machine at exit + lda #>sys.reset_system + pha + lda # { + if(options.floats) + asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in + asmgen.out(" jsr main.start | lda #4 | sta $01 | rts") + } + "c64" -> asmgen.out(" jsr main.start | lda #31 | sta $01 | rts") + else -> asmgen.jmp("main.start") + } + } + + private fun slaballocations() { + asmgen.out("; memory slabs") + asmgen.out("prog8_slabs\t.block") + for((name, info) in asmgen.allMemorySlabs) { + if(info.second>1u) + asmgen.out("\t.align ${info.second.toHex()}") + asmgen.out("$name\t.fill ${info.first}") + } + asmgen.out("\t.bend") + } + + private fun footer() { + // the global list of all floating point constants for the whole program + asmgen.out("; global float constants") + for (flt in asmgen.globalFloatConsts) { + val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm() + val floatvalue = flt.key + asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue") + } + asmgen.out("prog8_program_end\t; end of program label for progend()") + } + + private fun block2asm(block: Block) { + // no longer output the initialization assignments as regular statements in the block, + // they will be part of the prog8_init_vars init routine generated below. + val initializers = blockVariableInitializers.getValue(block) + val statements = block.statements.filterNot { it in initializers } + + asmgen.out("\n\n; ---- block: '${block.name}' ----") + if(block.address!=null) + asmgen.out("* = ${block.address!!.toHex()}") + else { + if("align_word" in block.options()) + asmgen.out("\t.align 2") + else if("align_page" in block.options()) + asmgen.out("\t.align $100") + } + + asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n")) + + asmgen.outputSourceLine(block) + zeropagevars2asm(statements, block) + memdefs2asm(statements, block) + vardecls2asm(statements, block) + + statements.asSequence().filterIsInstance().forEach { + if(it.type== VarDeclType.VAR && it.datatype in NumericDatatypes) + it.value=null // make sure every var has no init value any longer (could be set due to 'noreinit' option) because initialization is done via explicit assignment + } + + asmgen.out("\n; subroutines in this block") + + // first translate regular statements, and then put the subroutines at the end. + val (subroutine, stmts) = statements.partition { it is Subroutine } + stmts.forEach { asmgen.translate(it) } + subroutine.forEach { asmgen.translate(it) } + + if(!options.dontReinitGlobals) { + // generate subroutine to initialize block-level (global) variables + if (initializers.isNotEmpty()) { + asmgen.out("prog8_init_vars\t.proc\n") + initializers.forEach { assign -> asmgen.translate(assign) } + asmgen.out(" rts\n .pend") + } + } + + asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n") + } + + internal fun translateSubroutine(sub: Subroutine) { + var onlyVariables = false + + if(sub.inline) { + if(options.optimize) { + if(sub.isAsmSubroutine || callGraph.unused(sub)) + return + + // from an inlined subroutine only the local variables are generated, + // all other code statements are omitted in the subroutine itself + // (they've been inlined at the call site, remember?) + onlyVariables = true + } + } + + asmgen.out("") + asmgen.outputSourceLine(sub) + + + if(sub.isAsmSubroutine) { + if(sub.asmAddress!=null) + return // already done at the memvars section + + // asmsub with most likely just an inline asm in it + asmgen.out("${sub.name}\t.proc") + sub.statements.forEach { asmgen.translate(it) } + asmgen.out(" .pend\n") + } else { + // regular subroutine + asmgen.out("${sub.name}\t.proc") + zeropagevars2asm(sub.statements, null) + memdefs2asm(sub.statements, null) + + // the main.start subroutine is the program's entrypoint and should perform some initialization logic + if(sub.name=="start" && sub.definingBlock.name=="main") + entrypointInitialization() + + if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) { + asmgen.out("; simple int arg(s) passed via register(s)") + if(sub.parameters.size==1) { + val dt = sub.parameters[0].type + val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, sub, variableAsmName = sub.parameters[0].name) + if(dt in ByteDatatypes) + asmgen.assignRegister(RegisterOrPair.A, target) + else + asmgen.assignRegister(RegisterOrPair.AY, target) + } else { + require(sub.parameters.size==2) + // 2 simple byte args, first in A, second in Y + val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name) + val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name) + asmgen.assignRegister(RegisterOrPair.A, target1) + asmgen.assignRegister(RegisterOrPair.Y, target2) + } + } + + if(!onlyVariables) { + asmgen.out("; statements") + sub.statements.forEach { asmgen.translate(it) } + } + + for(removal in removals.toList()) { + if(removal.second==sub) { + removal.second.remove(removal.first) + removals.remove(removal) + } + } + + asmgen.out("; variables") + for((dt, name, addr) in sub.asmGenInfo.extraVars) { + if(addr!=null) + asmgen.out("$name = $addr") + else when(dt) { + DataType.UBYTE -> asmgen.out("$name .byte 0") + DataType.UWORD -> asmgen.out("$name .word 0") + else -> throw AssemblyError("weird dt") + } + } + if(sub.asmGenInfo.usedRegsaveA) // will probably never occur + asmgen.out("prog8_regsaveA .byte 0") + if(sub.asmGenInfo.usedRegsaveX) + asmgen.out("prog8_regsaveX .byte 0") + if(sub.asmGenInfo.usedRegsaveY) + asmgen.out("prog8_regsaveY .byte 0") + if(sub.asmGenInfo.usedFloatEvalResultVar1) + asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0") + if(sub.asmGenInfo.usedFloatEvalResultVar2) + asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0") + vardecls2asm(sub.statements, null) + asmgen.out(" .pend\n") + } + } + + private fun entrypointInitialization() { + asmgen.out("; program startup initialization") + asmgen.out(" cld") + if(!options.dontReinitGlobals) { + blockVariableInitializers.forEach { + if (it.value.isNotEmpty()) + asmgen.out(" jsr ${it.key.name}.prog8_init_vars") + } + } + + // string and array variables in zeropage that have initializer value, should be initialized + val stringVarsInZp = varsInZeropage.filter { it.datatype==DataType.STR && it.value!=null } + val arrayVarsInZp = varsInZeropage.filter { it.datatype in ArrayDatatypes && it.value!=null } + if(stringVarsInZp.isNotEmpty() || arrayVarsInZp.isNotEmpty()) { + asmgen.out("; zp str and array initializations") + stringVarsInZp.forEach { + asmgen.out(""" + lda #<${it.name} + ldy #>${it.name} + sta P8ZP_SCRATCH_W1 + sty P8ZP_SCRATCH_W1+1 + lda #<${it.name}_init_value + ldy #>${it.name}_init_value + jsr prog8_lib.strcpy""") + } + arrayVarsInZp.forEach { + val numelements = (it.value as ArrayLiteralValue).value.size + val size = numelements * program.memsizer.memorySize(ArrayToElementTypes.getValue(it.datatype)) + asmgen.out(""" + lda #<${it.name}_init_value + ldy #>${it.name}_init_value + sta cx16.r0L + sty cx16.r0H + lda #<${it.name} + ldy #>${it.name} + sta cx16.r1L + sty cx16.r1H + lda #<$size + ldy #>$size + jsr sys.memcopy""") + } + asmgen.out(" jmp +") + } + + stringVarsInZp.forEach { + outputStringvar(it, it.name+"_init_value") + } + arrayVarsInZp.forEach { + vardecl2asm(it, it.name+"_init_value") + } + + asmgen.out("""+ tsx + stx prog8_lib.orig_stackpointer ; required for sys.exit() + ldx #255 ; init estack ptr + clv + clc""") + } + + private fun allocateAllZeropageVariables() { + if(options.zeropage==ZeropageType.DONTUSE) + return + val allVariables = this.callGraph.allIdentifiers.asSequence() + .map { it.value } + .filterIsInstance() + .filter { it.type==VarDeclType.VAR } + .toSet() + .map { it to it.scopedName } + val varsRequiringZp = allVariables.filter { it.first.zeropage==ZeropageWish.REQUIRE_ZEROPAGE } + val varsPreferringZp = allVariables + .filter { it.first.zeropage==ZeropageWish.PREFER_ZEROPAGE } + .sortedBy { options.compTarget.memorySize(it.first.datatype) } // allocate the smallest DT first + + for ((vardecl, scopedname) in varsRequiringZp) { + val numElements: Int? = when(vardecl.datatype) { + DataType.STR -> { + (vardecl.value as StringLiteralValue).value.length + } + in ArrayDatatypes -> { + vardecl.arraysize!!.constIndex() + } + else -> null + } + val result = asmgen.zeropage.allocate(scopedname, vardecl.datatype, numElements, vardecl.position, errors) + result.fold( + success = { varsInZeropage.add(vardecl) }, + failure = { errors.err(it.message!!, vardecl.position) } + ) + } + if(errors.noErrors()) { + varsPreferringZp.forEach { (vardecl, scopedname) -> + val arraySize: Int? = when (vardecl.datatype) { + DataType.STR -> { + (vardecl.value as StringLiteralValue).value.length + } + in ArrayDatatypes -> { + vardecl.arraysize!!.constIndex() + } + else -> null + } + val result = asmgen.zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors) + result.onSuccess { varsInZeropage.add(vardecl) } + // no need to check for error, if there is one, just allocate in normal system ram later. + } + } + } + + private fun zeropagevars2asm(statements: List, inBlock: Block?) { + asmgen.out("; vars allocated on zeropage") + val variables = statements.asSequence().filterIsInstance().filter { it.type==VarDeclType.VAR } + val blockname = inBlock?.name + for(variable in variables) { + if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_")) + continue // the "hooks" to the temp vars are not generated as new variables + val scopedName = variable.scopedName + val zpAlloc = asmgen.zeropage.variables[scopedName] + if (zpAlloc == null) { + // This var is not on the ZP yet. Attempt to move it there if it's an integer type + if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE && + variable.datatype in IntegerDatatypes + && options.zeropage != ZeropageType.DONTUSE) { + val result = asmgen.zeropage.allocate(scopedName, variable.datatype, null, null, errors) + errors.report() + result.fold( + success = { (address, _) -> asmgen.out("${variable.name} = $address\t; zp ${variable.datatype}") }, + failure = { /* leave it as it is, not on zeropage. */ } + ) + } + } else { + // Var has been placed in ZP, just output the address + val lenspec = when(zpAlloc.second.first) { + DataType.FLOAT, + DataType.STR, + in ArrayDatatypes -> " ${zpAlloc.second.second} bytes" + else -> "" + } + asmgen.out("${variable.name} = ${zpAlloc.first}\t; zp ${variable.datatype} $lenspec") + } + } + } + + private fun vardecl2asm(decl: VarDecl, nameOverride: String?=null) { + val name = nameOverride ?: decl.name + val value = decl.value + val staticValue: Number = + if(value!=null) { + if(value is NumericLiteralValue) { + if(value.type== DataType.FLOAT) + value.number + else + value.number.toInt() + } else { + if(decl.datatype in NumericDatatypes) + throw AssemblyError("can only deal with constant numeric values for global vars $value at ${decl.position}") + else 0 + } + } else 0 + + when (decl.datatype) { + DataType.UBYTE -> asmgen.out("$name\t.byte ${staticValue.toHex()}") + DataType.BYTE -> asmgen.out("$name\t.char $staticValue") + DataType.UWORD -> asmgen.out("$name\t.word ${staticValue.toHex()}") + DataType.WORD -> asmgen.out("$name\t.sint $staticValue") + DataType.FLOAT -> { + if(staticValue==0) { + asmgen.out("$name\t.byte 0,0,0,0,0 ; float") + } else { + val floatFill = compTarget.machine.getFloat(staticValue).makeFloatFillAsm() + asmgen.out("$name\t.byte $floatFill ; float $staticValue") + } + } + DataType.STR -> { + throw AssemblyError("all string vars should have been interned into prog") + } + DataType.ARRAY_UB -> { + val data = makeArrayFillDataUnsigned(decl) + if (data.size <= 16) + asmgen.out("$name\t.byte ${data.joinToString()}") + else { + asmgen.out(name) + for (chunk in data.chunked(16)) + asmgen.out(" .byte " + chunk.joinToString()) + } + } + DataType.ARRAY_B -> { + val data = makeArrayFillDataSigned(decl) + if (data.size <= 16) + asmgen.out("$name\t.char ${data.joinToString()}") + else { + asmgen.out(name) + for (chunk in data.chunked(16)) + asmgen.out(" .char " + chunk.joinToString()) + } + } + DataType.ARRAY_UW -> { + val data = makeArrayFillDataUnsigned(decl) + if (data.size <= 16) + asmgen.out("$name\t.word ${data.joinToString()}") + else { + asmgen.out(name) + for (chunk in data.chunked(16)) + asmgen.out(" .word " + chunk.joinToString()) + } + } + DataType.ARRAY_W -> { + val data = makeArrayFillDataSigned(decl) + if (data.size <= 16) + asmgen.out("$name\t.sint ${data.joinToString()}") + else { + asmgen.out(name) + for (chunk in data.chunked(16)) + asmgen.out(" .sint " + chunk.joinToString()) + } + } + DataType.ARRAY_F -> { + val array = + if(decl.value!=null) + (decl.value as ArrayLiteralValue).value + else { + // no init value, use zeros + val zero = decl.zeroElementValue() + Array(decl.arraysize!!.constIndex()!!) { zero } + } + val floatFills = array.map { + val number = (it as NumericLiteralValue).number + compTarget.machine.getFloat(number).makeFloatFillAsm() + } + asmgen.out(name) + for (f in array.zip(floatFills)) + asmgen.out(" .byte ${f.second} ; float ${f.first}") + } + else -> { + throw AssemblyError("weird dt") + } + } + } + + private fun memdefs2asm(statements: List, inBlock: Block?) { + val blockname = inBlock?.name + + asmgen.out("\n; memdefs and kernal subroutines") + val memvars = statements.asSequence().filterIsInstance().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST } + for(m in memvars) { + if(blockname!="prog8_lib" || !m.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables + if(m.value is NumericLiteralValue) + asmgen.out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}") + else + asmgen.out(" ${m.name} = ${asmgen.asmVariableName((m.value as AddressOf).identifier)}") + } + val asmSubs = statements.asSequence().filterIsInstance().filter { it.isAsmSubroutine } + for(sub in asmSubs) { + val addr = sub.asmAddress + if(addr!=null) { + if(sub.statements.isNotEmpty()) + throw AssemblyError("kernal subroutine cannot have statements") + asmgen.out(" ${sub.name} = ${addr.toHex()}") + } + } + } + + private fun vardecls2asm(statements: List, inBlock: Block?) { + asmgen.out("\n; non-zeropage variables") + val vars = statements.asSequence() + .filterIsInstance() + .filter { + it.type==VarDeclType.VAR + && it.zeropage!= ZeropageWish.REQUIRE_ZEROPAGE + && it.scopedName !in asmgen.zeropage.variables + } + + vars.filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) } + .forEach { outputStringvar(it) } + + // non-string variables + val blockname = inBlock?.name + + vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach { + require(it.zeropage!= ZeropageWish.REQUIRE_ZEROPAGE) + if(!asmgen.isZpVar(it.scopedName)) { + if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables + vardecl2asm(it) + } + } + } + + private fun shouldActuallyOutputStringVar(strvar: VarDecl): Boolean { + if(strvar.sharedWithAsm) + return true + val uses = callGraph.usages(strvar) + val onlyInMemoryFuncs = uses.all { + val builtinfunc = (it.parent as? IFunctionCall)?.target?.targetStatement(program) as? BuiltinFunctionPlaceholder + builtinfunc?.name=="memory" + } + return !onlyInMemoryFuncs + } + + private fun outputStringvar(strdecl: VarDecl, nameOverride: String?=null) { + val varname = nameOverride ?: strdecl.name + val sv = strdecl.value as StringLiteralValue + asmgen.out("$varname\t; ${strdecl.datatype} ${sv.encoding}:\"${escape(sv.value).replace("\u0000", "")}\"") + val bytes = compTarget.encodeString(sv.value, sv.encoding).plus(0.toUByte()) + val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } + for (chunk in outputBytes.chunked(16)) + asmgen.out(" .byte " + chunk.joinToString()) + } + + private fun makeArrayFillDataUnsigned(decl: VarDecl): List { + val array = + if(decl.value!=null) + (decl.value as ArrayLiteralValue).value + else { + // no array init value specified, use a list of zeros + val zero = decl.zeroElementValue() + Array(decl.arraysize!!.constIndex()!!) { zero } + } + return when (decl.datatype) { + DataType.ARRAY_UB -> + // byte array can never contain pointer-to types, so treat values as all integers + array.map { + val number = (it as NumericLiteralValue).number.toInt() + "$"+number.toString(16).padStart(2, '0') + } + DataType.ARRAY_UW -> array.map { + when (it) { + is NumericLiteralValue -> { + "$" + it.number.toInt().toString(16).padStart(4, '0') + } + is AddressOf -> { + asmgen.asmSymbolName(it.identifier) + } + is IdentifierReference -> { + asmgen.asmSymbolName(it) + } + else -> throw AssemblyError("weird array elt dt") + } + } + else -> throw AssemblyError("invalid arraysize type") + } + } + + private fun makeArrayFillDataSigned(decl: VarDecl): List { + val array = + if(decl.value!=null) { + if(decl.value !is ArrayLiteralValue) + throw AssemblyError("can only use array literal values as array initializer value") + (decl.value as ArrayLiteralValue).value + } + else { + // no array init value specified, use a list of zeros + val zero = decl.zeroElementValue() + Array(decl.arraysize!!.constIndex()!!) { zero } + } + return when (decl.datatype) { + DataType.ARRAY_UB -> + // byte array can never contain pointer-to types, so treat values as all integers + array.map { + val number = (it as NumericLiteralValue).number.toInt() + "$"+number.toString(16).padStart(2, '0') + } + DataType.ARRAY_B -> + // byte array can never contain pointer-to types, so treat values as all integers + array.map { + val number = (it as NumericLiteralValue).number.toInt() + val hexnum = number.absoluteValue.toString(16).padStart(2, '0') + if(number>=0) + "$$hexnum" + else + "-$$hexnum" + } + DataType.ARRAY_UW -> array.map { + val number = (it as NumericLiteralValue).number.toInt() + "$" + number.toString(16).padStart(4, '0') + } + DataType.ARRAY_W -> array.map { + val number = (it as NumericLiteralValue).number.toInt() + val hexnum = number.absoluteValue.toString(16).padStart(4, '0') + if(number>=0) + "$$hexnum" + else + "-$$hexnum" + } + else -> throw AssemblyError("invalid arraysize type ${decl.datatype}") + } + } + +} \ No newline at end of file