prog8/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt
2023-01-22 17:10:52 +01:00

596 lines
23 KiB
Kotlin

package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.code.ast.PtForLoop
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtProgram
import prog8.code.ast.PtRange
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: PtProgram, private val asmgen: AsmGen, private val zeropage: Zeropage) {
internal fun translate(stmt: PtForLoop) {
val iterableDt = stmt.iterable.type
when(stmt.iterable) {
is PtRange -> {
val range = (stmt.iterable as PtRange).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
} else {
translateForOverConstRange(stmt, iterableDt, range)
}
}
is PtIdentifier -> {
translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
}
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.asConstInteger()!!
if(stepsize < -1) {
val limit = range.to.asConstInteger()
if(limit==0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
// bytes, step >= 2 or <= -2
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
if(stepsize>0) {
asmgen.out("""
lda $varname
clc
adc #$stepsize
sta $varname
$modifiedLabel cmp #0 ; modified
bmi $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
lda $varname
sec
sbc #${stepsize.absoluteValue}
sta $varname
$modifiedLabel cmp #0 ; modified
bpl $loopLabel""")
}
asmgen.out(endLabel)
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname+1
$modifiedLabel cmp #0 ; modified
bne +
lda $varname
$modifiedLabel2 cmp #0 ; modified
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
stepsize > 0 -> {
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
$modifiedLabel cmp #0 ; modified
bcc $loopLabel
bne $endLabel
$modifiedLabel2 lda #0 ; modified
cmp $varname
bcc $endLabel
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
$modifiedLabel2 lda #0 ; modified
cmp $varname
$modifiedLabel lda #0 ; modified
sbc $varname+1
bvc +
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
else -> {
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.statements)
if(iterableDt==DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
$modifiedLabel cmp #0 ; modified
bcc $endLabel
bne $loopLabel
lda $varname
$modifiedLabel2 cmp #0 ; modified
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
$modifiedLabel sbc #0 ; modified
bvc +
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
}
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program)!!
when(iterableDt) {
DataType.STR -> {
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $loopLabel+1
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
inc $loopLabel+1
bne $loopLabel
inc $loopLabel+2
bne $loopLabel
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
val length = decl.arraySize!!
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(length<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$length
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(length>=16u) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
val length = decl.arraySize!! * 2u
val indexVar = asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127u) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
bne $loopLabel
beq $endLabel""")
}
if(length>=16u) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
DataType.ARRAY_F -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
}
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
if (range.isEmpty() || range.step==0)
throw AssemblyError("empty range or step 0")
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
}
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
// loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
2 -> {
if(range.last==255 || range.last==254) {
asmgen.out("""
inc $varname
beq $endLabel
inc $varname
bne $loopLabel""")
} else {
asmgen.out("""
inc $varname
inc $varname
lda $varname
cmp #${range.last+2}
bne $loopLabel""")
}
}
-2 -> {
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname""")
asmgen.jmp(loopLabel)
}
1 -> asmgen.out("""
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
else -> asmgen.out("""
dec $varname
dec $varname
lda $varname
cmp #${range.last-2}
bne $loopLabel""")
}
}
else -> {
// step <= -3 or >= 3
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
clc
adc #${range.step}
sta $varname""")
asmgen.jmp(loopLabel)
}
}
asmgen.out(endLabel)
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
else -> {
// word, step >= 2 or <= -2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
clc
adc #<${range.step}
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
if (range.last == 255) {
asmgen.out("""
inc $varname
bne $loopLabel
$endLabel""")
} else {
asmgen.out("""
inc $varname
lda $varname
cmp #${range.last+1}
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.last) {
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
1 -> {
asmgen.out("""
dec $varname
bne $loopLabel
$endLabel""")
}
else -> {
asmgen.out("""
dec $varname
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
}
}
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.variable),
stmt.variable.type,
stmt.definingISub())
}