mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
prevent faulty inlining of subs with register-aliased parameters
This commit is contained in:
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 && (
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user