mirror of
https://github.com/irmen/prog8.git
synced 2024-06-14 00:29:34 +00:00
1490 lines
65 KiB
Kotlin
1490 lines
65 KiB
Kotlin
package prog8.compiler.target.c64.codegen2
|
|
|
|
import prog8.ast.IFunctionCall
|
|
import prog8.ast.Node
|
|
import prog8.ast.Program
|
|
import prog8.ast.antlr.escape
|
|
import prog8.ast.base.*
|
|
import prog8.ast.expressions.*
|
|
import prog8.ast.statements.*
|
|
import prog8.compiler.*
|
|
import prog8.compiler.target.c64.AssemblyProgram
|
|
import prog8.compiler.target.c64.MachineDefinition
|
|
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
|
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
|
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
|
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
import prog8.compiler.target.c64.Petscii
|
|
import prog8.functions.BuiltinFunctions
|
|
import java.io.File
|
|
import java.math.RoundingMode
|
|
import java.util.*
|
|
import kotlin.math.absoluteValue
|
|
|
|
|
|
internal class AssemblyError(msg: String) : RuntimeException(msg)
|
|
|
|
|
|
internal class AsmGen2(val program: Program,
|
|
val options: CompilationOptions,
|
|
val zeropage: Zeropage) {
|
|
|
|
private val assemblyLines = mutableListOf<String>()
|
|
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
|
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
|
private val breakpointLabels = mutableListOf<String>()
|
|
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, options, zeropage, this)
|
|
|
|
internal fun compileToAssembly(optimize: Boolean): AssemblyProgram {
|
|
assemblyLines.clear()
|
|
println("Generating assembly code... ")
|
|
|
|
header()
|
|
val allBlocks = program.allBlocks()
|
|
if(allBlocks.first().name != "main")
|
|
throw AssemblyError("first block should be 'main'")
|
|
for(b in program.allBlocks())
|
|
block2asm(b)
|
|
footer()
|
|
|
|
if(optimize) {
|
|
var optimizationsDone = 1
|
|
while (optimizationsDone > 0) {
|
|
optimizationsDone = optimizeAssembly(assemblyLines)
|
|
}
|
|
}
|
|
|
|
File("${program.name}.asm").printWriter().use {
|
|
for (line in assemblyLines) { it.println(line) }
|
|
}
|
|
|
|
return AssemblyProgram(program.name)
|
|
}
|
|
|
|
private fun header() {
|
|
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")
|
|
|
|
program.actualLoadAddress = program.definedLoadAddress
|
|
if (program.actualLoadAddress == 0) // fix load address
|
|
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
|
|
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
|
|
|
|
when {
|
|
options.launcher == LauncherType.BASIC -> {
|
|
if (program.actualLoadAddress != 0x0801)
|
|
throw AssemblyError("BASIC output must have load address $0801")
|
|
out("; ---- basic program with sys call ----")
|
|
out("* = ${program.actualLoadAddress.toHex()}")
|
|
val year = Calendar.getInstance().get(Calendar.YEAR)
|
|
out(" .word (+), $year")
|
|
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
|
out("+\t.word 0")
|
|
out("_prog8_entrypoint\t; assembly code starts here\n")
|
|
out(" jsr prog8_lib.init_system")
|
|
}
|
|
options.output == OutputType.PRG -> {
|
|
out("; ---- program without basic sys call ----")
|
|
out("* = ${program.actualLoadAddress.toHex()}\n")
|
|
out(" jsr prog8_lib.init_system")
|
|
}
|
|
options.output == OutputType.RAW -> {
|
|
out("; ---- raw assembler program ----")
|
|
out("* = ${program.actualLoadAddress.toHex()}\n")
|
|
}
|
|
}
|
|
|
|
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
|
// disable shift-commodore charset switching and run/stop key
|
|
out(" lda #$80")
|
|
out(" lda #$80")
|
|
out(" sta 657\t; disable charset switching")
|
|
out(" lda #239")
|
|
out(" sta 808\t; disable run/stop key")
|
|
}
|
|
|
|
out(" ldx #\$ff\t; init estack pointer")
|
|
|
|
out(" ; initialize the variables in each block")
|
|
for (block in program.allBlocks()) {
|
|
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
|
|
if(initVarsSub!=null)
|
|
out(" jsr ${block.name}.$initvarsSubName")
|
|
}
|
|
|
|
out(" clc")
|
|
when (zeropage.exitProgramStrategy) {
|
|
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
|
out(" jmp main.start\t; jump to program entrypoint")
|
|
}
|
|
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
|
out(" jsr main.start\t; call program entrypoint")
|
|
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
|
}
|
|
}
|
|
out("")
|
|
}
|
|
|
|
private fun footer() {
|
|
// the global list of all floating point constants for the whole program
|
|
out("; global float constants")
|
|
for (flt in globalFloatConsts) {
|
|
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key)
|
|
val floatFill = makeFloatFill(mflpt5)
|
|
val floatvalue = flt.key
|
|
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
|
}
|
|
}
|
|
|
|
private fun block2asm(block: Block) {
|
|
out("\n; ---- block: '${block.name}' ----")
|
|
out("${block.name}\t.proc\n") // TODO not if force_output?
|
|
if(block.address!=null) {
|
|
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'")
|
|
out("* = ${block.address.toHex()}")
|
|
}
|
|
|
|
outputSourceLine(block)
|
|
zeropagevars2asm(block.statements)
|
|
memdefs2asm(block.statements)
|
|
vardecls2asm(block.statements)
|
|
out("\n; subroutines in this block")
|
|
|
|
// first translate regular statements, and then put the subroutines at the end.
|
|
val (subroutine, stmts) = block.statements.partition { it is Subroutine }
|
|
stmts.forEach { translate(it) }
|
|
subroutine.forEach { translateSubroutine(it as Subroutine) }
|
|
|
|
out("\n\t.pend\n") // TODO not if force_output?
|
|
}
|
|
|
|
private var generatedLabelSequenceNumber: Int = 0
|
|
|
|
private fun makeLabel(postfix: String): String {
|
|
generatedLabelSequenceNumber++
|
|
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
|
|
}
|
|
|
|
private fun outputSourceLine(node: Node) {
|
|
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
|
|
}
|
|
|
|
internal fun out(str: String, splitlines: Boolean = true) {
|
|
val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n')
|
|
|
|
if (splitlines) {
|
|
for (line in fragment.split('\n')) {
|
|
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
|
|
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
|
assemblyLines.add(trimmed)
|
|
}
|
|
} else assemblyLines.add(fragment)
|
|
}
|
|
|
|
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
|
|
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
|
|
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
|
|
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
|
|
val b3 = "$" + flt.b3.toString(16).padStart(2, '0')
|
|
val b4 = "$" + flt.b4.toString(16).padStart(2, '0')
|
|
return "$b0, $b1, $b2, $b3, $b4"
|
|
}
|
|
|
|
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
|
return when(dt) {
|
|
DataType.STR -> {
|
|
val bytes = Petscii.encodePetscii(str, true)
|
|
bytes.plus(0)
|
|
}
|
|
DataType.STR_S -> {
|
|
val bytes = Petscii.encodeScreencode(str, true)
|
|
bytes.plus(0)
|
|
}
|
|
else -> throw AssemblyError("invalid str type")
|
|
}
|
|
}
|
|
|
|
private fun zeropagevars2asm(statements: List<Statement>) {
|
|
out("; vars allocated on zeropage")
|
|
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
|
for(variable in variables) {
|
|
// should NOT allocate subroutine parameters on the zero page
|
|
val fullName = variable.scopedname
|
|
val zpVar = allocatedZeropageVariables[fullName]
|
|
if(zpVar==null) {
|
|
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
|
if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE &&
|
|
variable.datatype in zeropage.allowedDatatypes
|
|
&& variable.datatype != DataType.FLOAT) {
|
|
try {
|
|
val address = zeropage.allocate(fullName, variable.datatype, null)
|
|
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
|
// make sure we add the var to the set of zpvars for this block
|
|
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
|
} catch (x: ZeropageDepletedError) {
|
|
// leave it as it is.
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
throw AssemblyError("huh, var is already on zp $zpVar")
|
|
// it was already allocated on the zp, what to do?
|
|
// out("${variable.name} = ${zpVar.first}\t; zp ${zpVar.second}")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun vardecl2asm(decl: VarDecl) {
|
|
when (decl.datatype) {
|
|
DataType.UBYTE -> out("${decl.name}\t.byte 0")
|
|
DataType.BYTE -> out("${decl.name}\t.char 0")
|
|
DataType.UWORD -> out("${decl.name}\t.word 0")
|
|
DataType.WORD -> out("${decl.name}\t.sint 0")
|
|
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
|
|
DataType.STRUCT -> {} // is flattened
|
|
DataType.STR, DataType.STR_S -> {
|
|
val string = (decl.value as ReferenceLiteralValue).str!!
|
|
val bytes = encodeStr(string, decl.datatype).map { "$" + it.toString(16).padStart(2, '0') }
|
|
out("${decl.name}\t; ${decl.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
|
|
for (chunk in bytes.chunked(16))
|
|
out(" .byte " + chunk.joinToString())
|
|
}
|
|
DataType.ARRAY_UB -> {
|
|
val data = makeArrayFillDataUnsigned(decl)
|
|
if (data.size <= 16)
|
|
out("${decl.name}\t.byte ${data.joinToString()}")
|
|
else {
|
|
out(decl.name)
|
|
for (chunk in data.chunked(16))
|
|
out(" .byte " + chunk.joinToString())
|
|
}
|
|
}
|
|
DataType.ARRAY_B -> {
|
|
val data = makeArrayFillDataSigned(decl)
|
|
if (data.size <= 16)
|
|
out("${decl.name}\t.char ${data.joinToString()}")
|
|
else {
|
|
out(decl.name)
|
|
for (chunk in data.chunked(16))
|
|
out(" .char " + chunk.joinToString())
|
|
}
|
|
}
|
|
DataType.ARRAY_UW -> {
|
|
val data = makeArrayFillDataUnsigned(decl)
|
|
if (data.size <= 16)
|
|
out("${decl.name}\t.word ${data.joinToString()}")
|
|
else {
|
|
out(decl.name)
|
|
for (chunk in data.chunked(16))
|
|
out(" .word " + chunk.joinToString())
|
|
}
|
|
}
|
|
DataType.ARRAY_W -> {
|
|
val data = makeArrayFillDataSigned(decl)
|
|
if (data.size <= 16)
|
|
out("${decl.name}\t.sint ${data.joinToString()}")
|
|
else {
|
|
out(decl.name)
|
|
for (chunk in data.chunked(16))
|
|
out(" .sint " + chunk.joinToString())
|
|
}
|
|
}
|
|
DataType.ARRAY_F -> {
|
|
val array = (decl.value as ReferenceLiteralValue).array!!
|
|
val floatFills = array.map {
|
|
val number = (it as NumericLiteralValue).number
|
|
makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number))
|
|
}
|
|
out(decl.name)
|
|
for (f in array.zip(floatFills))
|
|
out(" .byte ${f.second} ; float ${f.first}")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun memdefs2asm(statements: List<Statement>) {
|
|
out("\n; memdefs and kernel subroutines")
|
|
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
|
|
for(m in memvars) {
|
|
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
|
|
}
|
|
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
|
|
for(sub in asmSubs) {
|
|
if(sub.asmAddress!=null) {
|
|
if(sub.statements.isNotEmpty())
|
|
throw AssemblyError("kernel subroutine cannot have statements")
|
|
out(" ${sub.name} = ${sub.asmAddress.toHex()}")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun vardecls2asm(statements: List<Statement>) {
|
|
out("\n; non-zeropage variables")
|
|
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
|
|
|
// first output the flattened struct member variables *in order*
|
|
// after that, the other variables sorted by their datatype
|
|
|
|
val (structMembers, normalVars) = vars.partition { it.struct!=null }
|
|
structMembers.forEach { vardecl2asm(it) }
|
|
|
|
normalVars.sortedBy { it.datatype }.forEach {
|
|
if(it.scopedname !in allocatedZeropageVariables)
|
|
vardecl2asm(it)
|
|
}
|
|
}
|
|
|
|
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
|
|
val array = (decl.value as ReferenceLiteralValue).array!!
|
|
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')
|
|
}
|
|
decl.datatype== DataType.ARRAY_UW -> array.map {
|
|
val number = (it as NumericLiteralValue).number.toInt()
|
|
// TODO word array with address-references
|
|
"$"+number.toString(16).padStart(4, '0')
|
|
}
|
|
else -> throw AssemblyError("invalid arraysize type")
|
|
}
|
|
}
|
|
|
|
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
|
|
val array = (decl.value as ReferenceLiteralValue).array!!
|
|
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()
|
|
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
|
|
if(number>=0)
|
|
"$$hexnum"
|
|
else
|
|
"-$$hexnum"
|
|
}
|
|
decl.datatype== DataType.ARRAY_UW -> 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")
|
|
}
|
|
}
|
|
|
|
private fun getFloatConst(number: Double): String {
|
|
// try to match the ROM float constants to save memory
|
|
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number)
|
|
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
|
when {
|
|
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_ZERO"
|
|
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
|
|
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
|
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
|
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
|
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
|
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
|
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
|
|
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
|
|
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
|
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
|
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
|
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
|
|
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
|
|
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
|
|
else -> {
|
|
// attempt to correct for a few rounding issues
|
|
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
|
3.1415926536 -> return "c64flt.FL_PIVAL"
|
|
1.4142135624 -> return "c64flt.FL_SQRTWO"
|
|
0.7071067812 -> return "c64flt.FL_SQRHLF"
|
|
0.6931471806 -> return "c64flt.FL_LOG2"
|
|
else -> {}
|
|
}
|
|
|
|
// no ROM float const for this value, create our own
|
|
val name = globalFloatConsts[number]
|
|
if(name!=null)
|
|
return name
|
|
val newName = "prog8_float_const_${globalFloatConsts.size}"
|
|
globalFloatConsts[number] = newName
|
|
return newName
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun signExtendAtoMsb(destination: String) =
|
|
"""
|
|
ora #$7f
|
|
bmi +
|
|
lda #0
|
|
+ sta $destination
|
|
"""
|
|
|
|
private fun asmIdentifierName(identifier: IdentifierReference): String {
|
|
val name = if(identifier.memberOfStruct(program.namespace)!=null) {
|
|
identifier.targetVarDecl(program.namespace)!!.name
|
|
} else {
|
|
identifier.nameInSource.joinToString(".")
|
|
}
|
|
return fixNameSymbols(name)
|
|
}
|
|
|
|
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
|
|
|
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
|
|
if(complement) {
|
|
when (condition) {
|
|
BranchCondition.CS -> "bcc"
|
|
BranchCondition.CC -> "bcs"
|
|
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
|
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
|
BranchCondition.VS -> "bvc"
|
|
BranchCondition.VC -> "bvs"
|
|
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
|
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
|
}
|
|
} else {
|
|
when (condition) {
|
|
BranchCondition.CS -> "bcs"
|
|
BranchCondition.CC -> "bcc"
|
|
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
|
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
|
BranchCondition.VS -> "bvs"
|
|
BranchCondition.VC -> "bvc"
|
|
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
|
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
|
}
|
|
}
|
|
|
|
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
|
if(argType isAssignableTo paramType)
|
|
return true
|
|
|
|
// we have a special rule for some types.
|
|
// strings are assignable to UWORD, for example, and vice versa
|
|
if(argType in StringDatatypes && paramType==DataType.UWORD)
|
|
return true
|
|
if(argType==DataType.UWORD && paramType in StringDatatypes)
|
|
return true
|
|
|
|
return false
|
|
}
|
|
|
|
private fun translateSubroutine(sub: Subroutine) {
|
|
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)
|
|
memdefs2asm(sub.statements)
|
|
out("; statements")
|
|
sub.statements.forEach{ translate(it) }
|
|
out("; variables")
|
|
vardecls2asm(sub.statements)
|
|
out(" .pend\n")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: Statement) {
|
|
outputSourceLine(stmt)
|
|
when(stmt) {
|
|
is VarDecl, is StructDecl, is NopStatement -> {}
|
|
is Directive -> translate(stmt)
|
|
is Return -> translate(stmt)
|
|
is Subroutine -> translateSubroutine(stmt)
|
|
is InlineAssembly -> translate(stmt)
|
|
is FunctionCallStatement -> {
|
|
val functionName = stmt.target.nameInSource.last()
|
|
val builtinFunc = BuiltinFunctions[functionName]
|
|
if(builtinFunc!=null) {
|
|
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
|
} else {
|
|
translateSubroutineCall(stmt)
|
|
// discard any results from the stack:
|
|
val returns = stmt.target.targetSubroutine(program.namespace)!!.returntypes
|
|
for(t in returns) {
|
|
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
|
|
else if (t == DataType.FLOAT) out(" inx | inx | inx")
|
|
}
|
|
}
|
|
}
|
|
is Assignment -> translate(stmt)
|
|
is Jump -> translate(stmt)
|
|
is PostIncrDecr -> translate(stmt)
|
|
is Label -> translate(stmt)
|
|
is BranchStatement -> translate(stmt)
|
|
is IfStatement -> translate(stmt)
|
|
is ForLoop -> translate(stmt)
|
|
is Continue -> TODO("continue")
|
|
is Break -> TODO("break")
|
|
is WhileLoop -> translate(stmt)
|
|
is RepeatLoop -> translate(stmt)
|
|
is WhenStatement -> translate(stmt)
|
|
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
|
is AnonymousScope -> translate(stmt)
|
|
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
|
}
|
|
}
|
|
|
|
private fun translateSubroutineCall(stmt: IFunctionCall) {
|
|
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
|
if(Register.X in sub.asmClobbers)
|
|
out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
|
|
|
val subName = asmIdentifierName(stmt.target)
|
|
if(stmt.arglist.isNotEmpty()) {
|
|
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
|
|
translateSubroutineArgument(arg.first, arg.second, sub)
|
|
}
|
|
}
|
|
out(" jsr $subName")
|
|
|
|
if(Register.X in sub.asmClobbers)
|
|
out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
|
}
|
|
|
|
private fun translateSubroutineArgument(arg: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
|
val sourceDt = value.inferType(program)!!
|
|
if(!argumentTypeCompatible(sourceDt, arg.value.type))
|
|
throw AssemblyError("argument type incompatible")
|
|
if(sub.asmParameterRegisters.isEmpty()) {
|
|
// pass arg via a variable
|
|
val paramVar = arg.value
|
|
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
|
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
|
target.linkParents(value.parent)
|
|
val literal = value as? NumericLiteralValue
|
|
when {
|
|
literal!=null -> {
|
|
// optimize when the argument is a constant literal
|
|
when(arg.value.type) {
|
|
in ByteDatatypes -> assignByteConstant(target, literal.number.toShort())
|
|
in WordDatatypes -> assignWordConstant(target, literal.number.toInt())
|
|
DataType.FLOAT -> assignFloatConstant(target, literal.number.toDouble())
|
|
in PassByReferenceDatatypes-> TODO( "str/array/struct sub arg")
|
|
else -> throw AssemblyError("weird arg datatype")
|
|
}
|
|
}
|
|
value is IdentifierReference -> {
|
|
// optimize when the argument is a variable
|
|
when (arg.value.type) {
|
|
in ByteDatatypes -> assignByteVariable(target, value)
|
|
in WordDatatypes -> assignWordVariable(target, value)
|
|
DataType.FLOAT -> assignFloatVariable(target, value)
|
|
in PassByReferenceDatatypes -> TODO("str/array/struct sub arg")
|
|
else -> throw AssemblyError("weird arg datatype")
|
|
}
|
|
}
|
|
else -> TODO("non-constant sub arg $arg")
|
|
}
|
|
} else {
|
|
// pass arg via a register parameter
|
|
val paramRegister = sub.asmParameterRegisters[arg.index]
|
|
val statusflag = paramRegister.statusflag
|
|
val register = paramRegister.registerOrPair
|
|
val stack = paramRegister.stack
|
|
when {
|
|
stack==true -> TODO("param on stack")
|
|
statusflag!=null -> {
|
|
if (statusflag == Statusflag.Pc) TODO("carry flag param")
|
|
else throw AssemblyError("can only use Carry as status flag parameter")
|
|
}
|
|
register!=null && register.name.length==1 -> {
|
|
val literal = value as? NumericLiteralValue
|
|
when {
|
|
literal!=null -> {
|
|
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
|
target.linkParents(value.parent)
|
|
assignByteConstant(target, literal.number.toShort())
|
|
}
|
|
value is IdentifierReference -> {
|
|
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
|
target.linkParents(value.parent)
|
|
assignByteVariable(target, value)
|
|
}
|
|
else -> {
|
|
translateExpression(value)
|
|
when(register) {
|
|
RegisterOrPair.A -> out(" inx | lda $ESTACK_LO_HEX,x")
|
|
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
RegisterOrPair.Y -> out(" inx | ldy $ESTACK_LO_HEX,x")
|
|
else -> throw AssemblyError("cannot assign to register pair")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
register!=null && register.name.length==2 -> {
|
|
// register pair as a 16-bit value (only possible for subroutine parameters)
|
|
val literal = value as? NumericLiteralValue
|
|
if(literal!=null) {
|
|
// optimize when the argument is a constant literal
|
|
val hex = literal.number.toHex()
|
|
if (register == RegisterOrPair.AX) out(" lda #<$hex | ldx #>$hex")
|
|
else if (register == RegisterOrPair.AY) out(" lda #<$hex | ldy #>$hex")
|
|
else if (register == RegisterOrPair.XY) out(" ldx #<$hex | ldy #>$hex")
|
|
} else if(value is AddressOf) {
|
|
// optimize when the argument is an address of something
|
|
val sourceName = asmIdentifierName(value.identifier)
|
|
if (register == RegisterOrPair.AX) out(" lda #<$sourceName | ldx #>$sourceName")
|
|
else if (register == RegisterOrPair.AY) out(" lda #<$sourceName | ldy #>$sourceName")
|
|
else if (register == RegisterOrPair.XY) out(" ldx #<$sourceName | ldy #>$sourceName")
|
|
} else {
|
|
TODO("register pair param non-const $register = ${value}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: IfStatement) {
|
|
translateExpression(stmt.condition)
|
|
translateTestStack(stmt.condition.inferType(program)!!)
|
|
val elseLabel = makeLabel("if_else")
|
|
val endLabel = makeLabel("if_end")
|
|
out(" beq $elseLabel")
|
|
translate(stmt.truepart)
|
|
out(" jmp $endLabel")
|
|
out(elseLabel)
|
|
translate(stmt.elsepart)
|
|
out(endLabel)
|
|
}
|
|
|
|
private fun translateTestStack(dataType: DataType) {
|
|
when(dataType) {
|
|
in ByteDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x")
|
|
in WordDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x | ora $ESTACK_HI_HEX,x")
|
|
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
|
|
else -> throw AssemblyError("non-numerical dt")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: WhileLoop) {
|
|
TODO("while $stmt")
|
|
}
|
|
|
|
private fun translate(stmt: RepeatLoop) {
|
|
TODO("repeat $stmt")
|
|
}
|
|
|
|
private fun translate(stmt: WhenStatement) {
|
|
TODO("when $stmt")
|
|
}
|
|
|
|
private fun translate(stmt: Label) {
|
|
out(stmt.name)
|
|
}
|
|
|
|
private fun translate(scope: AnonymousScope) {
|
|
// note: the variables defined in an anonymous scope are moved to their defining subroutine's scope
|
|
scope.statements.forEach{ translate(it) }
|
|
}
|
|
|
|
private fun translate(stmt: BranchStatement) {
|
|
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
|
|
throw AssemblyError("only else part contains code, shoud have been switched already")
|
|
|
|
val jump = stmt.truepart.statements.first() as? Jump
|
|
if(jump!=null) {
|
|
// branch with only a jump
|
|
val instruction = branchInstruction(stmt.condition, false)
|
|
out(" $instruction ${getJumpTarget(jump)}")
|
|
translate(stmt.elsepart)
|
|
} else {
|
|
if(stmt.elsepart.containsNoCodeNorVars()) {
|
|
val instruction = branchInstruction(stmt.condition, true)
|
|
val elseLabel = makeLabel("branch_else")
|
|
out(" $instruction $elseLabel")
|
|
translate(stmt.truepart)
|
|
out(elseLabel)
|
|
} else {
|
|
val instruction = branchInstruction(stmt.condition, false)
|
|
val trueLabel = makeLabel("branch_true")
|
|
val endLabel = makeLabel("branch_end")
|
|
out(" $instruction $trueLabel")
|
|
translate(stmt.elsepart)
|
|
out(" jmp $endLabel")
|
|
out(trueLabel)
|
|
translate(stmt.truepart)
|
|
out(endLabel)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: Directive) {
|
|
when(stmt.directive) {
|
|
"%asminclude" -> {
|
|
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
|
val scopeprefix = stmt.args[1].str ?: ""
|
|
if(!scopeprefix.isBlank())
|
|
out("$scopeprefix\t.proc")
|
|
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
|
if(!scopeprefix.isBlank())
|
|
out(" .pend\n")
|
|
}
|
|
"%asmbinary" -> {
|
|
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
|
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
|
out(" .binary \"${stmt.args[0].str}\" $offset $length")
|
|
}
|
|
"%breakpoint" -> {
|
|
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
|
breakpointLabels.add(label)
|
|
out("$label\tnop")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: ForLoop) {
|
|
TODO("forloop $stmt")
|
|
}
|
|
|
|
private fun translate(stmt: PostIncrDecr) {
|
|
val incr = stmt.operator=="++"
|
|
when {
|
|
stmt.target.register!=null -> {
|
|
when(stmt.target.register!!) {
|
|
Register.A -> {
|
|
if(incr)
|
|
out(" clc | adc #1 ")
|
|
else
|
|
out(" sec | sbc #1 ")
|
|
}
|
|
Register.X -> {
|
|
if(incr) out(" inx") else out(" dex")
|
|
}
|
|
Register.Y -> {
|
|
if(incr) out(" iny") else out(" dey")
|
|
}
|
|
}
|
|
}
|
|
stmt.target.identifier!=null -> {
|
|
val what = asmIdentifierName(stmt.target.identifier!!)
|
|
val dt = stmt.target.inferType(program, stmt)
|
|
when (dt) {
|
|
in ByteDatatypes -> out(if (incr) " inc $what" else " dec $what")
|
|
in WordDatatypes -> {
|
|
if(incr)
|
|
out(" inc $what | bne + | inc $what+1 |+")
|
|
else
|
|
out(" lda $what | bne + | dec $what+1 |+ | dec $what")
|
|
}
|
|
DataType.FLOAT -> {
|
|
out(" lda #<$what | ldy #>$what")
|
|
out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
|
}
|
|
else -> throw AssemblyError("need numeric type")
|
|
}
|
|
}
|
|
stmt.target.memoryAddress!=null -> {
|
|
val target = stmt.target.memoryAddress!!.addressExpression
|
|
when (target) {
|
|
is NumericLiteralValue -> {
|
|
val what = target.number.toHex()
|
|
out(if(incr) " inc $what" else " dec $what")
|
|
}
|
|
is IdentifierReference -> {
|
|
val what = asmIdentifierName(target)
|
|
out(if(incr) " inc $what" else " dec $what")
|
|
}
|
|
else -> throw AssemblyError("weird target type $target")
|
|
}
|
|
}
|
|
stmt.target.arrayindexed!=null -> {
|
|
TODO("postincrdecr array element ${stmt.target.arrayindexed}")
|
|
}
|
|
else -> throw AssemblyError("weird target type ${stmt.target}")
|
|
}
|
|
}
|
|
|
|
private fun translate(jmp: Jump) {
|
|
out(" jmp ${getJumpTarget(jmp)}")
|
|
}
|
|
|
|
private fun getJumpTarget(jmp: Jump): String {
|
|
return when {
|
|
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
|
|
jmp.generatedLabel!=null -> jmp.generatedLabel
|
|
jmp.address!=null -> jmp.address.toHex()
|
|
else -> "????"
|
|
}
|
|
}
|
|
|
|
private fun translate(ret: Return) {
|
|
ret.value?.let { translateExpression(it) }
|
|
out(" rts")
|
|
}
|
|
|
|
private fun translate(asm: InlineAssembly) {
|
|
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
|
assemblyLines.add(assembly)
|
|
}
|
|
|
|
private fun translate(assign: Assignment) {
|
|
if(assign.aug_op!=null)
|
|
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
|
|
|
|
when(assign.value) {
|
|
is NumericLiteralValue -> {
|
|
val numVal = assign.value as NumericLiteralValue
|
|
when(numVal.type) {
|
|
DataType.UBYTE, DataType.BYTE -> assignByteConstant(assign.target, numVal.number.toShort())
|
|
DataType.UWORD, DataType.WORD -> assignWordConstant(assign.target, numVal.number.toInt())
|
|
DataType.FLOAT -> assignFloatConstant(assign.target, numVal.number.toDouble())
|
|
else -> throw AssemblyError("weird numval type")
|
|
}
|
|
}
|
|
is RegisterExpr -> {
|
|
assignRegister(assign.target, (assign.value as RegisterExpr).register)
|
|
}
|
|
is IdentifierReference -> {
|
|
val type = assign.target.inferType(program, assign)!!
|
|
when(type) {
|
|
DataType.UBYTE, DataType.BYTE -> assignByteVariable(assign.target, assign.value as IdentifierReference)
|
|
DataType.UWORD, DataType.WORD -> assignWordVariable(assign.target, assign.value as IdentifierReference)
|
|
DataType.FLOAT -> assignFloatVariable(assign.target, assign.value as IdentifierReference)
|
|
in StringDatatypes -> TODO("str assignment")
|
|
else -> throw AssemblyError("unsupported assignment target type $type")
|
|
}
|
|
}
|
|
is AddressOf -> {
|
|
val identifier = (assign.value as AddressOf).identifier
|
|
val scopedname = (assign.value as AddressOf).scopedname!!
|
|
assignAddressOf(assign.target, identifier, scopedname)
|
|
}
|
|
is DirectMemoryRead -> {
|
|
val read = (assign.value as DirectMemoryRead)
|
|
when(read.addressExpression) {
|
|
is NumericLiteralValue -> {
|
|
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
|
|
assignMemoryByte(assign.target, address, null)
|
|
}
|
|
is IdentifierReference -> {
|
|
assignMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
|
|
}
|
|
else -> {
|
|
translateExpression(read.addressExpression)
|
|
TODO("read memory byte from result and put that in ${assign.target}")
|
|
}
|
|
}
|
|
}
|
|
is PrefixExpression -> {
|
|
translateExpression(assign.value as PrefixExpression)
|
|
assignEvalResult(assign.target)
|
|
}
|
|
is BinaryExpression -> {
|
|
translateExpression(assign.value as BinaryExpression)
|
|
assignEvalResult(assign.target)
|
|
}
|
|
is ArrayIndexedExpression -> {
|
|
translateExpression(assign.value as ArrayIndexedExpression)
|
|
assignEvalResult(assign.target)
|
|
}
|
|
is TypecastExpression -> {
|
|
val cast = assign.value as TypecastExpression
|
|
val sourceType = cast.expression.inferType(program)
|
|
val targetType = assign.target.inferType(program, assign)
|
|
if((sourceType in ByteDatatypes && targetType in ByteDatatypes) ||
|
|
(sourceType in WordDatatypes && targetType in WordDatatypes)) {
|
|
// no need for a type cast
|
|
assign.value = cast.expression
|
|
translate(assign)
|
|
} else {
|
|
translateExpression(assign.value as TypecastExpression)
|
|
assignEvalResult(assign.target)
|
|
}
|
|
}
|
|
is FunctionCall -> {
|
|
translateExpression(assign.value as FunctionCall)
|
|
assignEvalResult(assign.target)
|
|
}
|
|
is ReferenceLiteralValue -> TODO("string/array/struct assignment?")
|
|
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: ArrayIndexedExpression) {
|
|
TODO("evaluate arrayindexed $expr")
|
|
}
|
|
|
|
private fun translateExpression(expr: TypecastExpression) {
|
|
translateExpression(expr.expression)
|
|
when(expr.expression.inferType(program)!!) {
|
|
DataType.UBYTE -> {
|
|
when(expr.type) {
|
|
DataType.UBYTE, DataType.BYTE -> {}
|
|
DataType.UWORD, DataType.WORD -> out(" lda #0 | sta $ESTACK_HI_PLUS1_HEX,x")
|
|
DataType.FLOAT -> out(" jsr c64flt.stack_ub2float")
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
DataType.BYTE -> {
|
|
when(expr.type) {
|
|
DataType.UBYTE, DataType.BYTE -> {}
|
|
DataType.UWORD, DataType.WORD -> out(" lda $ESTACK_HI_PLUS1_HEX,x | ${signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}")
|
|
DataType.FLOAT -> out(" jsr c64flt.stack_b2float")
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
DataType.UWORD -> {
|
|
when(expr.type) {
|
|
DataType.BYTE, DataType.UBYTE -> {}
|
|
DataType.WORD, DataType.UWORD -> {}
|
|
DataType.FLOAT -> out(" jsr c64flt.stack_uw2float")
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
DataType.WORD -> {
|
|
when(expr.type) {
|
|
DataType.BYTE, DataType.UBYTE -> {}
|
|
DataType.WORD, DataType.UWORD -> {}
|
|
DataType.FLOAT -> out(" jsr c64flt.stack_w2float")
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
DataType.FLOAT -> {
|
|
when(expr.type) {
|
|
DataType.UBYTE -> out(" jsr c64flt.stack_float2uw")
|
|
DataType.BYTE -> out(" jsr c64flt.stack_float2w")
|
|
DataType.UWORD -> out(" jsr c64flt.stack_float2uw")
|
|
DataType.WORD -> out(" jsr c64flt.stack_float2w")
|
|
DataType.FLOAT -> {}
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
|
|
internal fun translateExpression(expression: Expression) {
|
|
when(expression) {
|
|
is PrefixExpression -> translateExpression(expression)
|
|
is BinaryExpression -> translateExpression(expression)
|
|
is ArrayIndexedExpression -> translateExpression(expression)
|
|
is TypecastExpression -> translateExpression(expression)
|
|
is AddressOf -> translateExpression(expression)
|
|
is DirectMemoryRead -> translateExpression(expression)
|
|
is NumericLiteralValue -> translateExpression(expression)
|
|
is RegisterExpr -> translateExpression(expression)
|
|
is IdentifierReference -> translateExpression(expression)
|
|
is FunctionCall -> {
|
|
val functionName = expression.target.nameInSource.last()
|
|
val builtinFunc = BuiltinFunctions[functionName]
|
|
if(builtinFunc!=null) {
|
|
builtinFunctionsAsmGen.translateFunctioncallExpression(expression, builtinFunc)
|
|
} else {
|
|
translateSubroutineCall(expression)
|
|
}
|
|
}
|
|
is ReferenceLiteralValue -> TODO("string/array/struct assignment?")
|
|
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: AddressOf) {
|
|
TODO("take address of; $expr")
|
|
}
|
|
|
|
private fun translateExpression(expr: DirectMemoryRead) {
|
|
TODO("memread $expr")
|
|
}
|
|
|
|
private fun translateExpression(expr: NumericLiteralValue) {
|
|
when(expr.type) {
|
|
DataType.UBYTE, DataType.BYTE -> out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
|
DataType.UWORD, DataType.WORD -> out("""
|
|
lda #<${expr.number.toHex()}
|
|
sta $ESTACK_LO_HEX,x
|
|
lda #>${expr.number.toHex()}
|
|
sta $ESTACK_HI_HEX,x
|
|
dex
|
|
""")
|
|
DataType.FLOAT -> {
|
|
val floatConst = getFloatConst(expr.number.toDouble())
|
|
out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
|
}
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: RegisterExpr) {
|
|
when(expr.register) {
|
|
Register.A -> out(" sta $ESTACK_LO_HEX,x | dex")
|
|
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
|
|
Register.Y -> out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: IdentifierReference) {
|
|
val varname = asmIdentifierName(expr)
|
|
when(expr.inferType(program)!!) {
|
|
DataType.UBYTE, DataType.BYTE -> {
|
|
out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
|
|
}
|
|
DataType.UWORD, DataType.WORD -> {
|
|
out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex")
|
|
}
|
|
DataType.FLOAT -> {
|
|
out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
|
|
}
|
|
else -> throw AssemblyError("stack push weird variable type $expr")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: BinaryExpression) {
|
|
translateExpression(expr.left)
|
|
translateExpression(expr.right)
|
|
val leftDt = expr.left.inferType(program)!!
|
|
val rightDt = expr.right.inferType(program)!!
|
|
if(leftDt!=rightDt)
|
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
|
when (leftDt) {
|
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
|
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
|
else -> throw AssemblyError("non-numerical datatype")
|
|
}
|
|
}
|
|
|
|
private fun translateExpression(expr: PrefixExpression) {
|
|
translateExpression(expr.expression)
|
|
val type = expr.inferType(program)
|
|
when(expr.operator) {
|
|
"+" -> {}
|
|
"-" -> {
|
|
when(type) {
|
|
in ByteDatatypes -> out(" jsr prog8_lib.neg_b")
|
|
in WordDatatypes -> out(" jsr prog8_lib.neg_w")
|
|
DataType.FLOAT -> out(" jsr c64flt.neg_f")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
"~" -> {
|
|
when(type) {
|
|
in ByteDatatypes ->
|
|
out("""
|
|
lda $ESTACK_LO_PLUS1_HEX,x
|
|
eor #255
|
|
sta $ESTACK_LO_PLUS1_HEX,x
|
|
""")
|
|
in WordDatatypes -> out(" jsr prog8_lib.inv_word")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
"not" -> {
|
|
when(type) {
|
|
in ByteDatatypes -> out(" jsr prog8_lib.not_byte")
|
|
in WordDatatypes -> out(" jsr prog8_lib.not_word")
|
|
else -> throw AssemblyError("weird type")
|
|
}
|
|
}
|
|
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
|
}
|
|
}
|
|
|
|
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
|
when(operator) {
|
|
"**" -> throw AssemblyError("** operator requires floats")
|
|
"*" -> out(" jsr prog8_lib.mul_byte")
|
|
"/" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
|
"%" -> {
|
|
if(types==DataType.BYTE)
|
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
|
out(" jsr prog8_lib.remainder_ub")
|
|
}
|
|
"+" -> {TODO("plus bytes")}
|
|
"-" -> {TODO("minus bytes")}
|
|
"<<" -> throw AssemblyError("<< should not operate via stack")
|
|
">>" -> throw AssemblyError(">> should not operate via stack")
|
|
"<" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
|
">" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
|
"<=" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
|
">=" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
|
"==" -> out(" jsr prog8_lib.equal_b")
|
|
"!=" -> out(" jsr prog8_lib.notequal_b")
|
|
"&" -> out(" jsr prog8_lib.bitand_b")
|
|
"^" -> out(" jsr prog8_lib.bitxor_b")
|
|
"|" -> out(" jsr prog8_lib.bitor_b")
|
|
"and" -> out(" jsr prog8_lib.and_b")
|
|
"or" -> out(" jsr prog8_lib.or_b")
|
|
"xor" -> out(" jsr prog8_lib.xor_b")
|
|
else -> throw AssemblyError("invalid operator $operator")
|
|
}
|
|
}
|
|
|
|
private fun translateBinaryOperatorWords(operator: String, types: DataType) {
|
|
when(operator) {
|
|
"**" -> throw AssemblyError("** operator requires floats")
|
|
"*" -> out(" jsr prog8_lib.mul_word")
|
|
"/" -> out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
|
"%" -> {
|
|
if(types==DataType.WORD)
|
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
|
out(" jsr prog8_lib.remainder_uw")
|
|
}
|
|
"+" -> {TODO("plus word")}
|
|
"-" -> {TODO("minus word")}
|
|
"<<" -> throw AssemblyError("<< should not operate via stack")
|
|
">>" -> throw AssemblyError(">> should not operate via stack")
|
|
"<" -> out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
|
">" -> out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
|
"<=" -> out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
|
">=" -> out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
|
"==" -> out(" jsr prog8_lib.equal_w")
|
|
"!=" -> out(" jsr prog8_lib.notequal_w")
|
|
"&" -> out(" jsr prog8_lib.bitand_w")
|
|
"^" -> out(" jsr prog8_lib.bitxor_w")
|
|
"|" -> out(" jsr prog8_lib.bitor_w")
|
|
"and" -> out(" jsr prog8_lib.and_w")
|
|
"or" -> out(" jsr prog8_lib.or_w")
|
|
"xor" -> out(" jsr prog8_lib.xor_w")
|
|
else -> throw AssemblyError("invalid operator $operator")
|
|
}
|
|
}
|
|
|
|
private fun translateBinaryOperatorFloats(operator: String) {
|
|
when(operator) {
|
|
"**" -> out(" jsr c64flt.pow_f")
|
|
"*" -> out(" jsr c64flt.mul_f")
|
|
"/" -> out(" jsr c64flt.div_f")
|
|
"+" -> out(" jsr c64flt.add_f")
|
|
"-" -> out(" jsr c64flt.sub_f")
|
|
"<" -> out(" jsr c64flt.less_f")
|
|
">" -> out(" jsr c64flt.greater_f")
|
|
"<=" -> out(" jsr c64flt.lesseq_f")
|
|
">=" -> out(" jsr c64flt.greatereq_f")
|
|
"==" -> out(" jsr c64flt.equal_f")
|
|
"!=" -> out(" jsr c64flt.notequal_f")
|
|
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
|
else -> throw AssemblyError("invalid operator $operator")
|
|
}
|
|
}
|
|
|
|
private fun assignEvalResult(target: AssignTarget) {
|
|
when {
|
|
target.register!=null -> {
|
|
if(target.register==Register.X)
|
|
throw AssemblyError("can't pop into X register - use variable instead")
|
|
out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ")
|
|
}
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
val targetDt = target.identifier.inferType(program)!!
|
|
when(targetDt) {
|
|
DataType.UBYTE, DataType.BYTE -> {
|
|
out(" inx | lda $ESTACK_LO_HEX,x | sta $targetName")
|
|
}
|
|
DataType.UWORD, DataType.WORD -> {
|
|
out("""
|
|
inx
|
|
lda $ESTACK_LO_HEX,x
|
|
sta $targetName
|
|
lda $ESTACK_HI_HEX,x
|
|
sta $targetName+1
|
|
""")
|
|
}
|
|
DataType.FLOAT -> {
|
|
out("""
|
|
lda #<$targetName
|
|
ldy #>$targetName
|
|
jsr c64flt.pop_float
|
|
""")
|
|
}
|
|
else -> throw AssemblyError("weird target variable type $targetDt")
|
|
}
|
|
}
|
|
target.memoryAddress!=null -> {
|
|
val address = target.memoryAddress!!.addressExpression
|
|
if(address is NumericLiteralValue) {
|
|
out(" inx | lda $ESTACK_LO_HEX,x | sta ${address.number.toHex()}")
|
|
} else {
|
|
TODO("put result in memory $target")
|
|
}
|
|
}
|
|
target.arrayindexed!=null -> {
|
|
TODO("put result in arrayindexed $target")
|
|
}
|
|
else -> throw AssemblyError("weird assignment target $target")
|
|
}
|
|
}
|
|
|
|
private fun assignAddressOf(target: AssignTarget, name: IdentifierReference, scopedname: String) {
|
|
when {
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
val struct = name.memberOfStruct(program.namespace)
|
|
if(struct!=null) {
|
|
// take the address of the first struct member instead
|
|
val decl = name.targetVarDecl(program.namespace)!!
|
|
val firstStructMember = struct.nameOfFirstMember()
|
|
// find the flattened var that belongs to this first struct member
|
|
val firstVarName = listOf(decl.name, firstStructMember)
|
|
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
|
|
val sourceName = firstVar.name
|
|
out("""
|
|
lda #<$sourceName
|
|
ldy #>$sourceName
|
|
sta $targetName
|
|
sty $targetName+1
|
|
""")
|
|
} else {
|
|
val sourceName = fixNameSymbols(scopedname)
|
|
out("""
|
|
lda #<$sourceName
|
|
ldy #>$sourceName
|
|
sta $targetName
|
|
sty $targetName+1
|
|
""")
|
|
}
|
|
}
|
|
else -> TODO("assign address to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignWordVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
val sourceName = asmIdentifierName(variable)
|
|
when {
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda $sourceName
|
|
ldy $sourceName+1
|
|
sta $targetName
|
|
sty $targetName+1
|
|
""")
|
|
}
|
|
else -> TODO("assign word to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignFloatVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
val sourceName = asmIdentifierName(variable)
|
|
when {
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda $sourceName
|
|
sta $targetName
|
|
lda $sourceName+1
|
|
sta $targetName+1
|
|
lda $sourceName+2
|
|
sta $targetName+2
|
|
lda $sourceName+3
|
|
sta $targetName+3
|
|
lda $sourceName+4
|
|
sta $targetName+4
|
|
""")
|
|
}
|
|
else -> TODO("assign float to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignByteVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
val sourceName = asmIdentifierName(variable)
|
|
when {
|
|
target.register!=null -> {
|
|
out(" ld${target.register.name.toLowerCase()} $sourceName")
|
|
}
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda $sourceName
|
|
sta $targetName
|
|
""")
|
|
}
|
|
else -> TODO("assign byte to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignRegister(target: AssignTarget, register: Register) {
|
|
when {
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out(" st${register.name.toLowerCase()} $targetName")
|
|
}
|
|
target.register!=null -> {
|
|
when(register) {
|
|
Register.A -> when(target.register) {
|
|
Register.A -> {}
|
|
Register.X -> out(" tax")
|
|
Register.Y -> out(" tay")
|
|
}
|
|
Register.X -> when(target.register) {
|
|
Register.A -> out(" txa")
|
|
Register.X -> {}
|
|
Register.Y -> out(" txy")
|
|
}
|
|
Register.Y -> when(target.register) {
|
|
Register.A -> out(" tya")
|
|
Register.X -> out(" tyx")
|
|
Register.Y -> {}
|
|
}
|
|
}
|
|
}
|
|
else -> out("; TODO assign register $register to $target") // TODO
|
|
}
|
|
}
|
|
|
|
private fun saveRegister(register: Register) {
|
|
when(register) {
|
|
Register.A -> out(" pha")
|
|
Register.X -> out(" txa | pha")
|
|
Register.Y -> out(" tya | pha")
|
|
}
|
|
}
|
|
|
|
private fun restoreRegister(register: Register) {
|
|
when(register) {
|
|
Register.A -> out(" pla")
|
|
Register.X -> out(" pla | tax")
|
|
Register.Y -> out(" pla | tay")
|
|
}
|
|
}
|
|
|
|
private fun assignWordConstant(target: AssignTarget, word: Int) {
|
|
if(target.identifier!=null) {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
if(word ushr 8 == word and 255) {
|
|
// lsb=msb
|
|
out("""
|
|
lda #${(word and 255).toHex()}
|
|
sta $targetName
|
|
sta $targetName+1
|
|
""")
|
|
} else {
|
|
out("""
|
|
lda #<${word.toHex()}
|
|
ldy #>${word.toHex()}
|
|
sta $targetName
|
|
sty $targetName+1
|
|
""")
|
|
}
|
|
} else {
|
|
TODO("assign word $word to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignByteConstant(target: AssignTarget, byte: Short) {
|
|
when {
|
|
target.register!=null -> {
|
|
out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
|
|
}
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out(" lda #${byte.toHex()} | sta $targetName ")
|
|
}
|
|
else -> TODO("assign byte $byte to $target")
|
|
}
|
|
}
|
|
|
|
private fun assignFloatConstant(target: AssignTarget, float: Double) {
|
|
if(float==0.0) {
|
|
// optimized case for float zero
|
|
if (target.identifier != null) {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda #0
|
|
sta $targetName
|
|
sta $targetName+1
|
|
sta $targetName+2
|
|
sta $targetName+3
|
|
sta $targetName+4
|
|
""")
|
|
} else {
|
|
TODO("assign float 0.0 to $target")
|
|
}
|
|
} else {
|
|
// non-zero value
|
|
val constFloat = getFloatConst(float)
|
|
if (target.identifier != null) {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda $constFloat
|
|
sta $targetName
|
|
lda $constFloat+1
|
|
sta $targetName+1
|
|
lda $constFloat+2
|
|
sta $targetName+2
|
|
lda $constFloat+3
|
|
sta $targetName+3
|
|
lda $constFloat+4
|
|
sta $targetName+4
|
|
""")
|
|
} else {
|
|
TODO("assign float $float ($constFloat) to $target")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun assignMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
|
|
if(address!=null) {
|
|
when {
|
|
target.register!=null -> {
|
|
out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
|
|
}
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
lda ${address.toHex()}
|
|
sta $targetName
|
|
""")
|
|
}
|
|
else -> TODO("assign memory byte $target")
|
|
}
|
|
}
|
|
else if(identifier!=null) {
|
|
val sourceName = asmIdentifierName(identifier)
|
|
when {
|
|
target.register!=null -> {
|
|
out("""
|
|
ldy #0
|
|
lda ($sourceName),y
|
|
""")
|
|
when(target.register){
|
|
Register.A -> {}
|
|
Register.X -> out(" tax")
|
|
Register.Y -> out(" tay")
|
|
}
|
|
}
|
|
target.identifier!=null -> {
|
|
val targetName = asmIdentifierName(target.identifier)
|
|
out("""
|
|
ldy #0
|
|
lda ($sourceName),y
|
|
sta $targetName
|
|
""")
|
|
}
|
|
else -> TODO("assign memory byte $target")
|
|
}
|
|
}
|
|
}
|
|
}
|