better checking for number of return values

assignment optimization if return register already is the same as the assignment target
This commit is contained in:
Irmen de Jong 2024-04-04 23:47:33 +02:00
parent 5f11f485a2
commit 98acff802f
15 changed files with 134 additions and 48 deletions

View File

@ -210,11 +210,6 @@ class PtFunctionCall(val name: String,
val void: Boolean, val void: Boolean,
type: DataType, type: DataType,
position: Position) : PtExpression(type, position) { position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<PtExpression> val args: List<PtExpression>
get() = children.map { it as PtExpression } get() = children.map { it as PtExpression }
} }

View File

@ -10,7 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
fun type(dt: DataType) = "!${dt.name.lowercase()}!" fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String { fun txt(node: PtNode): String {
return when(node) { return when(node) {
is PtAssignTarget -> "<target>" is PtAssignTarget -> if(node.void) "<void>" else "<target>"
is PtAssignment -> "<assign>" is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}" is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint" is PtBreakpoint -> "%breakpoint"

View File

@ -1,13 +1,18 @@
package prog8.code.optimize package prog8.code.optimize
import prog8.code.StRomSub
import prog8.code.SymbolTable
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.* import prog8.code.core.*
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) { fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
if (!options.optimize) if (!options.optimize)
return return
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) { while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling // keep rolling
} }
} }
@ -109,6 +114,79 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
} }
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
var changes = 0
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtAssignment) {
val value = node.value
val functionName = when(value) {
is PtBuiltinFunctionCall -> value.name
is PtFunctionCall -> value.name
else -> null
}
if(functionName!=null) {
val stNode = st.lookup(functionName)
if (stNode is StRomSub) {
require(node.children.size==stNode.returns.size+1) {
"number of targets must match return values"
}
node.children.zip(stNode.returns).withIndex().forEach { (index, xx) ->
val target = xx.first as PtAssignTarget
val returnedRegister = xx.second.register.registerOrPair
if(returnedRegister!=null && !target.void && target.identifier!=null) {
if(isSame(target.identifier!!, xx.second.type, returnedRegister)) {
// output register is already identical to target register, so it can become void
val voidTarget = PtAssignTarget(true, target.position)
node.children[index] = voidTarget
voidTarget.parent = node
changes++
}
}
}
}
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
val index = node.parent.children.indexOf(node)
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
value.children.forEach { voidCall.add(it) }
node.parent.children[index] = voidCall
voidCall.parent = node.parent
changes++
}
}
}
true
}
return changes
}
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
if(returnedRegister in Cx16VirtualRegisters) {
val regname = returnedRegister.name.lowercase()
val identifierRegName = identifier.name.substringAfterLast('.')
/*
cx16.r? UWORD
cx16.r?s WORD
cx16.r?L UBYTE
cx16.r?H UBYTE
cx16.r?sL BYTE
cx16.r?sH BYTE
*/
if(identifier.type in ByteDatatypes && type in ByteDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "L", "sL") // note: not the -H (msb) variants!
}
}
else if(identifier.type in WordDatatypes && type in WordDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "s")
}
}
}
return false // there are no identifiers directly corresponding to cpu registers
}
internal fun findScopeName(node: PtNode): String { internal fun findScopeName(node: PtNode): String {
var parent=node var parent=node
while(parent !is PtNamedNode) while(parent !is PtNamedNode)

View File

@ -112,7 +112,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen) val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) { when(returns.type) {
in ByteDatatypesWithBoolean -> { in ByteDatatypesWithBoolean -> {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false) if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
} }
in WordDatatypes -> { in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!) assignRegisterpairWord(tgt, returns.register.registerOrPair!!)

View File

@ -235,7 +235,7 @@ save_end:
sub read_scanline(uword size) { sub read_scanline(uword size) {
while size!=0 { while size!=0 {
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true) void, cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
if_cs { if_cs {
; no MACPTR support ; no MACPTR support
repeat size repeat size
@ -297,7 +297,7 @@ save_end:
cx16.r0L = lsb(size) cx16.r0L = lsb(size)
if msb(size)!=0 if msb(size)!=0
cx16.r0L = 0 ; 256 bytes cx16.r0L = 0 ; 256 bytes
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true) void, cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
if_cs { if_cs {
; no MCIOUT support ; no MCIOUT support
repeat size repeat size

View File

@ -331,7 +331,7 @@ close_end:
readsize = 255 readsize = 255
if num_bytes<readsize if num_bytes<readsize
readsize = num_bytes readsize = num_bytes
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads void, readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
if_cs if_cs
goto byte_read_loop ; MACPTR block read not supported, do fallback loop goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize list_blocks += readsize
@ -468,7 +468,7 @@ _end rts
if num_bytes!=0 { if num_bytes!=0 {
reset_write_channel() reset_write_channel()
do { do {
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes void, cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
if_cs if_cs
goto no_mciout goto no_mciout
num_bytes -= cx16.r0 num_bytes -= cx16.r0

View File

@ -8,6 +8,7 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.NumericLiteral
import prog8.ast.printProgram import prog8.ast.printProgram
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram import prog8.code.ast.PtProgram
import prog8.code.ast.printAst import prog8.code.ast.printAst
@ -134,8 +135,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
} }
val intermediateAst = IntermediateAstMaker(program, args.errors).transform() val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
optimizeIntermediateAst(intermediateAst, compilationOptions, args.errors) val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
args.errors.report() val symbolTable = stMaker.make()
if(compilationOptions.optimize) {
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if(args.printAst2) { if(args.printAst2) {
println("\n*********** INTERMEDIATE AST *************") println("\n*********** INTERMEDIATE AST *************")
@ -143,7 +148,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
println("*********** INTERMEDIATE AST END *************\n") println("*********** INTERMEDIATE AST END *************\n")
} }
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) { if(!createAssemblyAndAssemble(intermediateAst, symbolTable, args.errors, compilationOptions)) {
System.err.println("Error in codegeneration or assembler") System.err.println("Error in codegeneration or assembler")
return null return null
} }
@ -464,6 +469,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
} }
private fun createAssemblyAndAssemble(program: PtProgram, private fun createAssemblyAndAssemble(program: PtProgram,
symbolTable: SymbolTable,
errors: IErrorReporter, errors: IErrorReporter,
compilerOptions: CompilationOptions compilerOptions: CompilationOptions
): Boolean { ): Boolean {
@ -477,8 +483,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else else
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}") throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors) val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report() errors.report()

View File

@ -579,12 +579,8 @@ internal class AstChecker(private val program: Program,
checkMultiAssignment(assignment, fcall, fcallTarget) checkMultiAssignment(assignment, fcall, fcallTarget)
} else if(fcallTarget!=null) { } else if(fcallTarget!=null) {
if(fcallTarget.returntypes.size!=1) { if(fcallTarget.returntypes.size!=1) {
// If there are 2 return values, one of them being a boolean in a status register, this is okay. errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
// In that case the normal value is assigned and the status bit is dealth with separately for example with if_cs return
val (returnRegisters, _) = fcallTarget.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if(returnRegisters.size>1) {
errors.err("multiple return values and too few assignment targets, need at least ${returnRegisters.size}", fcall.position)
}
} }
} }
@ -600,7 +596,7 @@ internal class AstChecker(private val program: Program,
} }
val targets = assignment.target.multi!! val targets = assignment.target.multi!!
if(fcallTarget.returntypes.size!=targets.size) { if(fcallTarget.returntypes.size!=targets.size) {
errors.err("number of assignment targets doesn't match number of return values", fcall.position) errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
return return
} }
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) -> fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->

View File

@ -270,7 +270,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall { private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
val (target, type) = srcCall.target.targetNameAndType(program) val (target, type) = srcCall.target.targetNameAndType(program)
val call = PtFunctionCall(target,true, type, srcCall.position) val call = PtFunctionCall(target, true, type, srcCall.position)
for (arg in srcCall.args) for (arg in srcCall.args)
call.add(transformExpression(arg)) call.add(transformExpression(arg))
return call return call
@ -279,7 +279,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall { private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = srcCall.target.targetNameAndType(program) val (target, _) = srcCall.target.targetNameAndType(program)
val iType = srcCall.inferType(program) val iType = srcCall.inferType(program)
val call = PtFunctionCall(target, iType.isUnknown, iType.getOrElse { DataType.UNDEFINED }, srcCall.position) val call = PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
for (arg in srcCall.args) for (arg in srcCall.args)
call.add(transformExpression(arg)) call.add(transformExpression(arg))
return call return call

View File

@ -1,9 +1,9 @@
TODO TODO
==== ====
remove redundant assignments in calls like this where the result registers are the same as the assigment targets: return cx16.MACPTR(0, 2, true) -> should give compiler error about number of values
void, cx16.r0s, cx16.r1s = cx16.mouse_pos()
(6502 + IR) check docs on assign about status register in assignment (can no longer be ignored, use void to not assign it)
... ...

View File

@ -73,7 +73,8 @@ waitkey:
; test_stack.test() ; test_stack.test()
} }
ubyte key=cbm.GETIN() ubyte key
void, key=cbm.GETIN()
if key==0 goto waitkey if key==0 goto waitkey
keypress(key) keypress(key)
@ -252,7 +253,7 @@ waitkey:
ubyte key = 0 ubyte key = 0
while key!=133 { while key!=133 {
; endless loop until user presses F1 to restart the game ; endless loop until user presses F1 to restart the game
key = cbm.GETIN() void, key = cbm.GETIN()
} }
} }

View File

@ -182,7 +182,7 @@ processchunk_call jsr $ffff ; modified
ubyte readsize = 255 ubyte readsize = 255
if msb(size)==0 if msb(size)==0
readsize = lsb(size) readsize = lsb(size)
cx16.r2 = cx16.MACPTR(readsize, bonkbuffer, false) ; can't MACPTR directly to bonk ram void, cx16.r2 = cx16.MACPTR(readsize, bonkbuffer, false) ; can't MACPTR directly to bonk ram
cx16.rombank(bonk) cx16.rombank(bonk)
sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram
cx16.rombank(orig_rom_bank) cx16.rombank(orig_rom_bank)

View File

@ -95,7 +95,8 @@ waitkey:
; test_stack.test() ; test_stack.test()
} }
ubyte key=cbm.GETIN() ubyte key
void, key=cbm.GETIN()
keypress(key) keypress(key)
cx16.r0,void = cx16.joystick_get(1) cx16.r0,void = cx16.joystick_get(1)
joystick(cx16.r0) joystick(cx16.r0)
@ -338,7 +339,7 @@ waitkey:
cx16.r0, void = cx16.joystick_get(1) cx16.r0, void = cx16.joystick_get(1)
if cx16.r0 & %0000000000010000 == 0 if cx16.r0 & %0000000000010000 == 0
break break
key = cbm.GETIN() void, key = cbm.GETIN()
} until key==133 } until key==133
} }

View File

@ -66,7 +66,8 @@ main {
vtui.print_str2("arrow keys to move!", $61, true) vtui.print_str2("arrow keys to move!", $61, true)
char_loop: char_loop:
ubyte char = cbm.GETIN() ubyte char
void, char = cbm.GETIN()
if char==0 if char==0
goto char_loop goto char_loop

View File

@ -1,19 +1,25 @@
%import textio
%zeropage basicsafe %zeropage basicsafe
%option no_sysinit %option no_sysinit
main { main {
sub start() { romsub $2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
cx16.mouse_config2(1) romsub $3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
wait_mousebutton() romsub $4000 = func3() clobbers(X) -> ubyte @R0
sub wait_mousebutton() { sub start() {
do { bool flag
cx16.r0L, void, void = cx16.mouse_pos() void cbm.GETIN()
} until cx16.r0L!=0 flag, cx16.r1L = cbm.GETIN()
do {
cx16.r0L, void, void = cx16.mouse_pos() void, cx16.r0s, cx16.r1sL = func1()
} until cx16.r0L==0 void, cx16.r2, cx16.r1 = func2()
} cx16.r0L = func3()
cx16.r0H = func3()
cx16.r0 = readblock()
}
sub readblock() -> uword {
return cx16.MACPTR(0, 2, true) ; TODO compiler error (number of return values)
} }
} }