keep routines referenced from struct instance initializers, so that unusedcoderemover doesn't strip them

This commit is contained in:
Irmen de Jong committed 2026-09-30 21:33:26 +02:00
1 parent 6c107379df
commit e38f71fd2d
2 files changed
+48

No files matched your search

@@ -8,6 +8,18 @@ class IRUnusedCodeRemover(
private val irprog: IRProgram,
private val errors: IErrorReporter
) {
/**
* The symbols that struct instance initializers take the address of. These are plain data
* references: nothing ever calls them, so without this the code they point at is considered
* unused and removed, leaving a dangling reference in the instance initializer.
* The same reasoning applies to the array initializers of variables, which are handled
* separately at each of the use sites below.
*/
private fun structInstanceInitRefs(): Sequence<IRStSymbolicReference.Symbol> =
irprog.st.allStructInstances()
.flatMap { instance -> instance.values.asSequence().map { it.value } }
.filterIsInstance<IRStSymbolicReference.Symbol>()
fun optimize(): Int {
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
@@ -52,6 +64,14 @@ class IRUnusedCodeRemover(
return // symbol occurs in an initializer value (address-of this symbol)_
}
}
irprog.st.allStructInstances().forEach { instance ->
if(instance.values.any { fieldValue ->
val ref = fieldValue.value
ref is IRStSymbolicReference.Symbol && ref.name.startsWith(blockLabel)
})
return // symbol occurs in a struct instance initializer value
}
}
val blockStructs = irprog.st.allStructDefs().filter { it.name.startsWith(prefix) }
@@ -221,6 +241,11 @@ class IRUnusedCodeRemover(
}
}
// ... and likewise the ones referenced in struct instance initializers:
structInstanceInitRefs()
.filter { irprog.st.lookup(it.name) == null }
.forEach { ref -> irprog.getChunkWithLabel(ref.name).let { reachable += it } }
fun grow() {
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
@@ -277,6 +302,11 @@ class IRUnusedCodeRemover(
}
}
// ... and likewise the chunks referenced in struct instance initializers:
structInstanceInitRefs()
.filter { irprog.st.lookup(it.name) == null }
.forEach { ref -> allLabeledChunks[ref.name]?.let { linkedChunks += it } }
irprog.foreachCodeChunk { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
+18
View File
@@ -362,4 +362,22 @@ class TestArraysOfStructs: FunSpec({
}
}
}
test("routine referenced only from a struct instance initializer is not removed as unused") {
val src = """
main {
sub handler() -> ubyte { return 42 }
struct Function { pointer addr }
^^Function[] a = [ ^^Function:[&handler] ]
sub start() {
pointer p = a[0].addr
call(p)
}
}"""
val out = tempdir().toPath()
compileText(C64Target(), true, src, out, writeAssembly = true, assemble = false, newCodegen = true)!!
val asmFile = out.toFile().listFiles()!!.single { it.name.endsWith(".asm") }
val lines = asmFile.readText().lines().map { it.trim().replace(Regex("\\s+"), " ") }
lines.any { it == "p8b_main.p8s_handler .proc" } shouldBe true
}
})