mirror of
https://github.com/irmen/prog8.git
synced 2026-04-26 05:17:54 +00:00
allow floating point value as part of a multi-value return
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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:**
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user