mirror of
https://github.com/irmen/prog8.git
synced 2025-01-26 19:30:59 +00:00
allow multi-assign to skip any status register result
This commit is contained in:
parent
0c5e8ca199
commit
3e34a3ef72
@ -84,24 +84,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
// because we can only handle integer results right now we can just zip() it all up
|
||||
val (statusFlagResult, registersResults) = sub.returns.zip(assignment.children).partition { it.first.register.statusflag!=null }
|
||||
if(statusFlagResult.isNotEmpty()) {
|
||||
val (returns, target) = statusFlagResult.single()
|
||||
if(returns.register.statusflag!=Statusflag.Pc)
|
||||
TODO("other status flag for return value")
|
||||
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 (statusFlagResult, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
|
||||
if(statusFlagResult.isNotEmpty()) {
|
||||
val (returns, target) = statusFlagResult.single()
|
||||
if(returns.register.statusflag!=Statusflag.Pc)
|
||||
TODO("other status flag for return value")
|
||||
|
||||
target as PtAssignTarget
|
||||
if(registersResults.all { (it.second as PtAssignTarget).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)
|
||||
assignCarryResult(target, false)
|
||||
return
|
||||
target as PtAssignTarget
|
||||
if(registersResults.all { (it.second as PtAssignTarget).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)
|
||||
assignCarryResult(target, false)
|
||||
return
|
||||
}
|
||||
assignCarryResult(target, needsToSaveA(registersResults))
|
||||
}
|
||||
assignCarryResult(target, needsToSaveA(registersResults))
|
||||
assignRegisterResults(registersResults)
|
||||
} else if (sub.returns.size>assignmentTargets.size) {
|
||||
// Targets and values don't match. Skip status flag results, assign only the normal value results.
|
||||
val targets = assignmentTargets.iterator()
|
||||
sub.returns.forEach {
|
||||
if(it.register.registerOrPair!=null) {
|
||||
val target = targets.next() as PtAssignTarget
|
||||
assignRegisterResults(listOf(it to target))
|
||||
}
|
||||
}
|
||||
require(!targets.hasNext())
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
assignRegisterResults(registersResults)
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,14 +22,30 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
|
||||
if(funcCall.multipleResultFpRegs.isNotEmpty())
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
|
||||
// because we can only handle integer results right now we can just zip() it all up
|
||||
val assignmentTargets = assignment.children.dropLast(1)
|
||||
addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg)
|
||||
sub.returns.zip(assignment.children).zip(funcCall.multipleResultRegs).forEach {
|
||||
val regNumber = it.second
|
||||
val returns = it.first.first
|
||||
val target = it.first.second as PtAssignTarget
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
if(sub.returns.size==assignmentTargets.size) {
|
||||
// Targets and values match. Assign all the things.
|
||||
sub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
|
||||
val regNumber = it.second
|
||||
val returns = it.first.first
|
||||
val target = it.first.second as PtAssignTarget
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
} else if (sub.returns.size>assignmentTargets.size) {
|
||||
// Targets and values don't match. Skip status flag results, assign only the normal value results.
|
||||
val targets = assignmentTargets.iterator()
|
||||
sub.returns.zip(funcCall.multipleResultRegs).forEach {
|
||||
val returns = it.first
|
||||
if(returns.register.registerOrPair!=null) {
|
||||
val target = targets.next() as PtAssignTarget
|
||||
val regNumber = it.second
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
}
|
||||
require(!targets.hasNext())
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
|
@ -547,47 +547,67 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
|
||||
// multi-assign: check the number of assign targets vs. the number of return values of the subroutine
|
||||
// also check the types of the variables vs the types of each return value
|
||||
val fcall = assignment.value as? IFunctionCall
|
||||
val fcallTarget = fcall?.target?.targetSubroutine(program)
|
||||
if(assignment.target.multi!=null) {
|
||||
val multi = assignment.target.multi!!
|
||||
if(fcall==null) {
|
||||
errors.err("expected a function call with multiple return values", assignment.value.position)
|
||||
} else {
|
||||
if(fcallTarget==null) {
|
||||
errors.err("expected a function call with multiple return values", assignment.value.position)
|
||||
} else {
|
||||
if(fcallTarget.returntypes.size!=multi.size) {
|
||||
errors.err("expected ${multi.size} return values, have ${fcallTarget.returntypes.size}", fcall.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.noErrors()) {
|
||||
// check the types...
|
||||
fcallTarget!!.returntypes.zip(multi).withIndex().forEach { (index, p) ->
|
||||
val (returnType, target) = p
|
||||
val targetDt = target.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(!(returnType isAssignableTo targetDt))
|
||||
errors.err("can't assign returnvalue #${index+1} to corresponding target; ${returnType} vs $targetDt", target.position)
|
||||
}
|
||||
}
|
||||
|
||||
checkMultiAssignment(assignment, fcall, fcallTarget)
|
||||
} else if(fcallTarget!=null) {
|
||||
if(fcallTarget.returntypes.size!=1) {
|
||||
// If there are 2 return values, one of them being a boolean in a status register, this is okay.
|
||||
// In that case the normal value is assigned and the status bit is dealth with separately for example with if_cs
|
||||
val (returnRegisters, _) = fcallTarget.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if(returnRegisters.size>1)
|
||||
errors.err("expected 1 return value, have ${fcallTarget.returntypes.size}", fcall.position)
|
||||
if(returnRegisters.size>1) {
|
||||
errors.err("multiple return values and too few assignment targets, need at least ${returnRegisters.size}", fcall.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
|
||||
private fun checkMultiAssignment(assignment: Assignment, fcall: IFunctionCall?, fcallTarget: Subroutine?) {
|
||||
// multi-assign: check the number of assign targets vs. the number of return values of the subroutine
|
||||
// also check the types of the variables vs the types of each return value
|
||||
if(fcall==null || fcallTarget==null) {
|
||||
errors.err("expected a function call with multiple return values", assignment.value.position)
|
||||
return
|
||||
}
|
||||
val targets = assignment.target.multi!!
|
||||
if(fcallTarget.returntypes.size<targets.size) {
|
||||
errors.err("too many assignment targets, ${targets.size} targets for ${fcallTarget.returntypes.size} return values", fcall.position)
|
||||
return
|
||||
}
|
||||
if(fcallTarget.returntypes.size>targets.size) {
|
||||
// You can have LESS assign targets than the number of result values,
|
||||
// as long as the result values contain booleans that are returned in cpu status flags (like Carry).
|
||||
// These may be ignored in the assignment - only "true" values NEED to have a target.
|
||||
val numberOfNormalValues = fcallTarget.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
|
||||
if(numberOfNormalValues != targets.size) {
|
||||
errors.err("multiple return values and too few assignment targets, need at least $numberOfNormalValues", fcall.position)
|
||||
return
|
||||
}
|
||||
// check the types of the 'normal' values that are being assigned
|
||||
val returnTypesAndRegisters = fcallTarget.returntypes.zip(fcallTarget.asmReturnvaluesRegisters)
|
||||
returnTypesAndRegisters.zip(targets).withIndex().forEach { (index, p) ->
|
||||
val (returnType, register) = p.first
|
||||
if(register.registerOrPair!=null) {
|
||||
val target = p.second
|
||||
val targetDt = target.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (!(returnType isAssignableTo targetDt))
|
||||
errors.err("can't assign returnvalue #${index + 1} to corresponding target; $returnType vs $targetDt", target.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check all the assigment target types
|
||||
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
|
||||
val (returnType, target) = p
|
||||
val targetDt = target.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (!(returnType isAssignableTo targetDt))
|
||||
errors.err("can't assign returnvalue #${index + 1} to corresponding target; $returnType vs $targetDt", target.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
super.visit(assignTarget)
|
||||
|
@ -2,6 +2,7 @@ package prog8tests.ast
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
@ -110,9 +111,11 @@ main {
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared flag
|
||||
ubyte @shared bytevar
|
||||
|
||||
cx16.r0L, flag = test2(12345, 5566, flag, -42)
|
||||
cx16.r1, flag = test3()
|
||||
cx16.r1, flag, bytevar = test3()
|
||||
cx16.r1, bytevar = test3() ; omitting the status flag result should also work
|
||||
}
|
||||
|
||||
asmsub test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc {
|
||||
@ -123,41 +126,37 @@ main {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub test3() -> uword @R1, bool @Pc {
|
||||
asmsub test3() -> uword @R1, bool @Pc, ubyte @X {
|
||||
%asm {{
|
||||
lda #0
|
||||
ldy #0
|
||||
ldx #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(Cx16Target(), false, src, errors, true)!!
|
||||
errors.errors.size shouldBe 0
|
||||
val start = result.codegenAst!!.entrypoint()!!
|
||||
start.children.size shouldBe 5
|
||||
val a1_1 = start.children[2] as PtAssignment
|
||||
val a1_2 = start.children[3] as PtAssignment
|
||||
start.children.size shouldBe 8
|
||||
val a1_1 = start.children[4] as PtAssignment
|
||||
val a1_2 = start.children[5] as PtAssignment
|
||||
val a1_3 = start.children[6] as PtAssignment
|
||||
a1_1.multiTarget shouldBe true
|
||||
a1_2.multiTarget shouldBe true
|
||||
a1_3.multiTarget shouldBe true
|
||||
a1_1.children.size shouldBe 3
|
||||
a1_2.children.size shouldBe 4
|
||||
a1_3.children.size shouldBe 3
|
||||
(a1_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
|
||||
(a1_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
|
||||
(a1_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a1_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
|
||||
|
||||
errors.clear()
|
||||
val result2=compileText(VMTarget(), false, src, errors, true)!!
|
||||
errors.errors.size shouldBe 0
|
||||
val start2 = result2.codegenAst!!.entrypoint()!!
|
||||
start2.children.size shouldBe 5
|
||||
val a2_1 = start2.children[2] as PtAssignment
|
||||
val a2_2 = start2.children[3] as PtAssignment
|
||||
a2_1.multiTarget shouldBe true
|
||||
a2_2.multiTarget shouldBe true
|
||||
(a2_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
|
||||
(a2_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("main.start.flag")
|
||||
(a2_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a2_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("main.start.flag")
|
||||
(a1_2.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
|
||||
(a1_3.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a1_3.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
|
||||
}
|
||||
|
||||
test("multi-assign from romsub") {
|
||||
@ -165,43 +164,40 @@ main {
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared flag
|
||||
ubyte @shared bytevar
|
||||
|
||||
flag = test(42)
|
||||
cx16.r0L, flag = test2(12345, 5566, flag, -42)
|
||||
cx16.r1, flag = test3()
|
||||
cx16.r1, flag, bytevar = test3()
|
||||
cx16.r1, bytevar = test3() ; omitting the status flag result should also work
|
||||
}
|
||||
|
||||
romsub ${'$'}8000 = test(ubyte arg @A) -> bool @Pc
|
||||
romsub ${'$'}8002 = test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc
|
||||
romsub ${'$'}8003 = test3() -> uword @R1, bool @Pc
|
||||
romsub ${'$'}8003 = test3() -> uword @R1, bool @Pc, ubyte @X
|
||||
}"""
|
||||
|
||||
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(Cx16Target(), false, src, errors, true)!!
|
||||
errors.errors.size shouldBe 0
|
||||
val start = result.codegenAst!!.entrypoint()!!
|
||||
start.children.size shouldBe 5
|
||||
val a1_1 = start.children[2] as PtAssignment
|
||||
val a1_2 = start.children[3] as PtAssignment
|
||||
start.children.size shouldBe 9
|
||||
val a1_1 = start.children[5] as PtAssignment
|
||||
val a1_2 = start.children[6] as PtAssignment
|
||||
val a1_3 = start.children[7] as PtAssignment
|
||||
a1_1.multiTarget shouldBe true
|
||||
a1_2.multiTarget shouldBe true
|
||||
a1_3.multiTarget shouldBe true
|
||||
a1_1.children.size shouldBe 3
|
||||
a1_2.children.size shouldBe 4
|
||||
a1_3.children.size shouldBe 3
|
||||
(a1_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
|
||||
(a1_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
|
||||
(a1_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a1_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
|
||||
|
||||
errors.clear()
|
||||
val result2=compileText(VMTarget(), false, src, errors, true)!!
|
||||
errors.errors.size shouldBe 0
|
||||
val start2 = result2.codegenAst!!.entrypoint()!!
|
||||
start2.children.size shouldBe 5
|
||||
val a2_1 = start2.children[2] as PtAssignment
|
||||
val a2_2 = start2.children[3] as PtAssignment
|
||||
a2_1.multiTarget shouldBe true
|
||||
a2_2.multiTarget shouldBe true
|
||||
(a2_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
|
||||
(a2_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("main.start.flag")
|
||||
(a2_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a2_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("main.start.flag")
|
||||
(a1_2.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
|
||||
(a1_3.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
|
||||
(a1_3.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
|
||||
}
|
||||
})
|
||||
|
@ -684,16 +684,13 @@ are all assigned to individual assignment targets. You simply write them as a co
|
||||
|
||||
asmsub multisub() -> uword @AY, bool @Pc, ubyte @X { ... }
|
||||
|
||||
**There is also a special rule:** if there's just one return value in a register, and one or more others that are returned
|
||||
as bits in the status register (such as the Carry bit), the compiler *also* allows you to call the subroutine and just assign a *single* return value.
|
||||
It will then store the result value in a variable if required, and *try to keep the status register untouched
|
||||
after the call* so you can often use a conditional branch statement for that. But the latter is tricky,
|
||||
make sure you check the generated assembly code.
|
||||
|
||||
.. note::
|
||||
For asmsubs or romsubs that return a boolean status flag in a cpu status register such as the Carry flag,
|
||||
it is always more efficient to use a conditional branch like `if_cs` to act on that value, than storing
|
||||
it in a variable and then adding an `if flag...` statement afterwards.
|
||||
**There is also a special rule:** you are allowed to omit assignments of the boolean values returned in status registers such as the carry flag.
|
||||
So in the case of multisub() above, you could also write `wordvar, bytevar = multisub()` and leave out the carry flag.
|
||||
The compiler will try to assign the normal numeric values and leave the status flags untouched, which then allows you to
|
||||
use a conditional branch such as `if_cs` to do something with it. This is always more efficient
|
||||
than storing it in a variable and then adding an `if flag...` statement afterwards.
|
||||
It can sometimes be tricky to keep the status flags that are returned from the subroutine intact though,
|
||||
if the assign targets are not simple variables. In such cases, make sure you check the generated assembly code to see if it all works out.
|
||||
|
||||
|
||||
Subroutine definitions
|
||||
|
@ -1,7 +1,10 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
make it possible to omit the Status Register values in multi-assigns regardless of the number of return values (is now 1 value)
|
||||
try to replace the variable number of assignment targets by allowing placeholder '_' target
|
||||
|
||||
|
||||
check souce code of examples and library, for void calls that could now be turned into multi-assign calls.
|
||||
|
||||
...
|
||||
|
||||
|
@ -1,41 +1,30 @@
|
||||
%import textio
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared flag
|
||||
ubyte @shared bytevar
|
||||
uword @shared wordvar
|
||||
|
||||
; cx16.r1=9999
|
||||
; flag = test(42)
|
||||
; cx16.r0L, flag = test2(12345, 5566, flag, -42)
|
||||
; cx16.r1, flag = test3()
|
||||
|
||||
wordvar, bytevar, flag = test4()
|
||||
wordvar, bytevar, flag = test4()
|
||||
wordvar, bytevar = test4()
|
||||
if_cs
|
||||
txt.print("true! ")
|
||||
else
|
||||
txt.print("false! ")
|
||||
|
||||
txt.print_uwhex(wordvar, true)
|
||||
txt.spc()
|
||||
txt.print_bool(flag)
|
||||
txt.spc()
|
||||
txt.print_ub(bytevar)
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
romsub $8000 = test(ubyte arg @A) -> bool @Pc
|
||||
romsub $8002 = test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc
|
||||
romsub $8003 = test3() -> uword @R1, bool @Pc
|
||||
|
||||
|
||||
asmsub test4() -> uword @AY, ubyte @X, bool @Pc {
|
||||
%asm {{
|
||||
lda #<$11ee
|
||||
ldy #>$11ee
|
||||
ldx #42
|
||||
sec
|
||||
clc
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user