long params and return values

This commit is contained in:
Irmen de Jong
2025-09-29 00:25:40 +02:00
parent e7fc0360ad
commit 88c5d9783a
7 changed files with 126 additions and 82 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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