refactor fuction arguments codegen a bit

This commit is contained in:
Irmen de Jong 2021-11-27 20:57:24 +01:00
parent 8b4ac7801f
commit c838821615
11 changed files with 121 additions and 57 deletions

View File

@ -5,11 +5,14 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
@ -23,6 +26,9 @@ object C64Target: ICompilationTarget {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> = asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>): Boolean = asmsub6502ArgsHaveRegisterClobberRisk(args)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1

View File

@ -5,7 +5,11 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
@ -23,6 +27,9 @@ object Cx16Target: ICompilationTarget {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> = asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>): Boolean = asmsub6502ArgsHaveRegisterClobberRisk(args)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1

View File

@ -104,6 +104,9 @@ class AsmGen(private val program: Program,
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
internal fun haveFPWR() = compTarget is Cx16Target
internal fun asmsubArgsEvalOrder(sub: Subroutine) = compTarget.asmsubArgsEvalOrder(sub)
internal fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>) = compTarget.asmsubArgsHaveRegisterClobberRisk(args)
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {

View File

@ -0,0 +1,37 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
val order = mutableListOf<Int>()
// order is: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
val (regs, rest) = args2.partition { it.value.second.registerOrPair != null }
cx16regs.forEach { order += it.index }
regs.forEach { order += it.index }
rest.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
if (expr.isSimple && expr !is PrefixExpression)
return false
if (expr is FunctionCall) {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
}
return true
}
return args.size>1 && args.any { isClobberRisk(it) }
}

View File

@ -111,45 +111,16 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
when {
call.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(call.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(call, sub)
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args)) {
registerArgsViaStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
}
}
}

View File

@ -265,7 +265,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
program.reorderStatements(errors)
program.reorderStatements(errors, compilerOptions.compTarget)
errors.report()
program.addTypecasts(errors)
errors.report()

View File

@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
@ -28,8 +29,8 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
internal fun Program.reorderStatements(errors: IErrorReporter, target: ICompilationTarget) {
val reorder = StatementReorderer(this, errors, target)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()

View File

@ -7,10 +7,11 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
internal class StatementReorderer(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
@ -396,9 +397,30 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
if(function.parameters.isEmpty()) {
// 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
} else if(!compTarget.asmsubArgsHaveRegisterClobberRisk(call.args)) {
// no register clobber risk, let the asmgen assign values to registers directly.
return noModifications
} else {
// TODO new logic for passing arguments to asmsub: >0 params -> not sure yet how to do this....
// TODO new logic for passing arguments to asmsub with clobber risk...
val argEvalOrder = compTarget.asmsubArgsEvalOrder(function)
println("ARGS ORDER OF $call: ${argEvalOrder.toList()}")
// function.asmsubArgsEvalOrder().forEach {
// val arg = call.args[it]
// val param = function.parameters[it]
// val paramReg = function.asmParameterRegisters[it]
// when(param.type) {
// DataType.UBYTE -> TODO()
// DataType.BYTE -> TODO()
// DataType.UWORD -> TODO()
// DataType.WORD -> TODO()
// else -> throw FatalAstException("invalidt dt for asmsub param")
// }
// }
//
return noModifications
}
}
}

View File

@ -6,9 +6,10 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.collections.shouldNotBeIn
import io.kotest.matchers.comparables.shouldBeGreaterThan
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
@ -32,6 +33,14 @@ class TestAbstractZeropage: FunSpec({
throw NotImplementedError("dummy")
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
throw NotImplementedError("dummy")
}
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>): Boolean {
throw NotImplementedError("dummy")
}
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}

View File

@ -1,8 +1,14 @@
package prog8.compilerinterface
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<UByte>
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
fun asmsubArgsEvalOrder(sub: Subroutine): List<Int>
fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>): Boolean
}

View File

@ -5,20 +5,22 @@ main {
sub start() {
test_stack.test()
sys.push(-22 as ubyte)
sys.push(44)
sys.pushw(-11234 as uword)
sys.pushw(12345)
sys.push(1)
sys.push(2)
ubyte @shared ub = sys.pop()
byte @shared bb = sys.pop() as byte
uword @shared uw = sys.popw()
word @shared ww = sys.popw() as word
void sys.pop()
void sys.pop()
uword @shared uw
; routine2(uw+1, true)
; sys.push(-22 as ubyte)
; sys.push(44)
; sys.pushw(-11234 as uword)
; sys.pushw(12345)
; sys.push(1)
; sys.push(2)
; ubyte @shared ub = sys.pop()
; byte @shared bb = sys.pop() as byte
; uw = sys.popw()
; word @shared ww = sys.popw() as word
; void sys.pop()
; void sys.pop()
routine2(uw, 11,22, true, 33)
test_stack.test()
@ -27,7 +29,7 @@ main {
}
asmsub routine2(uword num @AY, ubyte switch @X) {
asmsub routine2(uword num @AY, ubyte a1 @R1, ubyte a2 @R2, ubyte switch @Pc, ubyte a3 @X) {
%asm {{
adc #20
rts