mirror of
https://github.com/irmen/prog8.git
synced 2025-08-14 22:27:48 +00:00
improvements for inlined subroutines: fix identifier scoping
This commit is contained in:
@@ -253,7 +253,8 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
|
|||||||
errors.report()
|
errors.report()
|
||||||
programAst.addTypecasts(errors)
|
programAst.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups()
|
programAst.variousCleanups(programAst, errors, compilerOptions)
|
||||||
|
errors.report()
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||||
@@ -283,7 +284,7 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
|
|||||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
programAst.addTypecasts(errors)
|
programAst.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups()
|
programAst.variousCleanups(programAst, errors, compilerOptions)
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
|
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
|
||||||
|
@@ -56,12 +56,14 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.variousCleanups() {
|
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter, options: CompilationOptions) {
|
||||||
val process = VariousCleanups()
|
val process = VariousCleanups(program, errors, options)
|
||||||
process.visit(this)
|
process.visit(this)
|
||||||
process.applyModifications()
|
if(errors.isEmpty())
|
||||||
|
process.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.moveMainAndStartToFirst() {
|
internal fun Program.moveMainAndStartToFirst() {
|
||||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||||
// the "main" block containing the entrypoint is moved to the top in there,
|
// the "main" block containing the entrypoint is moved to the top in there,
|
||||||
|
@@ -3,17 +3,17 @@ package prog8.compiler.astprocessing
|
|||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.DirectMemoryRead
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.expressions.FunctionCall
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.TypecastExpression
|
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
internal class VariousCleanups: AstWalker() {
|
internal class VariousCleanups(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
@@ -56,6 +56,14 @@ internal class VariousCleanups: AstWalker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||||
|
|
||||||
|
if(compilerOptions.optimize) {
|
||||||
|
val sub = functionCall.target.targetSubroutine(program)
|
||||||
|
if(sub!=null && sub.inline && !sub.isAsmSubroutine)
|
||||||
|
annotateInlinedSubroutineIdentifiers(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(functionCall.target.nameInSource==listOf("peek")) {
|
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||||
// peek(a) is synonymous with @(a)
|
// peek(a) is synonymous with @(a)
|
||||||
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
||||||
@@ -70,4 +78,35 @@ internal class VariousCleanups: AstWalker() {
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine) {
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
class Annotator: AstWalker() {
|
||||||
|
var numReturns=0
|
||||||
|
|
||||||
|
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||||
|
val stmt = identifier.targetStatement(program)!!
|
||||||
|
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
|
||||||
|
val withPrefix = IdentifierReference(prefixed, identifier.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, identifier.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 emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotator = Annotator()
|
||||||
|
sub.accept(annotator, sub.parent)
|
||||||
|
if(annotator.numReturns>1)
|
||||||
|
errors.err("inlined subroutine can only have one return statement", sub.position)
|
||||||
|
else
|
||||||
|
annotator.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -111,7 +111,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sub.inline && asmgen.options.optimize) {
|
if(!sub.inline || !asmgen.options.optimize) {
|
||||||
|
asmgen.out(" jsr $subName")
|
||||||
|
} else {
|
||||||
|
// inline the subroutine
|
||||||
if(sub.containsDefinedVariables())
|
if(sub.containsDefinedVariables())
|
||||||
throw AssemblyError("can't inline sub with vars")
|
throw AssemblyError("can't inline sub with vars")
|
||||||
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
||||||
@@ -120,9 +123,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||||
statements.forEach { asmgen.translate(it) }
|
statements.forEach { asmgen.translate(it) }
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
asmgen.out(" jsr $subName")
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||||
}
|
}
|
||||||
|
@@ -137,8 +137,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
output("\n")
|
output("\n")
|
||||||
|
outputi("")
|
||||||
|
if(subroutine.inline)
|
||||||
|
output("inline ")
|
||||||
if(subroutine.isAsmSubroutine) {
|
if(subroutine.isAsmSubroutine) {
|
||||||
outputi("asmsub ${subroutine.name} (")
|
output("asmsub ${subroutine.name} (")
|
||||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||||
val reg =
|
val reg =
|
||||||
when {
|
when {
|
||||||
@@ -152,7 +155,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
outputi("sub ${subroutine.name} (")
|
output("sub ${subroutine.name} (")
|
||||||
for(param in subroutine.parameters) {
|
for(param in subroutine.parameters) {
|
||||||
output("${datatypeString(param.type)} ${param.name}")
|
output("${datatypeString(param.type)} ${param.name}")
|
||||||
if(param!==subroutine.parameters.last())
|
if(param!==subroutine.parameters.last())
|
||||||
|
@@ -711,8 +711,7 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(),
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||||
IAssignable {
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
fun targetStatement(program: Program) =
|
fun targetStatement(program: Program) =
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
|
|
||||||
|
- fix inlining issue with return statement inlined literally at call site
|
||||||
|
|
||||||
- allow inlining of subroutines with vardecls
|
- allow inlining of subroutines with vardecls
|
||||||
- optimize several inner loops in gfx2
|
- 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)
|
- 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)
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
%import textio
|
%import textio
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
%option no_sysinit
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
ubyte thing = otherblock.othersub()
|
ubyte thing = otherblock.othersub()
|
||||||
txt.print_ub(thing) ; should print 99!
|
txt.print_ub(thing) ; should print 21!
|
||||||
|
|
||||||
; str filename = "titlescreen.bin"
|
; str filename = "titlescreen.bin"
|
||||||
; ubyte success = cx16.vload(filename, 8, 0, $0000)
|
; ubyte success = cx16.vload(filename, 8, 0, $0000)
|
||||||
@@ -22,13 +23,21 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block3 {
|
||||||
|
ubyte returnvalue=10
|
||||||
|
|
||||||
|
sub thing()->ubyte {
|
||||||
|
return returnvalue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
otherblock {
|
otherblock {
|
||||||
|
|
||||||
ubyte othervar=20
|
ubyte othervar=20
|
||||||
ubyte calcparam=10
|
ubyte calcparam=10
|
||||||
|
|
||||||
sub calc(ubyte zz) -> ubyte {
|
sub calc(ubyte zz) -> ubyte {
|
||||||
return zz+1
|
return zz+1+block3.thing()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline sub othersub() -> ubyte {
|
inline sub othersub() -> ubyte {
|
||||||
|
Reference in New Issue
Block a user