mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
removed unreliable inlining of non-asmsub subroutines. Fixes #60
This commit is contained in:
parent
9c4582e283
commit
07132a2c42
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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}")
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user