clean up subroutine inlining, basis for new try

This commit is contained in:
Irmen de Jong 2022-05-09 15:42:58 +02:00
parent dad5b17ac8
commit 627aa61184
11 changed files with 137 additions and 21 deletions

View File

@ -54,6 +54,14 @@ fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount
}
fun Program.inlineSubroutines(): Int {
// TODO implement the inliner
// val inliner = Inliner(this)
// inliner.visit(this)
// return inliner.applyModifications()
return 0
}
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this, errors)
opti.visit(this)

View File

@ -0,0 +1,107 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
class Inliner(val program: Program): AstWalker() {
class DetermineInlineSubs(program: Program): IAstVisitor {
init {
visit(program)
}
override fun visit(subroutine: Subroutine) {
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
if(!containsSubsOrVariables) {
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && subroutine.statements[1] is Return)) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when(val stmt=subroutine.statements[0]) {
is Return -> {
if(stmt.value!!.isSimple) {
makeFullyScoped(stmt)
true
} else
false
}
is Assignment -> {
val inline = stmt.value.isSimple && (stmt.target.identifier!=null || stmt.target.memoryAddress?.addressExpression?.isSimple==true)
if(inline)
makeFullyScoped(stmt)
inline
}
is BuiltinFunctionCallStatement,
is FunctionCallStatement -> {
stmt as IFunctionCall
val inline = stmt.args.size<=1 && stmt.args.all { it.isSimple }
if(inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
val inline = (stmt.target.identifier!=null || stmt.target.memoryAddress?.addressExpression?.isSimple==true)
if(inline)
makeFullyScoped(stmt)
inline
}
is Jump, is GoSub -> true
else -> false
}
}
}
}
super.visit(subroutine)
}
private fun makeFullyScoped(incrdecr: PostIncrDecr) {
TODO("Not yet implemented")
}
private fun makeFullyScoped(call: IFunctionCall) {
TODO("Not yet implemented")
}
private fun makeFullyScoped(assign: Assignment) {
TODO("Not yet implemented")
}
private fun makeFullyScoped(ret: Return) {
TODO("Not yet implemented")
}
}
override fun before(program: Program): Iterable<IAstModification> {
DetermineInlineSubs(program)
return super.before(program)
}
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
val sub = gosub.identifier.targetStatement(program) as? Subroutine
if(sub!=null && sub.inline) {
val inlined = sub.statements
TODO("INLINE GOSUB: $gosub ---> $inlined")
}
return noModifications
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = inlineCall(functionCallExpr as IFunctionCall, parent)
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = inlineCall(functionCallStatement as IFunctionCall, parent)
private fun inlineCall(call: IFunctionCall, parent: Node): Iterable<IAstModification> {
val sub = call.target.targetStatement(program) as? Subroutine
if(sub!=null && sub.inline) {
val inlined = sub.statements
TODO("INLINE FCALL: $call ---> $inlined")
}
return noModifications
}
}

View File

@ -350,9 +350,10 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
val optsDone1 = program.simplifyExpressions(errors)
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines()
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
if (optsDone1 + optsDone2 + optsDone3 + optsDone4 == 0)
break
}
errors.report()

View File

@ -298,8 +298,10 @@ internal class AstChecker(private val program: Program,
}
}
if(compilerOptions.compTarget.name!=VMTarget.NAME && subroutine.inline && !subroutine.isAsmSubroutine)
err("subroutine inlining is currently only supported on asmsub routines")
// Most code generation targets only support subroutine inlining on asmsub subroutines
// So we reset the flag here to be sure it doesn't cause problems down the line in the codegen.
if(!subroutine.isAsmSubroutine && compilerOptions.compTarget.name!=VMTarget.NAME)
subroutine.inline = false
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")

View File

@ -124,7 +124,7 @@ internal class BeforeAsmAstChanger(val program: Program,
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
if (!subroutine.isAsmSubroutine && (!subroutine.inline || !options.optimize)) {
if (!subroutine.isAsmSubroutine) {
if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return

View File

@ -274,13 +274,12 @@ private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val inline = inline()!=null
val returntype = sub_return_part()?.datatype()?.toAst()
return Subroutine(identifier().text,
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
if(returntype==null) emptyList() else listOf(returntype),
statement_block()?.toAst() ?: mutableListOf(),
inline,
false,
toPosition())
}

View File

@ -690,7 +690,7 @@ class Subroutine(override val name: String,
val asmClobbers: Set<CpuRegister>,
val asmAddress: UInt?,
val isAsmSubroutine: Boolean,
val inline: Boolean,
var inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {

View File

@ -691,8 +691,7 @@ in-place to the locations where the subroutine is called, rather than inserting
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.
Inlining regular Prog8 subroutines is at the discretion of the compiler.
Calling a subroutine

View File

@ -3,13 +3,8 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- make it possible to inline non-asmsub routines that just contain a single statement (return, functioncall, assignment)
Only if the arguments are simple expressions, and the inlined subroutine cannot contain further nested subroutines!
This requires all identifiers in the inlined expression to be changed to fully scoped names (because their scope changes).
If we can do that why not perhaps also able to inline multi-line subroutines?
Why would it be limited to just 1 line? Maybe to protect against code size bloat.
Once this works, look for library subroutines that should be inlined.
- vm: add way more instructions operating directly on memory instead of only registers
- complete the Inliner
- add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value?
...

View File

@ -6,13 +6,18 @@
; NOTE: meant to test to virtual machine output target (use -target vitual)
main {
ubyte value = 42
sub derp() -> ubyte {
return math.sin8u(value)
}
sub start() {
str thing = "????"
if thing=="bmap" {
txt.print("gottem")
}
ubyte value = derp()
txt.print_ub(value)
txt.nl()
txt.print_ub(derp())
txt.nl()
; TODO: test with builtin function using multiple args (such as mkword)
; ubyte value = add(3,4) |> add(10) |> mul(2) |> math.sin8u() ; TODO should not work yet on vm codegen, but it compiles.... :/

View File

@ -242,7 +242,7 @@ inlineasm : '%asm' INLINEASMBLOCK;
inline: 'inline';
subroutine :
inline? 'sub' identifier '(' sub_params? ')' sub_return_part? (statement_block EOL)
'sub' identifier '(' sub_params? ')' sub_return_part? (statement_block EOL)
;
sub_return_part : '->' datatype ;