mirror of
https://github.com/irmen/prog8.git
synced 2025-08-09 19:25:22 +00:00
clean up subroutine inlining, basis for new try
This commit is contained in:
@@ -54,6 +54,14 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
|||||||
return optimizationCount
|
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 {
|
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||||
val opti = ExpressionSimplifier(this, errors)
|
val opti = ExpressionSimplifier(this, errors)
|
||||||
opti.visit(this)
|
opti.visit(this)
|
||||||
|
107
codeOptimizers/src/prog8/optimizer/Inliner.kt
Normal file
107
codeOptimizers/src/prog8/optimizer/Inliner.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -350,9 +350,10 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
|||||||
val optsDone1 = program.simplifyExpressions(errors)
|
val optsDone1 = program.simplifyExpressions(errors)
|
||||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
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
|
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||||
errors.report()
|
errors.report()
|
||||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
if (optsDone1 + optsDone2 + optsDone3 + optsDone4 == 0)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
errors.report()
|
errors.report()
|
||||||
|
@@ -298,8 +298,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(compilerOptions.compTarget.name!=VMTarget.NAME && subroutine.inline && !subroutine.isAsmSubroutine)
|
// Most code generation targets only support subroutine inlining on asmsub subroutines
|
||||||
err("subroutine inlining is currently only supported on asmsub routines")
|
// 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)
|
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")
|
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||||
|
@@ -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.
|
// 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.
|
// 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() ||
|
if(subroutine.statements.isEmpty() ||
|
||||||
(subroutine.amountOfRtsInAsm() == 0
|
(subroutine.amountOfRtsInAsm() == 0
|
||||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||||
|
@@ -274,13 +274,12 @@ private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
|
|||||||
|
|
||||||
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
||||||
// non-asm subroutine
|
// non-asm subroutine
|
||||||
val inline = inline()!=null
|
|
||||||
val returntype = sub_return_part()?.datatype()?.toAst()
|
val returntype = sub_return_part()?.datatype()?.toAst()
|
||||||
return Subroutine(identifier().text,
|
return Subroutine(identifier().text,
|
||||||
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
|
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
|
||||||
if(returntype==null) emptyList() else listOf(returntype),
|
if(returntype==null) emptyList() else listOf(returntype),
|
||||||
statement_block()?.toAst() ?: mutableListOf(),
|
statement_block()?.toAst() ?: mutableListOf(),
|
||||||
inline,
|
false,
|
||||||
toPosition())
|
toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -690,7 +690,7 @@ class Subroutine(override val name: String,
|
|||||||
val asmClobbers: Set<CpuRegister>,
|
val asmClobbers: Set<CpuRegister>,
|
||||||
val asmAddress: UInt?,
|
val asmAddress: UInt?,
|
||||||
val isAsmSubroutine: Boolean,
|
val isAsmSubroutine: Boolean,
|
||||||
val inline: Boolean,
|
var inline: Boolean,
|
||||||
override var statements: MutableList<Statement>,
|
override var statements: MutableList<Statement>,
|
||||||
override val position: Position) : Statement(), INameScope {
|
override val position: Position) : Statement(), INameScope {
|
||||||
|
|
||||||
|
@@ -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.
|
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,
|
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!
|
so pay attention to any jumps and rts instructions in the inlined code!
|
||||||
|
Inlining regular Prog8 subroutines is at the discretion of the compiler.
|
||||||
At this time it is not yet possible to inline regular Prog8 subroutines, this may be added in the future.
|
|
||||||
|
|
||||||
|
|
||||||
Calling a subroutine
|
Calling a subroutine
|
||||||
|
@@ -3,13 +3,8 @@ TODO
|
|||||||
|
|
||||||
For next release
|
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
|
- 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?
|
- add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value?
|
||||||
|
|
||||||
...
|
...
|
||||||
|
@@ -6,13 +6,18 @@
|
|||||||
; NOTE: meant to test to virtual machine output target (use -target vitual)
|
; NOTE: meant to test to virtual machine output target (use -target vitual)
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
ubyte value = 42
|
||||||
|
|
||||||
|
sub derp() -> ubyte {
|
||||||
|
return math.sin8u(value)
|
||||||
|
}
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
str thing = "????"
|
ubyte value = derp()
|
||||||
|
txt.print_ub(value)
|
||||||
if thing=="bmap" {
|
txt.nl()
|
||||||
txt.print("gottem")
|
txt.print_ub(derp())
|
||||||
}
|
txt.nl()
|
||||||
|
|
||||||
; TODO: test with builtin function using multiple args (such as mkword)
|
; 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.... :/
|
; ubyte value = add(3,4) |> add(10) |> mul(2) |> math.sin8u() ; TODO should not work yet on vm codegen, but it compiles.... :/
|
||||||
|
@@ -242,7 +242,7 @@ inlineasm : '%asm' INLINEASMBLOCK;
|
|||||||
inline: 'inline';
|
inline: 'inline';
|
||||||
|
|
||||||
subroutine :
|
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 ;
|
sub_return_part : '->' datatype ;
|
||||||
|
Reference in New Issue
Block a user