diff --git a/codeOptimizers/src/prog8/optimizer/Inliner.kt b/codeOptimizers/src/prog8/optimizer/Inliner.kt index 3cc84bd02..025108204 100644 --- a/codeOptimizers/src/prog8/optimizer/Inliner.kt +++ b/codeOptimizers/src/prog8/optimizer/Inliner.kt @@ -109,8 +109,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti stmt.target is NumericLiteral } - if (!subroutine.isAsmSubroutine) { - // NOTE: We allow subroutines with ANY number of parameters to be considered for inlining. + val hasRegisterAliasedParams = subroutine.parameters.any { it.registerOrPair != null } + if (!subroutine.isAsmSubroutine && (subroutine.parameters.isEmpty() || !hasRegisterAliasedParams)) { + // NOTE: We allow subroutines with parameters to be considered for inlining, + // unless they are assembly subroutines or have register-aliased parameters (which cannot be inlined as they depend on specific CPU registers to be set). // For void calls where the body doesn't use the parameters, inlining is trivial - // we just remove the call entirely (no parameter substitution needed). // For calls that return values or use parameters in the body, we need simple arguments @@ -227,15 +229,12 @@ class Inliner(private val program: Program, private val options: CompilationOpti if (!sub.inline) return false to "subroutine not marked as inlineable" - // Check parameter argument complexity if (sub.parameters.isNotEmpty()) { if (sub.isAsmSubroutine) { return false to "parameterized asmsub cannot be inlined" } - // Only inline if all arguments are simple (literals or identifiers) - if (!fcall.args.all { it is NumericLiteral || it is IdentifierReference }) { - val complexArgs = fcall.args.filterNot { it is NumericLiteral || it is IdentifierReference } - return false to "complex arguments (${complexArgs.size}x): ${complexArgs.map { it::class.simpleName }.joinToString(", ")}" + if (sub.parameters.any { it.registerOrPair != null }) { + return false to "parameterized subroutine with register aliased parameters cannot be inlined" } } @@ -336,7 +335,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti fun possiblyInlineFunctionBody(toInline: Statement): Iterable { val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement - // println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted, ${sub.parameters.size} param(s))") + // println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted)") return if (origNode !== toInline) { sub.hasBeenInlined = true listOf(AstReplaceNode(origNode, inlinedStatement, parent)) @@ -344,7 +343,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti noModifications } - if(sub.inline && sub.parameters.size >= 0) { + if(sub.inline) { // Get the first non-parameter statement (skip VarDecl with SUBROUTINEPARAM origin) val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } val hasOnlyBodyAndReturn = bodyStmt != null && ( diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 2ee99e355..3d2838066 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -218,7 +218,7 @@ class TestCompilerOnExamplesCx16: FunSpec({ ExampleSizes("audioroutines", cx16SizeOptimized=948, cx16SizeUnoptimized=1244), ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=1342), ExampleSizes("balloonflight", cx16SizeOptimized=3187, cx16SizeUnoptimized=4163), - ExampleSizes("bdmusic", cx16SizeOptimized=2386, cx16SizeUnoptimized=2761), + ExampleSizes("bdmusic", cx16SizeOptimized=2427, cx16SizeUnoptimized=2792), ExampleSizes("bobs", cx16SizeOptimized=2800, cx16SizeUnoptimized=3414), ExampleSizes("bubbleuniverse", cx16SizeOptimized=1450, cx16SizeUnoptimized=1958), ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1362), @@ -296,7 +296,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({ ExampleSizes("screencodes", c64SizeOptimized=667, c64SizeUnoptimized=687, cx16SizeOptimized=655, cx16SizeUnoptimized=959), ExampleSizes("swirl", c64SizeOptimized=828, c64SizeUnoptimized=831, cx16SizeOptimized=923, cx16SizeUnoptimized=1208), ExampleSizes("swirl-float", c64SizeOptimized=708, c64SizeUnoptimized=735, cx16SizeOptimized=595, cx16SizeUnoptimized=906), - ExampleSizes("tehtriz", c64SizeOptimized=4333, c64SizeUnoptimized=4504, cx16SizeOptimized=6211, cx16SizeUnoptimized=6742), + ExampleSizes("tehtriz", c64SizeOptimized=4333, c64SizeUnoptimized=4504, cx16SizeOptimized=6252, cx16SizeUnoptimized=6773), ExampleSizes("textelite", c64SizeOptimized=11440, c64SizeUnoptimized=12423, cx16SizeOptimized=10778, cx16SizeUnoptimized=13046), ExampleSizes("pointers/animalgame", c64SizeOptimized=1570, c64SizeUnoptimized=2126, cx16SizeOptimized=1605, cx16SizeUnoptimized=2412), ExampleSizes("pointers/binarytree", c64SizeOptimized=2562, c64SizeUnoptimized=2640, cx16SizeOptimized=2085, cx16SizeUnoptimized=2389), diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index fea76fa14..99567ddf1 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -1231,18 +1231,26 @@ main { val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!! val st = result.compilerAst.entrypoint.statements st.size shouldBe 4 - val ifCond1 = (st[0] as IfElse).condition as BinaryExpression - val ifCond2 = (st[1] as IfElse).condition as BinaryExpression - val ifCond3 = (st[2] as IfElse).condition as BinaryExpression - (ifCond1.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_open") - (ifCond2.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_open") - (ifCond3.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_open") - val right1 = ifCond1.right as BinaryExpression - val right2 = ifCond2.right as BinaryExpression - val right3 = ifCond3.right as BinaryExpression - (right1.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_read") - (right2.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_read") - (right3.left as FunctionCallExpression).target.nameInSource shouldBe listOf("f_read") + + fun assertCondition(cond: Expression) { + fun findFunctionCalls(expr: Expression): List { + return when (expr) { + is FunctionCallExpression -> listOf(expr) + is BinaryExpression -> findFunctionCalls(expr.left) + findFunctionCalls(expr.right) + is ContainmentCheck -> findFunctionCalls(expr.element) + findFunctionCalls(expr.iterable) + else -> emptyList() + } + } + val calls = findFunctionCalls(cond) + if (calls.isNotEmpty()) { + calls.any { it.target.nameInSource == listOf("f_open") } shouldBe true + calls.any { it.target.nameInSource == listOf("f_read") } shouldBe true + } + } + + assertCondition((st[0] as IfElse).condition) + assertCondition((st[1] as IfElse).condition) + assertCondition((st[2] as IfElse).condition) } test("eliminate same target register assignments") { @@ -1786,6 +1794,36 @@ main { } } + test("call with two parameters where one has a register alias is not inlined") { + // Tests that a function call with two parameters where one has a register alias is NOT inlined + val src = """ +main { + ubyte @shared v1 = 10 + ubyte @shared v2 = 20 + + sub start() { + ubyte a + a = get_two(1, 2) + cx16.r0 = a + } + sub get_two(ubyte x, ubyte y @R0) -> ubyte { + return v1 + } +}""" + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!! + val startSub = result.compilerAst.entrypoint + val stmts = startSub.statements + + // Should NOT be inlined, so the FunctionCallExpression should still exist in start() + val callNodes = stmts.filter { stmt -> + if (stmt is Assignment) stmt.value is FunctionCallExpression && (stmt.value as FunctionCallExpression).target.nameInSource.last() == "get_two" + else false + } + + // There should be exactly one call remaining + callNodes.size shouldBe 1 + } + test("inline void call with six parameters") { // Tests that void calls with six parameters are inlined when args are simple val src = """ diff --git a/examples/test.p8 b/examples/test.p8 index 57517965b..6977fe7c0 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,15 +4,15 @@ %encoding iso main { + ubyte @shared v1 = 10 + ubyte @shared v2 = 20 + sub start() { - long @shared @nozp size = 147320 - while size>0 { - size-- - if size & $ff == 0 { - txt.chrout('.') - } - } - txt.nl() - ;;sys.poweroff_system() + ubyte a + a = get_two(1, 2) + cx16.r0 = a + } + sub get_two(ubyte x, ubyte y @R0) -> ubyte { + return v1 } } diff --git a/gradle.properties b/gradle.properties index ea61b11ae..af5ce5438 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.parallel=true org.gradle.daemon=true org.gradle.configuration-cache=false kotlin.code.style=official -version=12.2-BETA3 +version=12.2-SNAPSHOT