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,
type: DataType,
position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<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 txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> "<target>"
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint"

View File

@ -1,13 +1,18 @@
package prog8.code.optimize
import prog8.code.StRomSub
import prog8.code.SymbolTable
import prog8.code.ast.*
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)
return
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// 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 {
var parent=node
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)
when(returns.type) {
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 -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)

View File

@ -235,7 +235,7 @@ save_end:
sub read_scanline(uword size) {
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 {
; no MACPTR support
repeat size
@ -297,7 +297,7 @@ save_end:
cx16.r0L = lsb(size)
if msb(size)!=0
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 {
; no MCIOUT support
repeat size

View File

@ -331,7 +331,7 @@ close_end:
readsize = 255
if num_bytes<readsize
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
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
@ -468,7 +468,7 @@ _end rts
if num_bytes!=0 {
reset_write_channel()
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
goto no_mciout
num_bytes -= cx16.r0

View File

@ -8,6 +8,7 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.printProgram
import prog8.ast.statements.Directive
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram
import prog8.code.ast.printAst
@ -134,8 +135,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
optimizeIntermediateAst(intermediateAst, compilationOptions, args.errors)
args.errors.report()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
if(compilationOptions.optimize) {
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if(args.printAst2) {
println("\n*********** INTERMEDIATE AST *************")
@ -143,7 +148,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
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")
return null
}
@ -464,6 +469,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
}
private fun createAssemblyAndAssemble(program: PtProgram,
symbolTable: SymbolTable,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): Boolean {
@ -477,8 +483,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else
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)
errors.report()

View File

@ -579,12 +579,8 @@ internal class AstChecker(private val program: Program,
checkMultiAssignment(assignment, fcall, fcallTarget)
} else if(fcallTarget!=null) {
if(fcallTarget.returntypes.size!=1) {
// If there are 2 return values, one of them being a boolean in a status register, this is okay.
// In that case the normal value is assigned and the status bit is dealth with separately for example with if_cs
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)
}
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
return
}
}
@ -600,7 +596,7 @@ internal class AstChecker(private val program: Program,
}
val targets = assignment.target.multi!!
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
}
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 {
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)
call.add(transformExpression(arg))
return call
@ -279,7 +279,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = srcCall.target.targetNameAndType(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)
call.add(transformExpression(arg))
return call

View File

@ -1,9 +1,9 @@
TODO
====
remove redundant assignments in calls like this where the result registers are the same as the assigment targets:
void, cx16.r0s, cx16.r1s = cx16.mouse_pos()
(6502 + IR)
return cx16.MACPTR(0, 2, true) -> should give compiler error about number of values
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()
}
ubyte key=cbm.GETIN()
ubyte key
void, key=cbm.GETIN()
if key==0 goto waitkey
keypress(key)
@ -252,7 +253,7 @@ waitkey:
ubyte key = 0
while key!=133 {
; 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
if msb(size)==0
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)
sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram
cx16.rombank(orig_rom_bank)

View File

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

View File

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

View File

@ -1,19 +1,25 @@
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
cx16.mouse_config2(1)
wait_mousebutton()
romsub $2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
romsub $3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
romsub $4000 = func3() clobbers(X) -> ubyte @R0
sub wait_mousebutton() {
do {
cx16.r0L, void, void = cx16.mouse_pos()
} until cx16.r0L!=0
do {
cx16.r0L, void, void = cx16.mouse_pos()
} until cx16.r0L==0
}
sub start() {
bool flag
void cbm.GETIN()
flag, cx16.r1L = cbm.GETIN()
void, cx16.r0s, cx16.r1sL = func1()
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)
}
}