mirror of
https://github.com/irmen/prog8.git
synced 2025-02-18 05:30:34 +00:00
IR support for multi-value returns in normal subroutines, documentation.
This commit is contained in:
parent
a6f9ed07e7
commit
66558f7638
@ -70,6 +70,7 @@ What does Prog8 provide?
|
||||
- ``defer`` statement to help write concise and robust subroutine cleanup logic
|
||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- subroutines can return more than one result value
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
|
||||
- encode strings and characters into petscii or screencodes or even other encodings
|
||||
|
@ -30,7 +30,7 @@ internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, D
|
||||
return listOf(Pair(register, returntype))
|
||||
}
|
||||
else -> {
|
||||
// TODO for multi-value results, put the first one in register(s) and only the rest elsewhere (like stack)???
|
||||
// TODO for multi-value results, put the first one in register(s) and only the rest in the virtual registers?
|
||||
throw AssemblyError("multi-value returns from a normal subroutine are not put into registers, this routine shouldn't have been called in this scenario")
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val funcCall = expressionEval.translate(values)
|
||||
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
|
||||
if (funcCall.multipleResultFpRegs.isNotEmpty())
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
val assignmentTargets = assignment.children.dropLast(1)
|
||||
addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg)
|
||||
|
||||
val extsub = codeGen.symbolTable.lookup(values.name) as? StExtSub
|
||||
if(extsub!=null) {
|
||||
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
|
||||
if (funcCall.multipleResultFpRegs.isNotEmpty())
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
if (extsub.returns.size == assignmentTargets.size) {
|
||||
// Targets and values match. Assign all the things. Skip 'void' targets.
|
||||
extsub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
|
||||
@ -41,7 +41,17 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
} else {
|
||||
val normalsub = codeGen.symbolTable.lookup(values.name) as? StSub
|
||||
if (normalsub != null) {
|
||||
TODO()
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
normalsub.returns.zip(assignmentTargets).zip(registersReverseOrder).forEach {
|
||||
val target = it.first.second as PtAssignTarget
|
||||
if(!target.void) {
|
||||
val assignSingle = PtAssignment(assignment.position)
|
||||
assignSingle.add(target)
|
||||
assignSingle.add(PtIdentifier("cx16.${it.second.toString().lowercase()}", it.first.first, assignment.position))
|
||||
result += translateRegularAssign(assignSingle)
|
||||
}
|
||||
}
|
||||
}
|
||||
else throw AssemblyError("expected extsub or normal sub")
|
||||
}
|
||||
|
@ -645,7 +645,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegSpecs)), null)
|
||||
return if(fcall.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) // TODO void?
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) // TODO datatype void?
|
||||
else if(returnRegSpecs.size==1) {
|
||||
val returnRegSpec = returnRegSpecs.single()
|
||||
if (fcall.type.isFloat)
|
||||
@ -653,7 +653,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1)
|
||||
} else {
|
||||
TODO("multi-value return ; expression result")
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
// so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
}
|
||||
is StExtSub -> {
|
||||
|
@ -1759,8 +1759,19 @@ class IRCodeGen(
|
||||
private fun translate(ret: PtReturn): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(ret.children.size>1) {
|
||||
TODO("multi-value return")
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
for ((value, register) in ret.children.zip(registersReverseOrder)) {
|
||||
val tr = expressionEval.translateExpression(value as PtExpression)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, tr.dt, reg1=tr.resultReg, labelSymbol = "cx16.${register.toString().lowercase()}")
|
||||
it += IRInstruction(Opcode.RETURN)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
val value = ret.children.singleOrNull()
|
||||
if(value==null) {
|
||||
addInstr(result, IRInstruction(Opcode.RETURN), null)
|
||||
|
@ -12,6 +12,7 @@ import prog8.ast.statements.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.astprocessing.hasRtsInAsm
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
@ -309,6 +310,6 @@ main {
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, src, writeAssembly = true).shouldNotBeNull()
|
||||
// compileText(VMTarget(), false, src, writeAssembly = true).shouldNotBeNull() TODO("multi-value return ; unittest")
|
||||
compileText(VMTarget(), false, src, writeAssembly = true).shouldNotBeNull()
|
||||
}
|
||||
})
|
||||
|
@ -66,7 +66,7 @@ Subroutines
|
||||
- There is no call stack for subroutine arguments: subroutine parameters are overwritten when called again. Thus recursion is not easily possible, but you can do it with manual stack manipulations.
|
||||
There are a couple of example programs that show how to solve this in different ways, among which are fractal-tree.p8, maze.p8 and queens.p8
|
||||
- There is no function overloading (except for a couple of builtin functions).
|
||||
- Some subroutine types can return multiple return values, and you can multi-assign those in a single statement.
|
||||
- Subroutines can return multiple return values, and you can multi-assign those in a single statement.
|
||||
- Because every declared variable allocates some memory, it might be beneficial to share the same variables over different subroutines
|
||||
instead of defining the same sort of variables in every subroutine.
|
||||
This reduces the memory needed for variables. A convenient way to do this is by using nested subroutines - these can easily access the
|
||||
|
@ -107,6 +107,7 @@ Features
|
||||
- Encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
|
||||
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
|
||||
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
|
||||
- Subroutines can return more than one result value
|
||||
- Advanced code optimizations to make the resulting program smaller and faster
|
||||
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
|
||||
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. You can look at them as general purpose global variables. These are also available on the other compilation targets!
|
||||
|
@ -778,8 +778,8 @@ for normal assignments (``aa = aa + xx``).
|
||||
It is possible to "chain" assignments: ``x = y = z = 42``, this is just a shorthand
|
||||
for the three individual assignments with the same value 42.
|
||||
|
||||
Only for certain subroutines that return multiple values it is possible to write a "multi assign" statement
|
||||
with comma separated assignment targets, that assigns those multiple values to different targets in one statement.
|
||||
For subroutines that return multiple values, you should write a "multi assign" statement
|
||||
with comma separated assignment targets, to assigns those multiple values.
|
||||
Details can be found here: :ref:`multiassign`.
|
||||
|
||||
|
||||
@ -1130,10 +1130,11 @@ Otherwise the compiler will warn you about discarding the result of the call.
|
||||
|
||||
Multiple return values
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normal subroutines can only return zero or one return values.
|
||||
However, the special ``asmsub`` routines (implemented in assembly code) or ``extsub`` routines
|
||||
(referencing an external routine in ROM or elsewhere in RAM) can return more than one return value.
|
||||
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers and some more values in R0 and R1.
|
||||
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.
|
||||
@ -1147,13 +1148,19 @@ So for instance::
|
||||
|
||||
asmsub multisub() -> uword @AY, bool @Pc, ubyte @X { ... }
|
||||
|
||||
.. sidebar:: Using just one of the values
|
||||
.. sidebar:: usage of cx16.r0-cx16.r15
|
||||
|
||||
Sometimes it is easier to just have a single return value in the subroutine's signagure (even though it
|
||||
actually may return multiple values): this avoids having to put ``void`` for all other values.
|
||||
It also allows it to be called in expressions such as if-statements again.
|
||||
Examples of these second 'convenience' definition are library routines such as ``cbm.STOP2`` and ``cbm.GETIN2``,
|
||||
that only return a single value where the "official" versions ``STOP`` and ``GETIN`` always return multiple values.
|
||||
Subroutines with multiple return values use the "virtual registers" to return those.
|
||||
Using those virtual registers during the calculation of the values in the return statement should be avoided.
|
||||
Otherwise you risk overwriting an earlier return value in the sequence.
|
||||
|
||||
|
||||
**Using just one of the values:**
|
||||
Sometimes it is easier to just have a single return value in a subroutine's signagure (even though it
|
||||
actually may return multiple values): this avoids having to put ``void`` for all other values if you aren't really interested in those.
|
||||
It also allows it to be called in expressions such as if-statements again.
|
||||
Examples of these second 'convenience' definition are library routines such as ``cbm.STOP2`` and ``cbm.GETIN2``,
|
||||
that only return a single value where the "official" versions ``STOP`` and ``GETIN`` always return multiple values.
|
||||
|
||||
**Skipping values:** Instead of using ``void`` to ignore the result of a subroutine call altogether,
|
||||
you can also use it as a placeholder name in a multi-assignment. This skips assignment of the return value in that place.
|
||||
|
@ -151,6 +151,14 @@ Regular subroutines
|
||||
- A word 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'.
|
||||
|
||||
- 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 use the "virtual registers" cx16.r0-cx16.r15, from r15 down to r0, for the
|
||||
result values left to right. This may change in a future compiler version.
|
||||
|
||||
|
||||
**Builtin functions can be different:**
|
||||
some builtin functions are special and won't exactly follow these rules.
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- implement IR support for the TODO("multi-value occurences in both codegens, to handle multi-value subroutine return values. Fix the unittest too.
|
||||
- document new multi-value return feature (only bool/byte/word types supported, call convention)
|
||||
|
||||
- change library routines that now return 1 value + say, another in R0, to just return 2 values now that this is supported for normal subroutines too.
|
||||
- rename "intermediate AST" into "simplified AST" (docs + classes in code)
|
||||
|
||||
- add paypal donation button as well?
|
||||
@ -29,7 +27,6 @@ Future Things and Ideas
|
||||
Maybe propose a patch to 64tass itself that will treat .proc as .block ?
|
||||
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
|
||||
|
||||
- Allow normal subroutines to return multiple values as well (just as asmsubs already can)
|
||||
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first)
|
||||
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
|
||||
- Improve register load order in subroutine call args assignments:
|
||||
@ -82,6 +79,7 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- Multi-value returns of normal subroutines: use cpu register A or AY for the first one and only start using virtual registers for the rest.
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
|
||||
|
Loading…
x
Reference in New Issue
Block a user