mirror of
https://github.com/irmen/prog8.git
synced 2026-04-21 17:16:33 +00:00
Merge branch 'master' into structs
# Conflicts: # codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt # codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt # examples/test.p8
This commit is contained in:
@@ -433,23 +433,6 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
|
||||
internal val tempVarsCounters = mutableMapOf(
|
||||
BaseDataType.BOOL to 0,
|
||||
BaseDataType.BYTE to 0,
|
||||
BaseDataType.UBYTE to 0,
|
||||
BaseDataType.WORD to 0,
|
||||
BaseDataType.UWORD to 0,
|
||||
BaseDataType.LONG to 0,
|
||||
BaseDataType.FLOAT to 0
|
||||
)
|
||||
|
||||
internal fun buildTempVarName(dt: BaseDataType, counter: Int): String = "prog8_tmpvar_${dt.toString().lowercase()}_$counter"
|
||||
|
||||
internal fun getTempVarName(dt: BaseDataType): String {
|
||||
tempVarsCounters[dt] = tempVarsCounters.getValue(dt)+1
|
||||
return buildTempVarName(dt, tempVarsCounters.getValue(dt))
|
||||
}
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
|
||||
// returns the source name of the zero page pointervar if it's already in the ZP,
|
||||
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
|
||||
@@ -855,7 +838,7 @@ class AsmGen6502Internal (
|
||||
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
|
||||
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
|
||||
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
|
||||
out("""
|
||||
ldy #>$loopcount
|
||||
@@ -875,7 +858,7 @@ $repeatLabel""")
|
||||
// note: A/Y must have been loaded with the number of iterations!
|
||||
// the iny + double dec is microoptimization of the 16 bit loop
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, false, stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UWORD, false, stmt)
|
||||
out("""
|
||||
cmp #0
|
||||
beq +
|
||||
@@ -898,13 +881,13 @@ $repeatLabel""")
|
||||
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65C02)) {
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
|
||||
out(" lda #${count and 255} | sta $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
out(" dec $counterVar | bne $repeatLabel")
|
||||
} else {
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
out(" lda #${count and 255} | sta $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
@@ -916,13 +899,13 @@ $repeatLabel""")
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
out(" cpy #0")
|
||||
if(isTargetCpu(CpuType.CPU65C02)) {
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
|
||||
out(" beq $endLabel | sty $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
out(" dec $counterVar | bne $repeatLabel")
|
||||
} else {
|
||||
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
|
||||
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
out(" beq $endLabel | sty $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
@@ -931,43 +914,9 @@ $repeatLabel""")
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun createRepeatCounterVar(dt: BaseDataType, preferZeropage: Boolean, stmt: PtRepeatLoop): String {
|
||||
val scope = stmt.definingISub()!!
|
||||
val asmInfo = subroutineExtra(scope)
|
||||
var parent = stmt.parent
|
||||
while(parent !is PtProgram) {
|
||||
if(parent is PtRepeatLoop)
|
||||
break
|
||||
parent = parent.parent
|
||||
}
|
||||
val isNested = parent is PtRepeatLoop
|
||||
|
||||
if(!isNested) {
|
||||
// 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 && it.second.endsWith("counter") }
|
||||
if(existingVar!=null) {
|
||||
if(!preferZeropage || existingVar.third!=null)
|
||||
return existingVar.second
|
||||
}
|
||||
}
|
||||
|
||||
val counterVar = makeLabel("counter")
|
||||
when(dt) {
|
||||
BaseDataType.UBYTE, BaseDataType.UWORD -> {
|
||||
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
|
||||
result.fold(
|
||||
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
|
||||
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
|
||||
)
|
||||
return counterVar
|
||||
}
|
||||
else -> throw AssemblyError("invalidt dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: PtWhen) {
|
||||
val endLabel = makeLabel("when_end")
|
||||
val choiceBlocks = mutableListOf<Pair<String, PtNodeGroup>>()
|
||||
val choiceBlocks = mutableListOf<Pair<String, PtWhenChoice>>()
|
||||
val conditionDt = stmt.value.type
|
||||
if(conditionDt.isByte)
|
||||
assignExpressionToRegister(stmt.value, RegisterOrPair.A)
|
||||
@@ -978,13 +927,20 @@ $repeatLabel""")
|
||||
val choice = choiceNode as PtWhenChoice
|
||||
var choiceLabel = makeLabel("choice")
|
||||
if(choice.isElse) {
|
||||
require(choice.parent.children.last() === choice)
|
||||
translate(choice.statements)
|
||||
// is always the last node so can fall through
|
||||
} else {
|
||||
if(choice.statements.children.isEmpty()) {
|
||||
// no statements for this choice value, jump to the end immediately
|
||||
choiceLabel = endLabel
|
||||
} else {
|
||||
choiceBlocks.add(choiceLabel to choice.statements)
|
||||
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
|
||||
if(onlyJumpLabel==null) {
|
||||
choiceBlocks.add(choiceLabel to choice)
|
||||
} else {
|
||||
choiceLabel = onlyJumpLabel
|
||||
}
|
||||
}
|
||||
for (cv in choice.values.children) {
|
||||
val value = (cv as PtNumber).number.toInt()
|
||||
@@ -1001,11 +957,14 @@ $repeatLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
jmp(endLabel)
|
||||
|
||||
if(choiceBlocks.isNotEmpty())
|
||||
jmp(endLabel)
|
||||
|
||||
for(choiceBlock in choiceBlocks.withIndex()) {
|
||||
out(choiceBlock.value.first)
|
||||
translate(choiceBlock.value.second)
|
||||
if (choiceBlock.index < choiceBlocks.size - 1)
|
||||
translate(choiceBlock.value.second.statements)
|
||||
if (choiceBlock.index < choiceBlocks.size - 1 && !choiceBlock.value.second.isOnlyGotoOrReturn())
|
||||
jmp(endLabel)
|
||||
}
|
||||
out(endLabel)
|
||||
@@ -1581,6 +1540,51 @@ $repeatLabel""")
|
||||
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
internal fun createTempVarReused(dt: BaseDataType, preferZeropage: Boolean, stmt: PtNode): String {
|
||||
val scope = stmt.definingISub()!!
|
||||
val asmInfo = subroutineExtra(scope)
|
||||
var parent = stmt.parent
|
||||
while(parent !is PtProgram) {
|
||||
if(parent is PtRepeatLoop || parent is PtForLoop)
|
||||
break
|
||||
parent = parent.parent
|
||||
}
|
||||
val isNested = parent is PtRepeatLoop || parent is PtForLoop
|
||||
|
||||
if(!isNested) {
|
||||
// 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 && it.second.endsWith("tempv") }
|
||||
if(existingVar!=null) {
|
||||
if(!preferZeropage || existingVar.third!=null) {
|
||||
// println("reuse temp counter var: $dt ${existingVar.second} @${stmt.position}")
|
||||
return existingVar.second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val counterVar = makeLabel("tempv")
|
||||
// println("new temp counter var: $dt $counterVar @${stmt.position}")
|
||||
when {
|
||||
dt.isIntegerOrBool -> {
|
||||
if(preferZeropage) {
|
||||
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
|
||||
result.fold(
|
||||
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
|
||||
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
|
||||
)
|
||||
} else {
|
||||
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally
|
||||
}
|
||||
return counterVar
|
||||
}
|
||||
dt == BaseDataType.FLOAT -> {
|
||||
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally, floats never on zeropage
|
||||
return counterVar
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignConstFloatToPointerAY(number: PtNumber) {
|
||||
val floatConst = allocator.getFloatAsmConst(number.number)
|
||||
out("""
|
||||
|
||||
@@ -751,7 +751,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.assignConstFloatToPointerAY(number)
|
||||
}
|
||||
else -> {
|
||||
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
|
||||
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
|
||||
@@ -89,7 +89,7 @@ internal class ForLoopsAsmGen(
|
||||
if(asmgen.options.romable) {
|
||||
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
|
||||
// so we need to store the loop end value in a newly allocated temporary variable
|
||||
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
|
||||
asmgen.out(" sta $toValueVar")
|
||||
// pre-check for end already reached
|
||||
@@ -296,7 +296,7 @@ $modifiedLabel cmp #0 ; modified
|
||||
if(asmgen.options.romable) {
|
||||
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
|
||||
// so we need to store the loop end value in a newly allocated temporary variable
|
||||
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out(" sta $toValueVar")
|
||||
@@ -508,7 +508,7 @@ $endLabel""")
|
||||
when {
|
||||
iterableDt.isString -> {
|
||||
if(asmgen.options.romable) {
|
||||
val indexVar = asmgen.getTempVarName(BaseDataType.UBYTE)
|
||||
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
sty $indexVar
|
||||
@@ -539,7 +539,10 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
iterableDt.isByteArray || iterableDt.isBoolArray -> {
|
||||
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
@@ -576,7 +579,10 @@ $loopLabel sty $indexVar
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isSplitWordArray -> {
|
||||
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
@@ -616,8 +622,10 @@ $loopLabel sty $indexVar
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
iterableDt.isWordArray -> {
|
||||
val length = numElements * 2u
|
||||
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
|
||||
val indexVar = if(asmgen.options.romable)
|
||||
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
|
||||
else
|
||||
asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
@@ -627,16 +635,16 @@ $loopLabel sty $indexVar
|
||||
lda $iterableName+1,y
|
||||
sta $loopvarName+1""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(length<=127u) {
|
||||
if(numElements<=127u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
iny
|
||||
cpy #$length
|
||||
cpy #${numElements*2u}
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
// length is 128 words, 256 bytes
|
||||
// array size is 128 words, 256 bytes
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
@@ -645,7 +653,7 @@ $loopLabel sty $indexVar
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(!asmgen.options.romable) {
|
||||
if(length>=16u) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible, otherwise inline
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
|
||||
@@ -45,7 +45,6 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
memorySlabs()
|
||||
tempVars()
|
||||
footer()
|
||||
}
|
||||
}
|
||||
@@ -219,29 +218,6 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun tempVars() {
|
||||
// these can be in the no clear section because they'll always get assigned a new value
|
||||
asmgen.out("; expression temp vars\n .section BSS_NOCLEAR")
|
||||
for((dt, count) in asmgen.tempVarsCounters) {
|
||||
if(count>0) {
|
||||
for(num in 1..count) {
|
||||
val name = asmgen.buildTempVarName(dt, num)
|
||||
when (dt) {
|
||||
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
|
||||
BaseDataType.BYTE -> asmgen.out("$name .char ?")
|
||||
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
|
||||
BaseDataType.WORD -> asmgen.out("$name .sint ?")
|
||||
BaseDataType.UWORD -> asmgen.out("$name .word ?")
|
||||
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
|
||||
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
|
||||
else -> throw AssemblyError("weird dt for extravar $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" .send BSS_NOCLEAR")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
var relocateBssVars = false
|
||||
var relocateBssSlabs = false
|
||||
@@ -501,8 +477,8 @@ internal class ProgramAndVarsGen(
|
||||
if(addr!=null)
|
||||
asmgen.out("$name = $addr")
|
||||
else when(dt) {
|
||||
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
|
||||
BaseDataType.UWORD -> asmgen.out("$name .word ?")
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
|
||||
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
|
||||
else -> throw AssemblyError("weird dt for extravar $dt")
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" ldx P8ZP_SCRATCH_B1")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
|
||||
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
|
||||
asmgen.out(" sta $tempVar")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
|
||||
@@ -356,7 +356,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
|
||||
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
|
||||
asmgen.out(" sta $tempVar")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
|
||||
@@ -439,7 +439,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
|
||||
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
|
||||
asmgen.out(" sta $tempVar | stx $tempVar+1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
|
||||
@@ -457,7 +457,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
target.datatype.isFloat -> {
|
||||
// copy array value into tempvar
|
||||
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
|
||||
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
|
||||
@@ -424,8 +424,9 @@ class IRCodeGen(
|
||||
whenStmt.choices.children.forEach {
|
||||
val choice = it as PtWhenChoice
|
||||
if(choice.isElse) {
|
||||
require(choice.parent.children.last() === choice)
|
||||
result += translateNode(choice.statements)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
// is always the last node so can fall through
|
||||
} else {
|
||||
if(choice.statements.children.isEmpty()) {
|
||||
// no statements for this choice value, jump to the end immediately
|
||||
@@ -437,22 +438,30 @@ class IRCodeGen(
|
||||
}
|
||||
} else {
|
||||
val choiceLabel = createLabelName()
|
||||
choices.add(choiceLabel to choice)
|
||||
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
|
||||
val branchLabel: String
|
||||
if(onlyJumpLabel==null) {
|
||||
choices.add(choiceLabel to choice)
|
||||
branchLabel = choiceLabel
|
||||
} else {
|
||||
branchLabel = onlyJumpLabel
|
||||
}
|
||||
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
|
||||
result += IRCodeChunk(null, null).also { chunk ->
|
||||
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
|
||||
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
|
||||
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
|
||||
if(choices.isNotEmpty())
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
|
||||
choices.forEach { (label, choice) ->
|
||||
result += labelFirstChunk(translateNode(choice.statements), label)
|
||||
val lastStatement = choice.statements.children.last()
|
||||
if(lastStatement !is PtReturn && lastStatement !is PtJump)
|
||||
if(!choice.isOnlyGotoOrReturn())
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
}
|
||||
|
||||
@@ -473,10 +482,11 @@ class IRCodeGen(
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
require(forLoop.variable.name == loopvar.scopedNameString)
|
||||
val elementDt = irType(iterable.type.elementType())
|
||||
val iterableLength = symbolTable.getLength(iterable.name)
|
||||
val loopvarSymbol = forLoop.variable.name
|
||||
val indexReg = registers.next(IRDataType.BYTE)
|
||||
val tmpReg = registers.next(IRDataType.BYTE)
|
||||
val tmpReg = registers.next(elementDt)
|
||||
val loopLabel = createLabelName()
|
||||
val endLabel = createLabelName()
|
||||
when {
|
||||
@@ -484,9 +494,9 @@ class IRCodeGen(
|
||||
// iterate over a zero-terminated string
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
|
||||
result += IRCodeChunk(loopLabel, null).also {
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
|
||||
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
val jumpChunk = IRCodeChunk(null, null)
|
||||
@@ -496,9 +506,8 @@ class IRCodeGen(
|
||||
result += IRCodeChunk(endLabel, null)
|
||||
}
|
||||
iterable.type.isSplitWordArray || iterable.type.isPointerArray -> {
|
||||
// iterate over lsb/msb split word/pointer array
|
||||
val elementDt = iterable.type.elementType()
|
||||
if(!elementDt.isWord && !elementDt.isPointer)
|
||||
// iterate over lsb/msb split word array
|
||||
if(elementDt!=IRDataType.WORD)
|
||||
throw AssemblyError("weird dt")
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
|
||||
result += IRCodeChunk(loopLabel, null).also {
|
||||
@@ -508,7 +517,7 @@ class IRCodeGen(
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
|
||||
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
|
||||
@@ -522,6 +522,27 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(whenStmt.betterAsOnGoto(program, options)) {
|
||||
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
|
||||
var elseJump: Jump? = null
|
||||
val jumps = mutableListOf<Pair<Int, Jump>>()
|
||||
whenStmt.choices.forEach { choice ->
|
||||
if(choice.values==null) {
|
||||
elseJump = choice.statements.statements.single() as Jump
|
||||
} else {
|
||||
choice.values!!.forEach { value ->
|
||||
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
|
||||
|
||||
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
|
||||
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
|
||||
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -1216,6 +1216,7 @@ _vsync_vec .word ?
|
||||
_line_vec .word ?
|
||||
_aflow_vec .word ?
|
||||
_sprcol_vec .word ?
|
||||
_continue_with_system_handler .byte ?
|
||||
.send BSS
|
||||
|
||||
_irq_dispatcher
|
||||
@@ -1224,44 +1225,42 @@ _irq_dispatcher
|
||||
cld
|
||||
lda cx16.VERA_ISR
|
||||
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
|
||||
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
|
||||
|
||||
bit #2
|
||||
stz _continue_with_system_handler
|
||||
|
||||
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
|
||||
beq +
|
||||
pha
|
||||
jsr _line_handler
|
||||
ldy #2
|
||||
sty cx16.VERA_ISR
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #4
|
||||
beq +
|
||||
jsr _sprcol_handler
|
||||
ldy #4
|
||||
sty cx16.VERA_ISR
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #8
|
||||
beq +
|
||||
jsr _aflow_handler
|
||||
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #1
|
||||
beq +
|
||||
tsb _continue_with_system_handler
|
||||
pla
|
||||
|
||||
+ lsr a
|
||||
bcc +
|
||||
pha
|
||||
jsr _vsync_handler
|
||||
cmp #0
|
||||
bne _dispatch_end
|
||||
ldy #1
|
||||
sty cx16.VERA_ISR
|
||||
bra _return_irq
|
||||
+
|
||||
lda #0
|
||||
_dispatch_end
|
||||
cmp #0
|
||||
beq _return_irq
|
||||
jsr sys.restore_prog8_internals
|
||||
tsb _continue_with_system_handler
|
||||
pla
|
||||
|
||||
+ lsr a
|
||||
lsr a
|
||||
bcc +
|
||||
pha
|
||||
jsr _sprcol_handler
|
||||
tsb _continue_with_system_handler
|
||||
pla
|
||||
|
||||
+ lsr a
|
||||
bcc +
|
||||
jsr _aflow_handler
|
||||
tsb _continue_with_system_handler
|
||||
|
||||
+ jsr sys.restore_prog8_internals
|
||||
lda _continue_with_system_handler
|
||||
beq _no_sys_handler
|
||||
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
_return_irq
|
||||
jsr sys.restore_prog8_internals
|
||||
_no_sys_handler
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
|
||||
@@ -65,7 +65,10 @@ _done
|
||||
*/
|
||||
|
||||
sub gnomesort_uw(uword values, ubyte num_elements) {
|
||||
; TODO optimize this more, rewrite in asm?
|
||||
; When written in asm this is 10-20% faster, but unreadable. Not worth it.
|
||||
; Also, sorting just an array of word numbers is very seldomly used, most often you
|
||||
; need to sort other things associated with it as well and that is not done here anyway,
|
||||
; so requires a custom user coded sorting routine anyway.
|
||||
ubyte @zp pos = 1
|
||||
uword @requirezp ptr = values+2
|
||||
while pos != num_elements {
|
||||
|
||||
@@ -1908,6 +1908,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(whenStmt.condition.constValue(program)!=null)
|
||||
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
|
||||
|
||||
if(whenStmt.betterAsOnGoto(program, compilerOptions))
|
||||
errors.info("when statement can be replaced with on..goto", whenStmt.position)
|
||||
|
||||
super.visit(whenStmt)
|
||||
}
|
||||
|
||||
|
||||
@@ -1266,6 +1266,30 @@ class When(var condition: Expression,
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean =
|
||||
condition.referencesIdentifier(nameInSource) || choices.any { it.referencesIdentifier(nameInSource) }
|
||||
|
||||
fun betterAsOnGoto(program: Program, compilerOptions: CompilationOptions): Boolean {
|
||||
// a when that has only goto's and the values 0,1,2,3,4... is better written as a on..goto
|
||||
val sizeLimit = if(compilerOptions.compTarget.cpu == CpuType.CPU65C02) 4 else 6
|
||||
if(choices.size >= sizeLimit) {
|
||||
if (condition.inferType(program).isBytes) {
|
||||
if (choices.all { (it.statements.statements.singleOrNull() as? Jump)?.target is IdentifierReference }) {
|
||||
val values = choices.flatMap {
|
||||
it.values ?: mutableListOf()
|
||||
}.map {
|
||||
it.constValue(program)?.number?.toInt()
|
||||
}
|
||||
if(null !in values) {
|
||||
val sortedValues = values.filterNotNull().sorted()
|
||||
val range = IntRange(sortedValues.first(), sortedValues.last())
|
||||
if(range.toList() == sortedValues) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class WhenChoice(var values: MutableList<Expression>?, // if null, this is the 'else' part
|
||||
|
||||
@@ -115,6 +115,7 @@ IR/VM
|
||||
Libraries
|
||||
---------
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- Add double-array sorting routines to sorting module? (that allows you to sort a second array in sync with the array of numbers)
|
||||
- cx16: _irq_dispatcher now only dispatches a single irq source, better to ROL/BCC to handle *all* possible (multiple) sources.
|
||||
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
|
||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||
@@ -125,9 +126,6 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- when choices that are only a goto -> avoid the double branch, can branch to the label directly
|
||||
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
|
||||
- can the for loop temp var be replaced by the same logic that createRepeatCounterVar() uses for repeat loops? Take care of nested for/repeat loops to not use the same var
|
||||
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
|
||||
@@ -252,6 +252,16 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
||||
get() = children[0] as PtNodeGroup
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
|
||||
fun isOnlyGotoOrReturn(): Boolean {
|
||||
val c = statements.children
|
||||
if(c.size!=1)
|
||||
return false
|
||||
if(c[0] is PtJump || c[0] is PtReturn)
|
||||
return true
|
||||
val group = c[0] as? PtNodeGroup
|
||||
return group != null && group.children.size == 1 && (group.children[0] is PtJump || group.children[0] is PtReturn)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user