mirror of
https://github.com/irmen/prog8.git
synced 2025-02-22 16:29:05 +00:00
inlining subroutines that contain variable declarations is now possible (gives a warning though)
This commit is contained in:
parent
422b390c48
commit
6fdc733941
@ -7,7 +7,7 @@ import prog8.parser.ParsingFailedError
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun isEmpty(): Boolean
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
}
|
||||
|
||||
@ -53,5 +53,5 @@ internal class ErrorReporter: IErrorReporter {
|
||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||
}
|
||||
|
||||
override fun isEmpty() = messages.isEmpty()
|
||||
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
}
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
@ -203,12 +203,8 @@ internal class AstChecker(private val program: Program,
|
||||
if(uniqueNames.size!=subroutine.parameters.size)
|
||||
err("parameter names must be unique")
|
||||
|
||||
if(subroutine.inline) {
|
||||
if (subroutine.containsDefinedVariables())
|
||||
err("can't inline a subroutine that defines variables (might also be a generated intermediate variable for complex return expressions)")
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
|
||||
err("can't inline a non-asm subroutine that has parameters")
|
||||
}
|
||||
if(subroutine.inline && !subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
|
||||
err("can't inline a non-asm subroutine that has parameters")
|
||||
|
||||
super.visit(subroutine)
|
||||
|
||||
|
@ -42,7 +42,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
|
||||
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
@ -59,7 +59,7 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter, options: CompilationOptions) {
|
||||
val process = VariousCleanups(program, errors, options)
|
||||
process.visit(this)
|
||||
if(errors.isEmpty())
|
||||
if(errors.noErrors())
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
errors.err("element type mismatch", assign.position)
|
||||
}
|
||||
|
||||
if(!errors.isEmpty())
|
||||
if(!errors.noErrors())
|
||||
return noModifications
|
||||
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||
|
@ -12,6 +12,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.optimizer.retvarName
|
||||
|
||||
|
||||
internal class VariousCleanups(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
|
||||
@ -62,8 +63,14 @@ internal class VariousCleanups(private val program: Program, val errors: IErrorR
|
||||
|
||||
if(compilerOptions.optimize) {
|
||||
val sub = functionCall.target.targetSubroutine(program)
|
||||
if(sub!=null && sub.inline && !sub.isAsmSubroutine)
|
||||
modifications.addAll(annotateInlinedSubroutineIdentifiers(sub))
|
||||
if(sub!=null && sub.inline && !sub.isAsmSubroutine) {
|
||||
val (annotations, intermediateReturnValueVar) = annotateInlinedSubroutineIdentifiers(sub)
|
||||
modifications.addAll(annotations)
|
||||
if(intermediateReturnValueVar!=null) {
|
||||
val decl=intermediateReturnValueVar.copy()
|
||||
modifications.add(IAstModification.InsertFirst(decl, parent.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||
@ -122,12 +129,15 @@ internal class VariousCleanups(private val program: Program, val errors: IErrorR
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
|
||||
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): Pair<List<IAstModification>, VarDecl?> {
|
||||
// this adds full name prefixes to all 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.
|
||||
|
||||
// TODO warning : "inlining a subroutine with variables, this could result in large code/memory size", identifier.position)
|
||||
if(sub.containsDefinedVariables())
|
||||
errors.warn("inlining a subroutine with variables, this could result in large code/memory size", sub.position)
|
||||
|
||||
var intermediateReturnVar: VarDecl? = null
|
||||
|
||||
class Annotator: AstWalker() {
|
||||
var numReturns=0
|
||||
@ -149,6 +159,8 @@ internal class VariousCleanups(private val program: Program, val errors: IErrorR
|
||||
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)
|
||||
if(decl.name == retvarName)
|
||||
intermediateReturnVar = newdecl
|
||||
return listOf(IAstModification.ReplaceNode(decl, newdecl, parent))
|
||||
}
|
||||
|
||||
@ -168,9 +180,9 @@ internal class VariousCleanups(private val program: Program, val errors: IErrorR
|
||||
sub.accept(annotator, sub.parent)
|
||||
if(annotator.numReturns>1) {
|
||||
errors.err("inlined subroutine can only have one return statement", sub.position)
|
||||
return noModifications
|
||||
return Pair(noModifications, intermediateReturnVar)
|
||||
}
|
||||
return annotator.theModifications()
|
||||
return Pair(annotator.theModifications(), intermediateReturnVar)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -118,8 +118,6 @@ 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.containsDefinedVariables())
|
||||
throw AssemblyError("can't inline sub with vars")
|
||||
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}")
|
||||
|
@ -10,25 +10,25 @@ import java.nio.file.Path
|
||||
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
|
||||
replacer.visit(this)
|
||||
if (errors.isEmpty()) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
@ -36,7 +36,7 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.isEmpty())
|
||||
if(errors.noErrors())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import prog8.compiler.target.ICompilationTarget
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.floor
|
||||
|
||||
private const val retvalName = "prog8_retval"
|
||||
internal const val retvarName = "prog8_retval"
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
@ -47,7 +47,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
for(returnvar in subsThatNeedReturnVariable) {
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvalName, null, null, false, true, returnvar.third)
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null, null, false, true, returnvar.third)
|
||||
returnvar.first.statements.add(0, decl)
|
||||
}
|
||||
subsThatNeedReturnVariable.clear()
|
||||
@ -451,8 +451,8 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvalName), returnStmt.position)
|
||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvalName), returnStmt.position)
|
||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||
|
@ -273,6 +273,8 @@ open class VarDecl(val type: VarDeclType,
|
||||
structHasBeenFlattened = true
|
||||
return result
|
||||
}
|
||||
|
||||
fun copy() = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, value, isArray, autogeneratedDontRemove, position)
|
||||
}
|
||||
|
||||
// a vardecl used only for subroutine parameters
|
||||
|
@ -2,7 +2,8 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- allow inlining of subroutines with vardecls
|
||||
- fix multiple cals to inlined subroutines
|
||||
|
||||
- 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)
|
||||
|
@ -6,7 +6,9 @@ main {
|
||||
|
||||
sub start() {
|
||||
ubyte thing = otherblock.othersub()
|
||||
txt.print_ub(thing) ; should print 21!
|
||||
;ubyte thing2 = otherblock.othersub()
|
||||
txt.print_ub(thing) ; should print 41!
|
||||
;txt.print_ub(thing2) ; should print 41!
|
||||
|
||||
; str filename = "titlescreen.bin"
|
||||
; ubyte success = cx16.vload(filename, 8, 0, $0000)
|
||||
|
Loading…
x
Reference in New Issue
Block a user