From 4e98fb75d615529a30b790c3749a1cd6680349ed Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 9 Sep 2024 22:56:40 +0200 Subject: [PATCH] support assigning multiple return flags from asmsub in 6502 codegen --- .../cpu6502/assignment/AssignmentAsmGen.kt | 213 ++++++++++-------- .../codegen/intermediate/AssignmentGen.kt | 5 +- compiler/test/ast/TestSubroutines.kt | 6 +- .../test/codegeneration/TestVariousCodeGen.kt | 20 ++ examples/test.p8 | 139 +++++++++++- 5 files changed, 273 insertions(+), 110 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index f5994a4d8..e09ab2409 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -48,6 +48,26 @@ internal class AssignmentAsmGen( asmgen.translate(values) + val assignmentTargets = assignment.children.dropLast(1) + if(sub.returns.size==assignmentTargets.size) { + // because we can only handle integer results right now we can just zip() it all up + val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null } + if (statusFlagResults.isEmpty()) + assignRegisterResults(registersResults) + else if(registersResults.isEmpty()) + assignOnlyTheStatusFlagsResults(false, statusFlagResults) + else + assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults) + } else { + throw AssemblyError("number of values and targets don't match") + } + } + + private fun assignStatusFlagsAndRegistersResults( + statusFlagResults: List>, + registersResults: List> + ) { + fun needsToSaveA(registersResults: List>): Boolean = if(registersResults.isEmpty()) false @@ -56,124 +76,123 @@ internal class AssignmentAsmGen( else true - fun assignCarryFlagResult(target: PtAssignTarget, saveA: Boolean) { - if(saveA) asmgen.out(" pha") - asmgen.out(" lda #0 | rol a") - val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) - assignRegisterByte(tgt, CpuRegister.A, false, false) - if(saveA) asmgen.out(" pla") + if(registersResults.all { + val tgt = it.second as PtAssignTarget + tgt.void || tgt.identifier!=null}) + { + // all other results are just stored into identifiers directly so first handle those + // (simple store instructions that don't modify the carry flag) + assignRegisterResults(registersResults) + assignOnlyTheStatusFlagsResults(false, statusFlagResults) + } else { + val saveA = needsToSaveA(registersResults) + assignOnlyTheStatusFlagsResults(saveA, statusFlagResults) + assignRegisterResults(registersResults) + } + } + + private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List>) { + // assigning flags to their variables targets requires load-instructions that destroy flags + // so if there's more than 1, we need to save and restore the flags + val saveFlags = statusFlagResults.size>1 + + fun hasFlag(statusFlagResults: List>, flag: Statusflag): PtAssignTarget? { + for ((returns, target) in statusFlagResults) { + if(returns.register.statusflag!! == flag) + return target as PtAssignTarget + } + return null } - fun assignZeroFlagResult(target: PtAssignTarget, saveA: Boolean) { - if(saveA) asmgen.out(" pha") - asmgen.out(""" + val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc) + val targetZero = hasFlag(statusFlagResults, Statusflag.Pz) + val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn) + val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv) + + if(saveA) asmgen.out(" pha") + if(targetZero!=null && !targetZero.void) + assignZeroFlagResult(targetZero, saveFlags) + if(targetNeg!=null && !targetNeg.void) + assignNegativeFlagResult(targetNeg, saveFlags) + if(targetCarry!=null && !targetCarry.void) + assignCarryFlagResult(targetCarry) + if(targetOverflow!=null && !targetOverflow.void) + assignOverflowFlagResult(targetOverflow) + if(saveA) asmgen.out(" pla") + } + + private fun assignRegisterResults(registersResults: List>) { + registersResults.forEach { (returns, target) -> + target as PtAssignTarget + if(!target.void) { + val targetIdent = target.identifier + val targetMem = target.memory + if(targetIdent!=null || targetMem!=null) { + val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) + when(returns.type) { + in ByteDatatypesWithBoolean -> { + if(returns.register.registerOrPair in Cx16VirtualRegisters) { + assignVirtualRegister(tgt, returns.register.registerOrPair!!) + } else { + assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false) + } + } + in WordDatatypes -> { + assignRegisterpairWord(tgt, returns.register.registerOrPair!!) + } + else -> throw AssemblyError("weird dt") + } + } + else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity + } + } + } + + private fun assignCarryFlagResult(target: PtAssignTarget) { + // overflow is not clobbered so no need to save/restore it + asmgen.out(" lda #0 | rol a") + val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) + assignRegisterByte(tgt, CpuRegister.A, false, false) + } + + private fun assignZeroFlagResult(target: PtAssignTarget, saveFlags: Boolean) { + if(saveFlags) asmgen.out(" php") + asmgen.out(""" beq + lda #0 beq ++ + lda #1 +""") - val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) - assignRegisterByte(tgt, CpuRegister.A, false, false) - if(saveA) asmgen.out(" pla") - } + val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) + assignRegisterByte(tgt, CpuRegister.A, false, false) + if(saveFlags) asmgen.out(" plp") + } - fun assignNegativeFlagResult(target: PtAssignTarget, saveA: Boolean) { - if(saveA) asmgen.out(" pha") - asmgen.out(""" + private fun assignNegativeFlagResult(target: PtAssignTarget, saveFlags: Boolean) { + if(saveFlags) asmgen.out(" php") + asmgen.out(""" bmi + lda #0 beq ++ + lda #1 +""") - val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) - assignRegisterByte(tgt, CpuRegister.A, false, false) - if(saveA) asmgen.out(" pla") - } + val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) + assignRegisterByte(tgt, CpuRegister.A, false, false) + if(saveFlags) asmgen.out(" plp") + } - fun assignOverflowFlagResult(target: PtAssignTarget, saveA: Boolean) { - if(saveA) asmgen.out(" pha") - asmgen.out(""" + private fun assignOverflowFlagResult(target: PtAssignTarget) { + // overflow is not clobbered so no need to save/restore it + asmgen.out(""" bvs + lda #0 beq ++ + lda #1 +""") - val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) - assignRegisterByte(tgt, CpuRegister.A, false, false) - if(saveA) asmgen.out(" pla") - } - - fun assignRegisterResults(registersResults: List>) { - registersResults.forEach { (returns, target) -> - target as PtAssignTarget - if(!target.void) { - val targetIdent = target.identifier - val targetMem = target.memory - if(targetIdent!=null || targetMem!=null) { - val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) - when(returns.type) { - in ByteDatatypesWithBoolean -> { - if(returns.register.registerOrPair in Cx16VirtualRegisters) { - assignVirtualRegister(tgt, returns.register.registerOrPair!!) - } else { - assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false) - } - } - in WordDatatypes -> { - assignRegisterpairWord(tgt, returns.register.registerOrPair!!) - } - else -> throw AssemblyError("weird dt") - } - } - else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity - } - } - } - - val assignmentTargets = assignment.children.dropLast(1) - if(sub.returns.size==assignmentTargets.size) { - // because we can only handle integer results right now we can just zip() it all up - val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null } - if(statusFlagResults.isNotEmpty()) { - if(statusFlagResults.size>1) - TODO("handle multiple status flag results") - val (returns, target) = statusFlagResults.single() - target as PtAssignTarget - if(target.void) { - // forget about the Carry status flag, only assign the normal return values - assignRegisterResults(registersResults) - return - } - if(registersResults.all { - val tgt = it.second as PtAssignTarget - tgt.void || tgt.identifier!=null}) - { - // all other results are just stored into identifiers directly so first handle those - // (simple store instructions that don't modify the carry flag) - assignRegisterResults(registersResults) - return when(returns.register.statusflag!!) { - Statusflag.Pc -> assignCarryFlagResult(target, false) - Statusflag.Pz -> assignZeroFlagResult(target, false) - Statusflag.Pv -> assignOverflowFlagResult(target, false) - Statusflag.Pn -> assignNegativeFlagResult(target, false) - } - } - - val saveA = needsToSaveA(registersResults) - when(returns.register.statusflag!!) { - Statusflag.Pc -> assignCarryFlagResult(target, saveA) - Statusflag.Pz -> assignZeroFlagResult(target, saveA) - Statusflag.Pv -> assignOverflowFlagResult(target, saveA) - Statusflag.Pn -> assignNegativeFlagResult(target, saveA) - } - } - assignRegisterResults(registersResults) - } else { - throw AssemblyError("number of values and targets don't match") - } + val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) + assignRegisterByte(tgt, CpuRegister.A, false, false) } - fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) { when(assign.source.kind) { SourceStorageKind.LITERALBOOLEAN -> { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 30289d853..b66e81f93 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -57,10 +57,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum) in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}") null -> { - when(returns.register.statusflag) { - Statusflag.Pc -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum) - else -> throw AssemblyError("weird statusflag as returnvalue") - } + TODO("assign CPU status flag ${returns.register.statusflag!!}") } else -> throw AssemblyError("cannot load register") } diff --git a/compiler/test/ast/TestSubroutines.kt b/compiler/test/ast/TestSubroutines.kt index 92f9578ff..fb18068bf 100644 --- a/compiler/test/ast/TestSubroutines.kt +++ b/compiler/test/ast/TestSubroutines.kt @@ -10,8 +10,8 @@ import prog8.code.ast.PtAssignTarget import prog8.code.ast.PtAssignment import prog8.code.core.DataType import prog8.code.core.SourceCode +import prog8.code.target.C64Target import prog8.code.target.Cx16Target -import prog8.code.target.VMTarget import prog8.parser.Prog8Parser.parseModule import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.compileText @@ -136,7 +136,7 @@ main { }} } }""" - compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null + compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null val errors = ErrorReporterForTests() val result = compileText(Cx16Target(), false, src, errors, true)!! errors.errors.size shouldBe 0 @@ -186,7 +186,7 @@ main { romsub ${'$'}8003 = test3() -> uword @R1, bool @Pc, ubyte @X }""" - compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null + compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null val errors = ErrorReporterForTests() val result = compileText(Cx16Target(), false, src, errors, true)!! errors.errors.size shouldBe 0 diff --git a/compiler/test/codegeneration/TestVariousCodeGen.kt b/compiler/test/codegeneration/TestVariousCodeGen.kt index 2fffdacc6..eec805e69 100644 --- a/compiler/test/codegeneration/TestVariousCodeGen.kt +++ b/compiler/test/codegeneration/TestVariousCodeGen.kt @@ -420,4 +420,24 @@ main { compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null } + + test("multiple status flags return values from asmsub") { + val src=""" +main { + romsub 5000 = carryAndNegativeAndByteAndWord() -> bool @Pc, bool @Pn, ubyte @X, uword @AY + + sub start() { + ubyte @shared x + uword @shared w + bool @shared flag1 + bool @shared flag2 + + flag1, flag2, x, w = carryAndNegativeAndByteAndWord() + flag1, void, void, w = carryAndNegativeAndByteAndWord() + void, void, void, void = carryAndNegativeAndByteAndWord() + void carryAndNegativeAndByteAndWord() + } +}""" + compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null + } }) \ No newline at end of file diff --git a/examples/test.p8 b/examples/test.p8 index 55b0288f4..1bb8af7d9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -2,15 +2,142 @@ %zeropage basicsafe %option no_sysinit + main { sub start() { - byte @shared x - word @shared w + ubyte @shared x + uword @shared w + bool flag1, flag2 - if x==0 or x==1 or x==2 - x++ + sys.clear_carry() + flag1 = onlyCarry() + if flag1 + txt.print("1: ok\n") + else + txt.print("1: fail\n") - if w==0 or w==1 or w==2 - x++ + x=1 + flag1 = onlyZero() + if flag1 + txt.print("2: ok\n") + else + txt.print("2: fail\n") + + sys.clear_carry() + flag1, x = carryAndByte() + if flag1 and x==42 + txt.print("3: ok\n") + else + txt.print("3: fail\n") + + sys.clear_carry() + flag1, w = carryAndWord() + if flag1 and w==4242 + txt.print("4: ok\n") + else + txt.print("4: fail\n") + + sys.clear_carry() + flag1, x, w = carryAndValues() + if flag1 and x==99 and w==9999 + txt.print("5: ok\n") + else + txt.print("5: fail\n") + + x = 1 + sys.clear_carry() + flag1, flag2 = onlyCarryAndZero() + if flag1 and flag2 + txt.print("6: ok\n") + else + txt.print("6: fail\n") + + x = 1 + sys.clear_carry() + flag1, flag2, x = carryAndZeroAndByte() + if flag1 and flag2 and x==33 + txt.print("7: ok\n") + else + txt.print("7: fail\n") + + x = 1 + sys.clear_carry() + flag1, flag2, x, w = carryAndNegativeAndByteAndWord() + if flag1 and flag2 and x==55 and w==51400 + txt.print("8: ok\n") + else + txt.print("8: fail\n") + } + + + asmsub carryAndNegativeAndByteAndWord() -> bool @Pc, bool @Pn, ubyte @X, uword @AY { + %asm {{ + ldx #55 + lda #200 + ldy #200 + sec + rts + }} + } + + asmsub carryAndZeroAndByte() -> bool @Pc, bool @Pz, ubyte @Y { + %asm {{ + ldy #33 + lda #0 + sec + rts + }} + } + + + asmsub onlyCarryAndZero() -> bool @Pc, bool @Pz { + %asm {{ + lda #0 + sec + rts + }} + } + + + asmsub carryAndValues() -> bool @Pc, ubyte @X, uword @AY { + %asm {{ + ldx #99 + lda #<9999 + ldy #>9999 + sec + rts + }} + } + + + asmsub carryAndWord() -> bool @Pc, uword @AY { + %asm {{ + lda #<4242 + ldy #>4242 + sec + rts + }} + } + + asmsub carryAndByte() -> bool @Pc, ubyte @A { + %asm {{ + lda #42 + sec + rts + }} + } + + asmsub onlyCarry() -> bool @Pc { + %asm {{ + sec + rts + }} + } + + asmsub onlyZero() -> bool @Pz { + %asm {{ + lda #0 + rts + }} } }