allow floating point value as part of a multi-value return

This commit is contained in:
Irmen de Jong
2025-04-18 22:33:42 +02:00
parent 9e694c0337
commit bd1894580e
11 changed files with 120 additions and 36 deletions
@@ -76,6 +76,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
RegisterOrPair.FAC1 -> IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum)
RegisterOrPair.FAC2 -> IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum)
null -> {
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
@@ -361,10 +363,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
} else {
if (targetDt == IRDataType.FLOAT)
if (targetDt == IRDataType.FLOAT) {
require(valueFpRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
else
}
else {
require(valueRegister>=0)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
}
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
@@ -1,11 +1,14 @@
package prog8.codegen.intermediate
import prog8.code.StNode
import prog8.code.StExtSub
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.Statusflag
import prog8.intermediate.*
@@ -30,7 +33,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
return when (expr) {
is PtIrRegister -> {
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
if(expr.type.isFloat)
ExpressionCodeResult(emptyList(), IRDataType.FLOAT, -1, expr.register)
else
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
}
is PtBool -> {
val code = IRCodeChunk(null, null)
@@ -1761,18 +1761,33 @@ class IRCodeGen(
if(ret.children.size>1) {
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
// (this allows unencumbered use of many Rx registers if you don't return that many values)
// make sure to assign the first value as the last in the sequence, to avoid clobbering the AY registers afterwards
// a floating point value is passed via FAC (just one fp value is possible)
val returnRegs = ret.definingISub()!!.returnsWhatWhere()
val values = ret.children.zip(returnRegs)
// first all but the first return values
for ((value, register) in values.drop(1)) {
val tr = expressionEval.translateExpression(value as PtExpression)
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
if(register.second.isFloat) {
addToResult(result, tr, -1, tr.resultFpReg)
result += setCpuRegister(register.first, IRDataType.FLOAT, -1, tr.resultFpReg)
}
else {
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
}
// finally do the first of the return values (this avoids clobbering of its value in AY)
values.first().also { (value, register) ->
val tr = expressionEval.translateExpression(value as PtExpression)
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
if(register.second.isFloat) {
addToResult(result, tr, -1, tr.resultFpReg)
result += setCpuRegister(register.first, IRDataType.FLOAT, -1, tr.resultFpReg)
}
else {
addToResult(result, tr, tr.resultReg, -1)
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
}
}
addInstr(result, IRInstruction(Opcode.RETURN), null)
return result
@@ -1592,8 +1592,8 @@ internal class AstChecker(private val program: Program,
}
if(target.returntypes.size>1) {
if (DataType.FLOAT in target.returntypes) {
errors.err("floats cannot be used as part of a multi-value result", target.position)
if(target.returntypes.count { it.isFloat }>1) {
errors.err("can only have a single float value in a multi-value result", target.position)
}
}
if(target.returntypes.size>16) {
+9 -7
View File
@@ -1164,11 +1164,11 @@ Subroutines can return more than one value.
For example, ``asmsub`` routines (implemented in assembly code) or ``extsub`` routines
(referencing an external routine in ROM or elsewhere in RAM) can return multiple values spread
across different registers, and even the CPU's status register flags for boolean values.
Normal subroutines can also return multiple values (restricted to booleans, bytes and word values).
In all of these cases, you have to "multi assign" all return values of the subroutine call to something.
You simply write the assignment targets as a comma separated list,
where the element's order corresponds to the order of the return values declared in the subroutine's signature.
So for instance::
Normal subroutines can also return multiple values.
You have to "multi assign" all return values of the subroutine call to something:
write the assignment targets as a comma separated list, where the element's order corresponds
to the order of the return values declared in the subroutine's signature.
Remember that you can use ``void`` to skip a value. So for instance::
bool flag
ubyte bytevar
@@ -1180,8 +1180,10 @@ So for instance::
.. sidebar:: register usage
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those.
Using those virtual registers during the calculation of the values in the return statement should be avoided.
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those,
depending on the number of values returend. A floating point value is passed via the FAC 'register'
(only a single floating point value is supported).
Using these during the calculation of the values in the return statement should be avoided.
Otherwise you risk overwriting an earlier return value in the sequence.
+2
View File
@@ -160,6 +160,8 @@ Regular subroutines
- for regular subroutines, the compiler will return the first of the return values via the cpu register ``A``` (or ``A + Y``` if it's a word value),
just like for subroutines that only return a single value.
The remainder of the return values are returned via the "virtual registers" cx16.r16-cx16.r0 (using R15 first and counting down to R0).
A floating point value is passed via FAC1 as usual (only a single floating point value is supported,
using FAC1 and FAC2 together unfortunately interferes with the values).
**Builtin functions can be different:**
-1
View File
@@ -7,7 +7,6 @@ TODO
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- Multi-value returns of normal subroutines: Can FAC be used for floats? Floats are now not supported for multi-value returns ("floats cannot be used as part of a multi-value result")
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
+27 -6
View File
@@ -1,19 +1,40 @@
%import textio
%option enable_floats
%import floats
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
ubyte ub
uword uw
ub, uw, fl = multi()
float @shared fl1
ub, uw = multi()
txt.print_ub(ub)
txt.spc()
txt.print_uw(uw)
txt.nl()
ub, uw, fl1 = multif()
txt.print_ub(ub)
txt.spc()
txt.print_uw(uw)
txt.spc()
txt.print_f(fl1)
txt.nl()
}
float @shared fl
sub multi() -> ubyte, uword {
cx16.r0 = 55
cx16.r1 = 11111
return cx16.r0L, cx16.r1
}
sub multi() -> ubyte, uword, float {
cx16.r0++
return cx16.r0L, cx16.r1, fl
sub multif() -> ubyte, uword, float {
cx16.r0 = 111
cx16.r1 = 22222
return cx16.r0L, cx16.r1, 42.99
}
}
@@ -149,7 +149,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(usedRegs.readFpRegs.any())
regs.append(" read: ${usedRegs.readFpRegs.toSortedMap().map { (reg, amount) -> "fr$reg=${amount}" }}\n")
if(usedRegs.writeFpRegs.any())
regs.append(" write: ${usedRegs.writeRegs.toSortedMap().map { (reg, amount) -> "fr$reg=${amount}" }}\n")
regs.append(" write: ${usedRegs.writeFpRegs.toSortedMap().map { (reg, amount) -> "fr$reg=${amount}" }}\n")
}
xml.writeStartElement("CODE")
+15 -5
View File
@@ -31,11 +31,21 @@ sealed interface IPtSubroutine {
}
else -> {
// for multi-value results, put the first one in A or AY cpu register(s) and the rest in the virtual registers starting from R15 and counting down
val first = cpuRegisterFor(returns.first()) to returns.first()
val others = returns.drop(1)
.zip(Cx16VirtualRegisters.reversed())
.map { (type, reg) -> RegisterOrStatusflag(reg, null) to type }
return listOf(first) + others
// a floating point return values is returned in FAC1. Only a single fp value is possible.
// The reason FAC2 cannot be used as well to support 2 fp values is that working with both FACs interferes with another.
val firstRegister = cpuRegisterFor(returns.first()) to returns.first()
val availableIntegerRegisters = Cx16VirtualRegisters.toMutableList()
val availableFloatRegisters = mutableListOf(RegisterOrPair.FAC1) // just one value is possible
val others = returns.drop(1).map { type ->
when {
type.isFloat -> RegisterOrStatusflag(availableFloatRegisters.removeLast(), null) to type
type.isIntegerOrBool -> RegisterOrStatusflag(availableIntegerRegisters.removeLast(), null) to type
else -> throw AssemblyError("unsupported return type $type")
}
}
return listOf(firstRegister) + others
}
}
}
+27 -4
View File
@@ -60,6 +60,8 @@ class VirtualMachine(irProgram: IRProgram) {
var hardwareRegisterA: UByte = 0u
var hardwareRegisterX: UByte = 0u
var hardwareRegisterY: UByte = 0u
var hardwareRegisterFAC0: Double = 0.0
var hardwareRegisterFAC1: Double = 0.0
internal var randomGenerator = Random(0xa55a7653)
internal var randomGeneratorFloats = Random(0xc0d3dbad)
@@ -195,8 +197,8 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.LOADHAX -> InsLOADHAX(ins)
Opcode.LOADHAY -> InsLOADHAY(ins)
Opcode.LOADHXY -> InsLOADHXY(ins)
Opcode.LOADHFACZERO -> TODO("read cpu reg FAC0")
Opcode.LOADHFACONE -> TODO("read cpu reg FAC1")
Opcode.LOADHFACZERO -> InsLOADHFACZERO(ins)
Opcode.LOADHFACONE -> InsLOADHFACONE(ins)
Opcode.STOREM -> InsSTOREM(ins)
Opcode.STOREX -> InsSTOREX(ins)
Opcode.STOREIX -> InsSTOREIX(ins)
@@ -210,8 +212,8 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.STOREHAX -> InsSTOREHAX(ins)
Opcode.STOREHAY -> InsSTOREHAY(ins)
Opcode.STOREHXY -> InsSTOREHXY(ins)
Opcode.STOREHFACZERO -> TODO("store cpu reg FAC0")
Opcode.STOREHFACONE-> TODO("store cpu reg FAC1")
Opcode.STOREHFACZERO -> InsSTOREHFACZERO(ins)
Opcode.STOREHFACONE-> InsSTOREHFACONE(ins)
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPI -> InsJUMPI(ins)
Opcode.PREPARECALL -> nextPc()
@@ -1292,6 +1294,27 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsLOADHFACZERO(ins: IRInstruction) {
registers.setFloat(ins.fpReg1!!, hardwareRegisterFAC0)
nextPc()
}
private fun InsLOADHFACONE(ins: IRInstruction) {
registers.setFloat(ins.fpReg1!!, hardwareRegisterFAC1)
nextPc()
}
private fun InsSTOREHFACZERO(ins: IRInstruction) {
hardwareRegisterFAC0 = registers.getFloat(ins.fpReg1!!)
nextPc()
}
private fun InsSTOREHFACONE(ins: IRInstruction) {
hardwareRegisterFAC1 = registers.getFloat(ins.fpReg1!!)
nextPc()
}
private fun statusbitsNZ(value: Int, type: IRDataType) {
statusZero = value==0
when(type) {