From f7183e38eefa48857b5fc0da9b87fa368f26a9e3 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 8 Jun 2022 21:05:03 +0200 Subject: [PATCH] tweak trivial subroutine inlining --- codeOptimizers/src/prog8/optimizer/Inliner.kt | 57 +++++++++++++++---- .../src/prog8/optimizer/StatementOptimizer.kt | 40 ------------- .../compiler/astprocessing/AstChecker.kt | 5 -- .../astprocessing/BeforeAsmAstChanger.kt | 6 ++ docs/source/todo.rst | 1 - examples/test.p8 | 55 ++++++++++-------- 6 files changed, 83 insertions(+), 81 deletions(-) diff --git a/codeOptimizers/src/prog8/optimizer/Inliner.kt b/codeOptimizers/src/prog8/optimizer/Inliner.kt index 02fa5d5b5..7def61927 100644 --- a/codeOptimizers/src/prog8/optimizer/Inliner.kt +++ b/codeOptimizers/src/prog8/optimizer/Inliner.kt @@ -14,6 +14,8 @@ import prog8.code.core.InternalCompilerException private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.value==null +// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined. + class Inliner(val program: Program): AstWalker() { class DetermineInlineSubs(val program: Program): IAstVisitor { @@ -142,8 +144,10 @@ class Inliner(val program: Program): AstWalker() { } private fun makeFullyScoped(call: FunctionCallExpression) { + val sub = call.target.targetSubroutine(program)!! + val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedArgs = makeScopedArgs(call.args) - val scopedCall = FunctionCallExpression(call.target.copy(), scopedArgs.toMutableList(), call.position) + val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position) modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent) } @@ -168,41 +172,70 @@ class Inliner(val program: Program): AstWalker() { override fun after(gosub: GoSub, parent: Node): Iterable { val sub = gosub.identifier.targetStatement(program) as? Subroutine - if(sub!=null && sub.inline && sub.parameters.isEmpty()) { + return if(sub==null) + noModifications + else + possibleInlineFcallStmt(sub, gosub, parent) + + } + + private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable { + if(sub.inline && sub.parameters.isEmpty()) { require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) return if(sub.isAsmSubroutine) { // simply insert the asm for the argument-less routine - listOf(IAstModification.ReplaceNode(gosub, sub.statements.single().copy(), parent)) + listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent)) } else { // note that we don't have to process any args, because we online inline parameterless subroutines. when (val toInline = sub.statements.first()) { - is Return -> noModifications - else -> listOf(IAstModification.ReplaceNode(gosub, toInline.copy(), parent)) + is Return -> { + val fcall = toInline.value as? FunctionCallExpression + if(fcall!=null) { + // insert the function call expression as a void function call directly + val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position) + listOf(IAstModification.ReplaceNode(origNode, call, parent)) + } else + noModifications + } + else -> listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent)) } } - } return noModifications } override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable { val sub = functionCallStatement.target.targetStatement(program) as? Subroutine + return if(sub==null) + noModifications + else + possibleInlineFcallStmt(sub, functionCallStatement, parent) + } + + override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable { + val sub = functionCallExpr.target.targetStatement(program) as? Subroutine if(sub!=null && sub.inline && sub.parameters.isEmpty()) { require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1]))) return if(sub.isAsmSubroutine) { - // simply insert the asm for the argument-less routine - listOf(IAstModification.ReplaceNode(functionCallStatement, sub.statements.single().copy(), parent)) + // cannot inline assembly directly in the Ast here as an Asm node is not an expression.... + noModifications } else { - // note that we don't have to process any args, because we online inline parameterless subroutines. when (val toInline = sub.statements.first()) { - is Return -> noModifications - else -> listOf(IAstModification.ReplaceNode(functionCallStatement, toInline.copy(), parent)) + is Return -> { + // is an expression, so we have to have a Return here in the inlined sub + // note that we don't have to process any args, because we online inline parameterless subroutines. + if(toInline.value!=null) + listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent)) + else + noModifications + } + else -> noModifications } } } + return noModifications } - // TODO also inline function call expressions, and remove it from the StatementOptimizer } diff --git a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt index 30e012084..d369b77de 100644 --- a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt @@ -16,46 +16,6 @@ class StatementOptimizer(private val program: Program, private val compTarget: ICompilationTarget ) : AstWalker() { - override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable { - // if the first instruction in the called subroutine is a return statement with a simple value (NOT being a parameter), - // remove the jump altogeter and inline the returnvalue directly. (only if not part of a pipe expression) - - fun scopePrefix(variable: IdentifierReference): IdentifierReference { - val target = variable.targetStatement(program) as INamedStatement - return IdentifierReference(target.scopedName, variable.position) - } - - val subroutine = functionCallExpr.target.targetSubroutine(program) - if(subroutine!=null) { - val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() - if(first is Return && first.value?.isSimple==true && parent !is IPipe) { - val copy = when(val orig = first.value!!) { - is AddressOf -> { - val scoped = scopePrefix(orig.identifier) - AddressOf(scoped, orig.position) - } - is DirectMemoryRead -> { - when(val expr = orig.addressExpression) { - is NumericLiteral -> DirectMemoryRead(expr.copy(), orig.position) - else -> return noModifications - } - } - is IdentifierReference -> { - if(orig.targetVarDecl(program)?.origin == VarDeclOrigin.SUBROUTINEPARAM) - return noModifications - else - scopePrefix(orig) - } - is NumericLiteral -> orig.copy() - is StringLiteral -> orig.copy() - else -> return noModifications - } - return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent)) - } - } - return noModifications - } - override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable { if(functionCallStatement.target.nameInSource.size==1) { val functionName = functionCallStatement.target.nameInSource[0] diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 413d672fb..c5ba0f3eb 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -299,11 +299,6 @@ internal class AstChecker(private val program: Program, } } - // 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 648fb7627..8185bf3c8 100644 --- a/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt +++ b/compiler/src/prog8/compiler/astprocessing/BeforeAsmAstChanger.kt @@ -120,6 +120,12 @@ internal class BeforeAsmAstChanger(val program: Program, } override fun after(subroutine: Subroutine, parent: Node): Iterable { + + // 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 && options.compTarget.name!=VMTarget.NAME) + subroutine.inline = false + val mods = mutableListOf() // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index f44c44440..f34b03d7a 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,7 +5,6 @@ For next release ^^^^^^^^^^^^^^^^ - pipe operator: (targets other than 'Virtual'): allow non-unary function calls in the pipe that specify the other argument(s) in the calls. Already working for VM target. - add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value? -- Inliner: also inline function call expressions, and remove it from the StatementOptimizer ... diff --git a/examples/test.p8 b/examples/test.p8 index f023cafe0..4f90aa8ec 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -2,11 +2,33 @@ ;%import test_stack %zeropage basicsafe - ; NOTE: meant to test to virtual machine output target (use -target vitual) -main { +other { + ubyte variable = 40 + ubyte var2=2 + sub func1(ubyte arg) -> ubyte { + txt.print_ub(arg) + txt.spc() + return arg*var2 + } + + sub inliner() -> ubyte { + return func1(variable) + } + + sub inliner2() { + txt.print_ub(22) + return + } + +} + +main $2000 { + + ubyte x=10 + ubyte y=20 ; sub ands(ubyte arg, ubyte b1, ubyte b2, ubyte b3, ubyte b4) -> ubyte { ; return arg>b1 and arg>b2 and arg>b3 and arg>b4 ; } @@ -29,33 +51,20 @@ main { ; txt.spc() ; } + sub start() { ; mcCarthy() - ;test_stack.test() - ubyte one = 1 - ubyte two = 2 - uword onew = 1 - uword twow = 2 - ubyte[10] data = [1,2,3,4,5,6,7,8,9,10] - uword bitmapbuf = &data + other.inliner2() + other.inliner2() + rnd() + void other.inliner() + void other.inliner() -; @(bitmapbuf+onew) = 90+one -; @(bitmapbuf+twow) = 90+two - bitmapbuf += 5 -; @(bitmapbuf-1) = 90+one -; @(bitmapbuf-2) = 90+two - @(bitmapbuf-onew) = 90+one - @(bitmapbuf-twow) = 90+two - - ubyte value - for value in data { - txt.print_ub(value) ; 3 2 97 42 5 6 7 8 9 10 - txt.spc() - } + ubyte derp = other.inliner() * other.inliner() ; TODO inline this (was $207 bytes size) + txt.print_ub(derp) txt.nl() - ;test_stack.test() ; ; a "pixelshader":