removed unreliable inlining of non-asmsub subroutines. Fixes #60

This commit is contained in:
Irmen de Jong 2021-10-08 23:09:38 +02:00
parent 9c4582e283
commit 07132a2c42
9 changed files with 25 additions and 167 deletions

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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))
}
}

View File

@ -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}")
}

View File

@ -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<Pair<IFunctionCall, Node>>()
fun fixCallsToInlinedSubroutines() {
for((call, parent) in callsToInlinedSubroutines) {
val sub = call.target.targetSubroutine(program)!!
val intermediateReturnValueVar = sub.statements.filterIsInstance<VarDecl>().singleOrNull { it.name.endsWith(retvarName) }
if(intermediateReturnValueVar!=null) {
val scope = parent.definingScope()
if(!scope.statements.filterIsInstance<VarDecl>().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<IAstModification> {
return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine)
annotateInlinedSubroutineIdentifiers(subroutine)
else
noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return after(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
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<IAstModification> {
// 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<IAstModification> {
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<IAstModification> {
numReturns++
if(parent !== sub || sub.indexOfChild(returnStmt)<sub.statements.size-1)
errors.err("return statement must be the very last statement in the inlined subroutine", sub.position)
return noModifications
}
fun theModifications(): List<IAstModification> {
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()
}
}

View File

@ -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

View File

@ -552,7 +552,7 @@ Subroutine definitions
The syntax is::
[inline] sub <identifier> ( [parameters] ) [ -> returntype ] {
sub <identifier> ( [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 "<datatype> <parametername>" 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

View File

@ -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()
}
}