diff --git a/compiler/res/prog8lib/cx16/palette.p8 b/compiler/res/prog8lib/cx16/palette.p8 index 669f028a7..b2a1a5dd9 100644 --- a/compiler/res/prog8lib/cx16/palette.p8 +++ b/compiler/res/prog8lib/cx16/palette.p8 @@ -68,11 +68,11 @@ palette { } } - inline sub set_all_black() { + sub set_all_black() { set_monochrome($000, $000) } - inline sub set_all_white() { + sub set_all_white() { set_monochrome($fff, $fff) } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index c256f4850..bc34795d5 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -297,17 +297,6 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: break } - val inliner = SubroutineInliner(programAst, errors, options) - inliner.visit(programAst) - errors.report() - if(errors.noErrors()) { - inliner.applyModifications() - inliner.fixCallsToInlinedSubroutines() - val remover2 = UnusedCodeRemover(programAst, errors, compTarget) - remover2.visit(programAst) - remover2.applyModifications() - } - errors.report() } diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 07259b4aa..fd5a88cc1 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -269,10 +269,11 @@ internal class AstChecker(private val program: Program, } } - // scope check - if(subroutine.parent !is Block && subroutine.parent !is Subroutine) { + if(subroutine.inline && !subroutine.isAsmSubroutine) + err("subroutine inlining is currently only supported on asmsub routines") + + if(subroutine.parent !is Block && subroutine.parent !is Subroutine) err("subroutines can only be defined in the scope of a block or within another subroutine") - } if(subroutine.isAsmSubroutine) { if(subroutine.asmParameterRegisters.size != subroutine.parameters.size) diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 212911571..8ae826bdf 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -864,7 +864,7 @@ internal class AsmGen(private val program: Program, if(sub.inline) { if(options.optimize) { - if(sub.isAsmSubroutine ||callGraph.unused(sub)) + if(sub.isAsmSubroutine || callGraph.unused(sub)) return // from an inlined subroutine only the local variables are generated, @@ -873,7 +873,7 @@ internal class AsmGen(private val program: Program, onlyVariables = true } else if(sub.amountOfRtsInAsm()==0) { - // make sure the NOT INLINED subroutine actually does an rts at the end + // make sure the NOT INLINED subroutine actually does a rts at the end sub.statements.add(Return(null, Position.DUMMY)) } } diff --git a/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt b/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt index f44866ef1..05fe27299 100644 --- a/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt +++ b/compiler/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt @@ -118,16 +118,14 @@ 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) + + // note: for now, this is only reliably supported for asmsubs. + if(!sub.isAsmSubroutine) + throw AssemblyError("can only reliably inline asmsub routines at this time") + 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) { - asmgen.translate(it, false) // don't use RTS for the inlined return statement - } else { - if(!sub.inline || it !is VarDecl) - asmgen.translate(it) - } - } + val assembly = sub.statements.single() as InlineAssembly + asmgen.translate(assembly) asmgen.out(" \t; inlined routine end: ${sub.name}") } diff --git a/compiler/src/prog8/optimizer/SubroutineInliner.kt b/compiler/src/prog8/optimizer/SubroutineInliner.kt deleted file mode 100644 index 8f45ead9a..000000000 --- a/compiler/src/prog8/optimizer/SubroutineInliner.kt +++ /dev/null @@ -1,96 +0,0 @@ -package prog8.optimizer - -import prog8.ast.IFunctionCall -import prog8.ast.Node -import prog8.ast.Program -import prog8.ast.base.Position -import prog8.ast.expressions.FunctionCall -import prog8.ast.expressions.IdentifierReference -import prog8.ast.statements.* -import prog8.ast.walk.AstWalker -import prog8.ast.walk.IAstModification -import prog8.compiler.CompilationOptions -import prog8.compiler.IErrorReporter - - -internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() { - private var callsToInlinedSubroutines = mutableListOf>() - - fun fixCallsToInlinedSubroutines() { - for((call, parent) in callsToInlinedSubroutines) { - val sub = call.target.targetSubroutine(program)!! - val intermediateReturnValueVar = sub.statements.filterIsInstance().singleOrNull { it.name.endsWith(retvarName) } - if(intermediateReturnValueVar!=null) { - val scope = parent.definingScope() - if(!scope.statements.filterIsInstance().any { it.name==intermediateReturnValueVar.name}) { - val decl = intermediateReturnValueVar.copy() - scope.statements.add(0, decl) - decl.linkParents(scope as Node) - } - } - } - } - - override fun after(subroutine: Subroutine, parent: Node): Iterable { - return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine) - annotateInlinedSubroutineIdentifiers(subroutine) - else - noModifications - } - - override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable { - return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position) - } - - override fun after(functionCall: FunctionCall, parent: Node): Iterable { - return after(functionCall as IFunctionCall, parent, functionCall.position) - } - - private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable { - val sub = functionCall.target.targetSubroutine(program) - if(sub != null && compilerOptions.optimize && sub.inline && !sub.isAsmSubroutine) - callsToInlinedSubroutines.add(Pair(functionCall, parent)) - - return noModifications - } - - private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List { - // 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. - - class Annotator: AstWalker() { - var numReturns=0 - - override fun before(identifier: IdentifierReference, parent: Node): Iterable { - val stmt = identifier.targetStatement(program)!! - if(stmt is BuiltinFunctionStatementPlaceholder) - return noModifications - - 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 { - numReturns++ - if(parent !== sub || sub.indexOfChild(returnStmt) { - return this.modifications.map { it.first }.toList() - } - } - - val annotator = Annotator() - sub.accept(annotator, sub.parent) - if(annotator.numReturns>1) { - errors.err("inlined subroutine can only have one return statement", sub.position) - return noModifications - } - return annotator.theModifications() - } - -} diff --git a/docs/source/programming.rst b/docs/source/programming.rst index ad6634869..153789229 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -641,9 +641,13 @@ Subroutines can be defined in a Block, but also nested inside another subroutine With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters in registers directly. -Trivial subroutines can be tagged as ``inline`` to tell the compiler to copy their code +Trivial ``asmsub`` routines can be tagged as ``inline`` to tell the compiler to copy their code in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the subroutine. This may increase code size significantly and can only be used in limited scenarios, so YMMV. +Note that the routine's code is copied verbatim into the place of the subroutine call in this case, +so pay attention to any jumps and rts instructions in the inlined code! + +At this time it is not yet possible to inline regular Prog8 subroutines, this may be added in the future. Calling a subroutine diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index e97adb971..8db69295c 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -552,7 +552,7 @@ Subroutine definitions The syntax is:: - [inline] sub ( [parameters] ) [ -> returntype ] { + sub ( [parameters] ) [ -> returntype ] { ... statements ... } @@ -565,9 +565,6 @@ The open curly brace must immediately follow the subroutine result specification and can have nothing following it. The close curly brace must be on its own line as well. The parameters is a (possibly empty) comma separated list of " " pairs specifying the input parameters. The return type has to be specified if the subroutine returns a value. -The ``inline`` keyword makes their code copied in-place to the locations where the subroutine is called, -rather than having an actual call and return to the subroutine. This is meant for very small subroutines only -as it can increase code size significantly. Assembly / ROM subroutines diff --git a/examples/test.p8 b/examples/test.p8 index 1bce15bdb..75adb566b 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,47 +1,12 @@ -%import textio -%import floats +%import palette %import test_stack %zeropage basicsafe main { -label: sub start() { - - ubyte ub1 - ubyte ub2 - uword uw1 - uword uw2 - float fl1 - float fl2 - ubyte[10] ubarr - uword[10] uwarr - float[10] flarr - - swap(ub1, ub2) - swap(uw1, uw2) - swap(fl1, fl2) - swap(ubarr[1], ubarr[2]) - swap(uwarr[1], uwarr[2]) - swap(flarr[1], flarr[2]) - - ubyte ix1 - ubyte ix2 - - swap(ubarr[ix1], ubarr[ix2]) - swap(uwarr[ix1], uwarr[ix2]) - swap(flarr[ix1], flarr[ix2]) - swap(flarr[ix1], flarr[2]) - swap(flarr[2], flarr[ix2]) - - swap(ubarr[ix1], ubarr[ix1+2]) - swap(uwarr[ix1], uwarr[ix1+2]) - swap(flarr[ix1], flarr[ix1+2]) - - uword ptr = $c000 - swap(@(ptr+1), @(ptr+2)) - swap(@(ptr+ub1), @(ptr+ub2)) - - test_stack.test() + ; TODO inline a subroutine that only contains a direct call to another subroutine + palette.set_all_black() + palette.set_all_white() } }