prevent faulty inlining of subs with register-aliased parameters

This commit is contained in:
Irmen de Jong committed 2026-06-02 23:14:55 +02:00
1 parent e01ee10ef0
commit eb19d00a1e
5 files changed
+70 -33

No files matched your search

@@ -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<AstModification> {
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 && (
+2 -2
View File
@@ -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),
+50 -12
View File
@@ -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<FunctionCallExpression> {
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 = """
+9 -9
View File
@@ -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
}
}
+1 -1
View File
@@ -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