mirror of
https://github.com/irmen/prog8.git
synced 2026-03-11 05:41:42 +00:00
long params and return values
This commit is contained in:
@@ -1285,7 +1285,7 @@ $repeatLabel""")
|
||||
if(returnvalue!=null) {
|
||||
val returnDt = sub.signature.returns.single()
|
||||
if (returnDt.isNumericOrBool || returnDt.isPointer) {
|
||||
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!)
|
||||
assignExpressionToRegister(returnvalue, returnRegs.single().first.registerOrPair!!, returnDt.isSigned)
|
||||
}
|
||||
else {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
|
||||
@@ -142,7 +142,9 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
RegisterOrPair.R10R11_32,
|
||||
RegisterOrPair.R12R13_32,
|
||||
RegisterOrPair.R14R15_32 -> {
|
||||
val dt = if(signed) DataType.LONG else TODO("unsigned long")
|
||||
val dt = if(signed) DataType.LONG
|
||||
else
|
||||
TODO("unsigned long")
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,22 +710,8 @@ internal class AssignmentAsmGen(
|
||||
RegisterOrPair.AX -> assignVirtualRegister(target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignVirtualRegister(target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignVirtualRegister(target, RegisterOrPair.XY)
|
||||
RegisterOrPair.R0 -> assignVirtualRegister(target, RegisterOrPair.R0)
|
||||
RegisterOrPair.R1 -> assignVirtualRegister(target, RegisterOrPair.R1)
|
||||
RegisterOrPair.R2 -> assignVirtualRegister(target, RegisterOrPair.R2)
|
||||
RegisterOrPair.R3 -> assignVirtualRegister(target, RegisterOrPair.R3)
|
||||
RegisterOrPair.R4 -> assignVirtualRegister(target, RegisterOrPair.R4)
|
||||
RegisterOrPair.R5 -> assignVirtualRegister(target, RegisterOrPair.R5)
|
||||
RegisterOrPair.R6 -> assignVirtualRegister(target, RegisterOrPair.R6)
|
||||
RegisterOrPair.R7 -> assignVirtualRegister(target, RegisterOrPair.R7)
|
||||
RegisterOrPair.R8 -> assignVirtualRegister(target, RegisterOrPair.R8)
|
||||
RegisterOrPair.R9 -> assignVirtualRegister(target, RegisterOrPair.R9)
|
||||
RegisterOrPair.R10 -> assignVirtualRegister(target, RegisterOrPair.R10)
|
||||
RegisterOrPair.R11 -> assignVirtualRegister(target, RegisterOrPair.R11)
|
||||
RegisterOrPair.R12 -> assignVirtualRegister(target, RegisterOrPair.R12)
|
||||
RegisterOrPair.R13 -> assignVirtualRegister(target, RegisterOrPair.R13)
|
||||
RegisterOrPair.R14 -> assignVirtualRegister(target, RegisterOrPair.R14)
|
||||
RegisterOrPair.R15 -> assignVirtualRegister(target, RegisterOrPair.R15)
|
||||
in Cx16VirtualRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
in combinedLongRegisters -> assignVirtualRegister(target, returnValue.first.registerOrPair!!)
|
||||
else -> {
|
||||
val sflag = returnValue.first.statusflag
|
||||
if(sflag!=null)
|
||||
@@ -778,6 +764,10 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPointer -> assignRegisterpairWord(target, register)
|
||||
target.datatype.isLong -> {
|
||||
require(register in combinedLongRegisters)
|
||||
assignRegisterLong(target, register)
|
||||
}
|
||||
else -> throw AssemblyError("expected byte or word")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ Subroutine Calling Convention
|
||||
Calling a subroutine requires three steps:
|
||||
|
||||
#. preparing the arguments (if any) and passing them to the routine.
|
||||
Numeric types are passed by value (bytes, words, booleans, floats),
|
||||
Numeric types are passed by value (bytes, words, longs, booleans, floats),
|
||||
but array types passed by reference which means as ``uword`` being a pointer to their address in memory.
|
||||
Strings are passed as a pointer to a byte: ``^^ubyte``.
|
||||
#. calling the subroutine
|
||||
@@ -151,29 +151,16 @@ Regular subroutines
|
||||
- The arguments passed in a subroutine call are evaluated by the caller, and then put into those variables by the caller.
|
||||
The order of evaluation of subroutine call arguments *is unspecified* and should not be relied upon.
|
||||
- The subroutine is invoked.
|
||||
- The return value is not put into a variable, but the subroutine passes it back to the caller via register(s):
|
||||
- The return value is not put into a variable, but the subroutine passes it back to the caller via register(s). See below.
|
||||
|
||||
- A byte value will be put in ``A`` .
|
||||
- A boolean value will be put in ``A`` too, as 0 or 1.
|
||||
- A word or pointer value will be put in ``A`` + ``Y`` register pair (lsb in A, msb in Y).
|
||||
- A float value will be put in the ``FAC1`` float 'register'.
|
||||
.. sidebar::
|
||||
**Builtin functions can be different:**
|
||||
|
||||
- In case of *multiple* return values:
|
||||
some builtin functions are special and won't exactly follow the rules in this paragraph.
|
||||
|
||||
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
|
||||
just as for a single return value.
|
||||
- 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).
|
||||
**Single arguments will often be passed in registers:**
|
||||
|
||||
|
||||
**Builtin functions can be different:**
|
||||
some builtin functions are special and won't exactly follow these rules.
|
||||
|
||||
**Some arguments will be passed in registers:**
|
||||
For single byte, word, and pointer arguments, the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
For *single* byte, word, long, and pointer arguments, the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
*The subroutine itself will take care of putting the values into the parameter variables.* This saves on code size because
|
||||
otherwise all callers would have to store the values in those variables themselves.
|
||||
Note that his convention is also still used for subroutines that specify parameters to be put into
|
||||
@@ -193,12 +180,31 @@ Single pointer parameter: ``sub foo(^^ubyte bar) { ... }``
|
||||
gets bar in the register pair A + Y (lsb in A, msb in Y), *subroutine* stores it into parameter variable
|
||||
|
||||
Floating point parameter: ``sub foo(float bar) { ... }``
|
||||
value for bar gets copied into the parameter variable *by the caller*
|
||||
value for bar gets stored into the parameter variable *by the caller*
|
||||
|
||||
Other: ``sub foo(ubyte bar, ubyte baz, ubyte zoo) { ... }``
|
||||
register values indeterminate, values all get stored in the parameter variables *by the caller*
|
||||
not using registers; all values get stored in the subroutine's parameter variables *by the caller*
|
||||
|
||||
|
||||
**Return value**
|
||||
|
||||
- A byte return value will be put in ``A`` .
|
||||
- A boolean return value will be put in ``A`` too, as 0 or 1.
|
||||
- A word return or pointer value will be put in ``A`` + ``Y`` register pair (lsb in A, msb in Y).
|
||||
- A long return value will be put into ``cx16.r0 : cx16.r1`` (2 combined word registers to make up a single 32 bits long)
|
||||
- A float return value will be put in the ``FAC1`` float 'register'.
|
||||
|
||||
In case of *multiple* return values:
|
||||
|
||||
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
|
||||
just as for a single return value.
|
||||
- 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).
|
||||
Long values will take a pair of those "virtual registers" that combined make up a single 32 bits value.
|
||||
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).
|
||||
|
||||
|
||||
``asmsub`` and ``extsub`` routines
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -3,13 +3,13 @@ TODO
|
||||
|
||||
LONG TYPE
|
||||
---------
|
||||
- call convention: return long -> return it in R0+R1.... because AY is only 16 bits...
|
||||
- call convention: long param -> passed as regular variable NOT via R0:R1? asmsubs don't have syntax for this so use explicit separate msw() and lsw() arguments... Or introduce new syntax for R0+R1 combo's?
|
||||
- call convention: NEVER put LONG parameter into R0:R1 just use parameter variable (also fix convention doc)
|
||||
- call convention for asmsubs: asmsubs don't have syntax for passing a long value so use explicit separate msw() and lsw() arguments... Or introduce new syntax for R0+R1 combo's?
|
||||
- make sure == and != work with longs against byte and words as well signed and unsigned
|
||||
- how hard is it to also implement the other comparison operators on longs?
|
||||
- implement rol() and ror() on longs (also roxl and roxr)
|
||||
- implement rol() and ror() on longs (also rol2 and ror2)
|
||||
- implement LONG testcases in testmemory
|
||||
- document the new long type! and mklong(a,b,c,d) and mklong2(w1,w2) , print_l , print_ulhex (& conv.str_)
|
||||
- document the new long type! and mklong(a,b,c,d) and mklong2(w1,w2) , print_l , print_ulhex (& conv.str_l)
|
||||
- scan through library routines if there are opportunities to use the long? such as RDTIM
|
||||
|
||||
|
||||
|
||||
@@ -3,42 +3,18 @@
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
long @shared lv1, lv2
|
||||
|
||||
lv1 = $11223344
|
||||
lv2 = $22ffff22
|
||||
|
||||
txt.print_ulhex(lv1 | $8080, true)
|
||||
txt.spc()
|
||||
txt.print_ulhex(lv1 & $f0f0, true)
|
||||
txt.spc()
|
||||
txt.print_ulhex(lv1 ^ $8f8f, true)
|
||||
txt.nl()
|
||||
|
||||
cx16.r6 = $8080
|
||||
cx16.r7 = $f0f0
|
||||
cx16.r8 = $8f8f
|
||||
|
||||
txt.print_ulhex(lv1 | cx16.r6, true)
|
||||
txt.spc()
|
||||
txt.print_ulhex(lv1 & cx16.r7, true)
|
||||
txt.spc()
|
||||
txt.print_ulhex(lv1 ^ cx16.r8, true)
|
||||
txt.nl()
|
||||
|
||||
lv1 = $11223344
|
||||
lv2 = $22ffff22
|
||||
lv1 |= lv2
|
||||
txt.print_ulhex(lv1, true)
|
||||
txt.spc()
|
||||
lv1 = $11223344
|
||||
lv1 &= lv2
|
||||
txt.print_ulhex(lv1, true)
|
||||
txt.spc()
|
||||
lv1 = $11223344
|
||||
lv1 ^= lv2
|
||||
txt.print_ulhex(lv1, true)
|
||||
txt.nl()
|
||||
long lv = 99887766
|
||||
lv = func(lv)
|
||||
txt.print_l(lv)
|
||||
}
|
||||
|
||||
sub func(long arg) -> long {
|
||||
arg -= 1234567
|
||||
txt.print("func: ")
|
||||
txt.print_l(arg)
|
||||
txt.nl()
|
||||
return arg
|
||||
}
|
||||
|
||||
; TODO multi-value returns
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ sealed interface IPtSubroutine {
|
||||
fun cpuRegisterFor(returntype: DataType): RegisterOrStatusflag = when {
|
||||
returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||
returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
returntype.isLong -> RegisterOrStatusflag(RegisterOrPair.R0R1_32, null)
|
||||
returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
}
|
||||
@@ -38,10 +39,79 @@ sealed interface IPtSubroutine {
|
||||
|
||||
val availableIntegerRegisters = Cx16VirtualRegisters.toMutableList()
|
||||
val availableFloatRegisters = mutableListOf(RegisterOrPair.FAC1) // just one value is possible
|
||||
val availableLongRegisters = combinedLongRegisters.toMutableList()
|
||||
|
||||
fun getLongRegister(): RegisterOrPair {
|
||||
val reg = availableLongRegisters.removeLastOrNull()
|
||||
if(reg==null)
|
||||
throw AssemblyError("out of registers for long return type ${this.position}")
|
||||
else {
|
||||
// remove the pair from integer regs
|
||||
when(reg) {
|
||||
RegisterOrPair.R0R1_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R0)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R1)
|
||||
}
|
||||
RegisterOrPair.R2R3_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R2)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R3)
|
||||
}
|
||||
RegisterOrPair.R4R5_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R4)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R5)
|
||||
}
|
||||
RegisterOrPair.R6R7_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R6)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R7)
|
||||
}
|
||||
RegisterOrPair.R8R9_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R8)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R9)
|
||||
}
|
||||
RegisterOrPair.R10R11_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R10)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R11)
|
||||
}
|
||||
RegisterOrPair.R12R13_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R12)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R13)
|
||||
}
|
||||
RegisterOrPair.R14R15_32 -> {
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R14)
|
||||
availableIntegerRegisters.remove(RegisterOrPair.R15)
|
||||
}
|
||||
else -> throw AssemblyError("weird long register $reg")
|
||||
}
|
||||
return reg
|
||||
}
|
||||
}
|
||||
|
||||
fun getIntergerRegister(): RegisterOrPair {
|
||||
val reg = availableIntegerRegisters.removeLastOrNull()
|
||||
if(reg==null)
|
||||
throw AssemblyError("out of registers for byte/word return type ${this.position}")
|
||||
else {
|
||||
// remove it from long regs
|
||||
when(reg) {
|
||||
RegisterOrPair.R0, RegisterOrPair.R1 -> availableLongRegisters.remove(RegisterOrPair.R0R1_32)
|
||||
RegisterOrPair.R2, RegisterOrPair.R3 -> availableLongRegisters.remove(RegisterOrPair.R2R3_32)
|
||||
RegisterOrPair.R4, RegisterOrPair.R5 -> availableLongRegisters.remove(RegisterOrPair.R4R5_32)
|
||||
RegisterOrPair.R6, RegisterOrPair.R7 -> availableLongRegisters.remove(RegisterOrPair.R6R7_32)
|
||||
RegisterOrPair.R8, RegisterOrPair.R9 -> availableLongRegisters.remove(RegisterOrPair.R8R9_32)
|
||||
RegisterOrPair.R10, RegisterOrPair.R11 -> availableLongRegisters.remove(RegisterOrPair.R10R11_32)
|
||||
RegisterOrPair.R12, RegisterOrPair.R13 -> availableLongRegisters.remove(RegisterOrPair.R12R13_32)
|
||||
RegisterOrPair.R14, RegisterOrPair.R15 -> availableLongRegisters.remove(RegisterOrPair.R14R15_32)
|
||||
else -> throw AssemblyError("weird byte/long register $reg")
|
||||
}
|
||||
return reg
|
||||
}
|
||||
}
|
||||
|
||||
val others = returns.drop(1).map { type ->
|
||||
when {
|
||||
type.isFloat -> RegisterOrStatusflag(availableFloatRegisters.removeLastOrNull()!!, null) to type
|
||||
type.isWordOrByteOrBool -> RegisterOrStatusflag(availableIntegerRegisters.removeLastOrNull()!!, null) to type
|
||||
type.isLong -> RegisterOrStatusflag(getLongRegister(), null) to type
|
||||
type.isWordOrByteOrBool -> RegisterOrStatusflag(getIntergerRegister(), null) to type
|
||||
else -> throw AssemblyError("unsupported return type $type")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user