tweak trivial subroutine inlining

This commit is contained in:
Irmen de Jong 2022-06-08 21:05:03 +02:00
parent 0a65dfdd10
commit f7183e38ee
6 changed files with 83 additions and 81 deletions

View File

@ -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<IAstModification> {
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<IAstModification> {
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<IAstModification> {
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<IAstModification> {
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
}

View File

@ -16,46 +16,6 @@ class StatementOptimizer(private val program: Program,
private val compTarget: ICompilationTarget
) : AstWalker() {
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
// 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<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]

View File

@ -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")

View File

@ -120,6 +120,12 @@ internal class BeforeAsmAstChanger(val program: Program,
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// 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<IAstModification>()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.

View File

@ -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
...

View File

@ -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":