diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index b481d58e9..37c77c457 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -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 diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 149fd2af2..0c5d16c33 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -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() private val globalFloatConsts = mutableMapOf() // 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) { diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt index 3c863150a..2c0ff4a05 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt @@ -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 diff --git a/compiler/src/prog8/optimizer/SubroutineInliner.kt b/compiler/src/prog8/optimizer/SubroutineInliner.kt index 2e975cf3f..8f45ead9a 100644 --- a/compiler/src/prog8/optimizer/SubroutineInliner.kt +++ b/compiler/src/prog8/optimizer/SubroutineInliner.kt @@ -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 { - // 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 { 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 { - 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 { diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index 1e3c2d215..df095347c 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -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() diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 80b226d18..93dd5395a 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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)) diff --git a/examples/test.p8 b/examples/test.p8 index ee68b8823..1d50e27be 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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) } }