better unused variable removal

This commit is contained in:
Irmen de Jong 2021-11-11 03:03:21 +01:00
parent 69f4a4d4f8
commit 53ac11983b
5 changed files with 60 additions and 13 deletions

View File

@ -107,9 +107,30 @@ class UnusedCodeRemover(private val program: Program,
if(decl.type==VarDeclType.VAR) { if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock.options() val forceOutput = "force_output" in decl.definingBlock.options()
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) { if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
if (callgraph.unused(decl)) { val usages = callgraph.usages(decl)
if (usages.isEmpty()) {
errors.warn("removing unused variable '${decl.name}'", decl.position) errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer)) return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} else {
// if all usages are just an assignment to this vardecl
// and it is in regular RAM, then remove the var as well including all assignments
val assignTargets = usages.mapNotNull {
if(it.parent is AssignTarget)
it.parent as AssignTarget
else if(it.parent.parent is AssignTarget)
it.parent.parent as AssignTarget
else null
}.filter {
it.isInRegularRAMof(compTarget.machine)
}
if(assignTargets.size==usages.size) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return assignTargets.map { it.parent to it.definingScope}.toSet().map {
IAstModification.Remove(it.first, it.second)
} + listOf(
IAstModification.Remove(decl, parent as IStatementContainer)
)
}
} }
} }
} }

View File

@ -29,7 +29,7 @@ class TestCompilerOnRanges: FunSpec({
test("testUByteArrayInitializerWithRange_char_to_char") { test("testUByteArrayInitializerWithRange_char_to_char") {
val platform = Cx16Target val platform = Cx16Target
val result = compileText(platform, true, """ val result = compileText(platform, false, """
main { main {
sub start() { sub start() {
ubyte[] cs = @'a' to 'z' ; values are computed at compile time ubyte[] cs = @'a' to 'z' ; values are computed at compile time

View File

@ -93,10 +93,10 @@ class TestOptimization: FunSpec({
main { main {
sub start() { sub start() {
const ubyte TEST = 10 const ubyte TEST = 10
byte x1 = TEST as byte + 1 byte @shared x1 = TEST as byte + 1
byte x2 = 1 + TEST as byte byte @shared x2 = 1 + TEST as byte
ubyte y1 = TEST + 1 as byte ubyte @shared y1 = TEST + 1 as byte
ubyte y2 = 1 as byte + TEST ubyte @shared y2 = 1 as byte + TEST
} }
} }
""" """
@ -215,4 +215,29 @@ class TestOptimization: FunSpec({
val asm = generateAssembly(result1.program) val asm = generateAssembly(result1.program)
asm.valid shouldBe true asm.valid shouldBe true
} }
test("unused variable removal") {
val src="""
main {
sub start() {
ubyte unused
ubyte @shared unused_but_shared ; this one should remain
ubyte usedvar_only_written
usedvar_only_written=2
usedvar_only_written++
ubyte usedvar ; and this one too
usedvar = msb(usedvar)
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
result.program.entrypoint.statements.size shouldBe 4 // unused_but_shared decl, unused_but_shared=0, usedvar decl, usedvar assign
val (decl, assign, decl2, assign2) = result.program.entrypoint.statements
decl shouldBe instanceOf<VarDecl>()
(decl as VarDecl).name shouldBe "unused_but_shared"
assign shouldBe instanceOf<Assignment>()
decl2 shouldBe instanceOf<VarDecl>()
(decl2 as VarDecl).name shouldBe "usedvar"
assign2 shouldBe instanceOf<Assignment>()
}
}) })

View File

@ -173,14 +173,18 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
fun unused(decl: VarDecl): Boolean { fun unused(decl: VarDecl): Boolean {
// Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
return usages(decl).isEmpty()
}
fun usages(decl: VarDecl): List<IdentifierReference> {
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm) if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
return false return emptyList()
if(decl.definingBlock !in usedBlocks) if(decl.definingBlock !in usedBlocks)
return false return emptyList()
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet() return allIdentifiersAndTargets.filter { decl===it.value }.map{ it.key.first }
return decl !in allReferencedVardecls // Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
} }
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) } private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) }

View File

@ -5,9 +5,6 @@
main { main {
sub start() { sub start() {
ubyte unused ; TODO FIX : why is this not removed as an unused variable?
ubyte @shared unused2
ubyte bb ubyte bb
uword ww uword ww
ww = not bb or not ww ; TODO WHY DOES THIS USE STACK EVAL ww = not bb or not ww ; TODO WHY DOES THIS USE STACK EVAL