support assigning multiple return flags from asmsub in 6502 codegen

This commit is contained in:
Irmen de Jong 2024-09-09 22:56:40 +02:00
parent 64e66e732f
commit 4e98fb75d6
5 changed files with 273 additions and 110 deletions

View File

@ -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<Pair<StRomSubParameter, PtNode>>,
registersResults: List<Pair<StRomSubParameter, PtNode>>
) {
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): 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<Pair<StRomSubParameter, PtNode>>) {
// 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<Pair<StRomSubParameter, PtNode>>, 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<Pair<StRomSubParameter, PtNode>>) {
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<Pair<StRomSubParameter, PtNode>>) {
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 -> {

View File

@ -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")
}

View File

@ -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

View File

@ -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
}
})

View File

@ -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
}}
}
}