allow inlining of subroutines with parameters, and fix inlining of subroutines with variables

This commit is contained in:
Irmen de Jong 2021-04-07 23:28:30 +02:00
parent d116eb7655
commit e5ff61f201
7 changed files with 43 additions and 57 deletions

View File

@ -209,9 +209,6 @@ internal class AstChecker(private val program: Program,
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique")
if(subroutine.inline && !subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
err("can't inline a non-asm subroutine that has parameters")
super.visit(subroutine)
// user-defined subroutines can only have zero or one return type

View File

@ -13,6 +13,7 @@ import prog8.compiler.target.cbm.AssemblyProgram
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.optimizer.CallGraph
import java.io.CharConversionException
import java.nio.file.Path
import java.nio.file.Paths
@ -32,6 +33,7 @@ internal class AsmGen(private val program: Program,
// for expressions and augmented assignments:
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320)
private val callGraph = CallGraph(program)
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
@ -830,9 +832,18 @@ internal class AsmGen(private val program: Program,
private fun translateSubroutine(sub: Subroutine) {
var onlyVariables = false
if(sub.inline) {
if(options.optimize)
return // inline subroutines don't exist anymore on their own
if(options.optimize) {
if(sub.isAsmSubroutine ||callGraph.unused(sub))
return
// from an inlined subroutine only the local variables are generated,
// all other code statements are omitted in the subroutine itself
// (they've been inlined at the call site, remember?)
onlyVariables = true
}
else if(sub.amountOfRtsInAsm()==0) {
// make sure the NOT INLINED subroutine actually does an rts at the end
sub.statements.add(Return(null, Position.DUMMY))
@ -849,7 +860,7 @@ internal class AsmGen(private val program: Program,
// asmsub with most likely just an inline asm in it
out("${sub.name}\t.proc")
sub.statements.forEach{ translate(it) }
sub.statements.forEach { translate(it) }
out(" .pend\n")
} else {
// regular subroutine
@ -873,8 +884,10 @@ internal class AsmGen(private val program: Program,
clc""")
}
out("; statements")
sub.statements.forEach{ translate(it) }
if(!onlyVariables) {
out("; statements")
sub.statements.forEach { translate(it) }
}
for(removal in removals.toList()) {
if(removal.second==sub) {

View File

@ -118,9 +118,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
throw AssemblyError("can't inline a non-asm subroutine with parameters")
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach {
if(it is Return) {
@ -129,6 +127,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.translate(it)
}
}
asmgen.out(" \t; inlined routine end: ${sub.name}")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller

View File

@ -6,10 +6,7 @@ import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Return
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.CompilationOptions
@ -58,34 +55,21 @@ internal class SubroutineInliner(private val program: Program, val errors: IErro
}
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
// this adds full name prefixes to all identifiers used in the subroutine,
// this adds name prefixes to the identifiers used in the subroutine,
// so that the statements can be inlined (=copied) in the call site and still reference
// the correct symbols as seen from the scope of the subroutine.
if(sub.containsDefinedVariables())
errors.warn("inlining a subroutine with variables, this could result in large code/memory size", sub.position)
class Annotator: AstWalker() {
var numReturns=0
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val stmt = identifier.targetStatement(program)!!
val subroutine = identifier.definingSubroutine()
return if(stmt is VarDecl && stmt.parent === subroutine) {
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).replace('.','_')
val withPrefix = IdentifierReference(listOf(prefixed), identifier.position)
listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
} else {
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
val withPrefix = IdentifierReference(prefixed, identifier.position)
listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
}
}
if(stmt is BuiltinFunctionStatementPlaceholder)
return noModifications
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val prefixed = decl.makeScopedName(decl.name).replace('.','_')
val newdecl = VarDecl(decl.type, decl.datatype, decl.zeropage, decl.arraysize, prefixed, decl.struct?.name, decl.value, decl.isArray, decl.autogeneratedDontRemove, decl.position)
return listOf(IAstModification.ReplaceNode(decl, newdecl, parent))
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
val withPrefix = IdentifierReference(prefixed, identifier.position)
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {

View File

@ -178,7 +178,6 @@ interface INameScope {
}
}
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()

View File

@ -2,7 +2,6 @@
TODO
====
- allow inlining of subroutines with params
- optimize several inner loops in gfx2
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))

View File

@ -4,27 +4,22 @@
main {
sub start() {
uword[] uw_arr = [1111,2222,3333]
word[] w_arr = [1111,2222,3333]
ubyte ub = 42
byte bb = -42
ubyte ix = 2
uw_arr[1] = ub
w_arr[1] = bb
txt.print_uw(uw_arr[1])
txt.nl()
txt.print_w(w_arr[1])
txt.nl()
uw_arr[ix] = ub
w_arr[ix] = bb
txt.print_uw(uw_arr[1])
txt.nl()
txt.print_w(w_arr[1])
; cx16.rambank(4)
; cx16.rambank(4)
; cx16.rambank(4)
; cx16.rambank(4)
; cx16.rambank(4)
uword yy = 12345
ubyte xx
xx = calc2(41, 12345)
xx = calc2(41, 12345)
xx = calc2(41, 12345)
xx = calc2(41, 12345)
txt.print_ub(xx) ; must be 99
}
inline sub calc2(ubyte a1, uword a2) -> ubyte {
uword thesum = a2 + a1
return lsb(thesum+a2)
}
}