From 88c5d9783aaca794fddb1f771301acd856fbc6db Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 29 Sep 2025 00:25:40 +0200 Subject: [PATCH] long params and return values --- .../src/prog8/codegen/cpu6502/AsmGen.kt | 2 +- .../cpu6502/assignment/AsmAssignment.kt | 4 +- .../cpu6502/assignment/AssignmentAsmGen.kt | 22 ++---- docs/source/technical.rst | 50 +++++++------ docs/source/todo.rst | 8 +-- examples/test.p8 | 50 ++++--------- simpleAst/src/prog8/code/ast/AstStatements.kt | 72 ++++++++++++++++++- 7 files changed, 126 insertions(+), 82 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 9a2c3757d..0aaed0030 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -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 diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt index 626427146..2818c7295 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt @@ -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) } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 14393411c..2f2272f1f 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -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") } } diff --git a/docs/source/technical.rst b/docs/source/technical.rst index a96b45619..466d70398 100644 --- a/docs/source/technical.rst +++ b/docs/source/technical.rst @@ -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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 66496035a..29c7d01f3 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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 diff --git a/examples/test.p8 b/examples/test.p8 index 6698b0c73..a2c1c8e09 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 } diff --git a/simpleAst/src/prog8/code/ast/AstStatements.kt b/simpleAst/src/prog8/code/ast/AstStatements.kt index 9c757ec87..a0d3183eb 100644 --- a/simpleAst/src/prog8/code/ast/AstStatements.kt +++ b/simpleAst/src/prog8/code/ast/AstStatements.kt @@ -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") } }