From 627aa611840567e1f34a2125f31aa825949b77f5 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 9 May 2022 15:42:58 +0200 Subject: [PATCH] clean up subroutine inlining, basis for new try --- .../src/prog8/optimizer/Extensions.kt | 8 ++ codeOptimizers/src/prog8/optimizer/Inliner.kt | 107 ++++++++++++++++++ compiler/src/prog8/compiler/Compiler.kt | 3 +- .../compiler/astprocessing/AstChecker.kt | 6 +- .../astprocessing/BeforeAsmAstChanger.kt | 2 +- .../src/prog8/ast/antlr/Antlr2Kotlin.kt | 3 +- .../src/prog8/ast/statements/AstStatements.kt | 2 +- docs/source/programming.rst | 3 +- docs/source/todo.rst | 7 +- examples/test.p8 | 15 ++- parser/antlr/Prog8ANTLR.g4 | 2 +- 11 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 codeOptimizers/src/prog8/optimizer/Inliner.kt diff --git a/codeOptimizers/src/prog8/optimizer/Extensions.kt b/codeOptimizers/src/prog8/optimizer/Extensions.kt index ee3c693de..27a991176 100644 --- a/codeOptimizers/src/prog8/optimizer/Extensions.kt +++ b/codeOptimizers/src/prog8/optimizer/Extensions.kt @@ -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) diff --git a/codeOptimizers/src/prog8/optimizer/Inliner.kt b/codeOptimizers/src/prog8/optimizer/Inliner.kt new file mode 100644 index 000000000..3fd7a2541 --- /dev/null +++ b/codeOptimizers/src/prog8/optimizer/Inliner.kt @@ -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 { + DetermineInlineSubs(program) + return super.before(program) + } + + override fun after(gosub: GoSub, parent: Node): Iterable { + 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 = inlineCall(functionCallExpr as IFunctionCall, parent) + + override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable = inlineCall(functionCallStatement as IFunctionCall, parent) + + private fun inlineCall(call: IFunctionCall, parent: Node): Iterable { + val sub = call.target.targetStatement(program) as? Subroutine + if(sub!=null && sub.inline) { + val inlined = sub.statements + TODO("INLINE FCALL: $call ---> $inlined") + } + return noModifications + } +} + diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index b22d192ef..25bd80bfd 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -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() diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index b0cdcc416..062ea05da 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -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") diff --git a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt index 8e1f03d36..e2125702c 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt @@ -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 diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index e5cc9a00e..9c6aeb74d 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -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()) } diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 3468c0d9d..f28db8fd6 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -690,7 +690,7 @@ class Subroutine(override val name: String, val asmClobbers: Set, val asmAddress: UInt?, val isAsmSubroutine: Boolean, - val inline: Boolean, + var inline: Boolean, override var statements: MutableList, override val position: Position) : Statement(), INameScope { diff --git a/docs/source/programming.rst b/docs/source/programming.rst index e2b326f1e..a577f9a6c 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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 diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 70ce399d7..261fb5e3d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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? ... diff --git a/examples/test.p8 b/examples/test.p8 index 70cf48efe..44f44744f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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.... :/ diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index d006c6e71..cc36df17b 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -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 ;