callgraph: nameInAssemblyCode searches smarter (for unused())

This commit is contained in:
Irmen de Jong 2022-02-01 00:24:31 +01:00
parent ddf96943f0
commit 5c8c64242f
5 changed files with 94 additions and 29 deletions

View File

@ -6,12 +6,14 @@ import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe
import prog8.ast.Program
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.codegen.target.C64Target
import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import prog8tests.helpers.*
class TestCallgraph: FunSpec({
test("testGraphForEmptySubs") {
@ -121,4 +123,60 @@ class TestCallgraph: FunSpec({
empties[1].position.line shouldBe 5
empties[2].position.line shouldBe 6
}
test("checking block and subroutine names usage in assembly code") {
val source = """
main {
sub start() {
%asm {{
lda #<blockname
lda #<blockname.subroutine
correctlabel:
nop
}}
}
}
blockname {
sub subroutine() {
@(1000) = 0
}
sub correctlabel() {
@(1000) = 0
}
}
; all block and subroutines below should NOT be found in asm because they're only substrings of the names in there
locknam {
sub rout() {
@(1000) = 0
}
sub orrectlab() {
@(1000) = 0
}
}"""
val module = parseModule(SourceCode.Text(source))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val callgraph = CallGraph(program)
val blockMain = program.allBlocks.single { it.name=="main" }
val blockBlockname = program.allBlocks.single { it.name=="blockname" }
val blockLocknam = program.allBlocks.single { it.name=="locknam" }
val subStart = blockMain.statements.filterIsInstance<Subroutine>().single { it.name == "start" }
val subSubroutine = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "subroutine" }
val subCorrectlabel = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "correctlabel" }
val subRout = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "rout" }
val subOrrectlab = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "orrectlab" }
callgraph.unused(blockMain) shouldBe false
callgraph.unused(blockBlockname) shouldBe false
callgraph.unused(blockLocknam) shouldBe true
callgraph.unused(subStart) shouldBe false
callgraph.unused(subSubroutine) shouldBe false
callgraph.unused(subCorrectlabel) shouldBe false
callgraph.unused(subRout) shouldBe true
callgraph.unused(subOrrectlab) shouldBe true
}
})

View File

@ -612,10 +612,17 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
override fun copy() = throw NotImplementedError("no support for duplicating a InlineAssembly")
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
val names: Set<String> by lazy {
// A cache of all the words (identifiers) present in this block of assembly code
// this is used when checking if prog8 names are referenced from assembly code
// TODO: smarter pattern; don't include words in comments
val wordPattern = Regex("""\b([_a-zA-Z][_a-zA-Z0-9]+?)\b""", RegexOption.MULTILINE)
wordPattern.findAll(assembly).map { it.value }.toSet()
}
}
class AnonymousScope(override var statements: MutableList<Statement>,

View File

@ -3,7 +3,6 @@ package prog8.compilerinterface
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -222,8 +221,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
return allIdentifiersAndTargets.filter { decl===it.value }.map{ it.key }
}
private fun nameInAssemblyCode(name: String) =
allAssemblyNodes.any { it.assembly.contains(name) } // TODO be smarter about what to search in assembly (only labels starting in column 0?)
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { name in it.names }
inline fun unused(label: Label) = false // just always output labels

View File

@ -4,7 +4,6 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- Fix: don't report as recursion if code assigns address of its own subroutine to something, rather than calling it
- nameInAssemblyCode() should search smarter (only labels in column 0? only full words, not part of a larger word? use regex? )
Need help with
^^^^^^^^^^^^^^

View File

@ -1,30 +1,33 @@
%import textio
%zeropage basicsafe
main {
sub start() {
byte bb1 = -50
byte bb2 = -51
word ww = func(bb1, bb2)
txt.print_w(ww)
txt.print(" <- must be -50\n")
ubyte ub1 = 50
ubyte ub2 = 51
uword uw = funcu(ub1, ub2)
txt.print_uw(uw)
txt.print(" <- must be 50\n")
%asm {{
lda #<blockname
lda #<blockname.subroutine
correctlabel:
nop ; rout orrectlab locknam
}}
}
sub func(word x1, word y1) -> word {
return x1
;word zz = x1+1
;return zz-1
}
blockname {
sub subroutine() {
@($c000) = 0
}
sub funcu(uword x1, uword y1) -> uword {
uword zz = x1+1
return zz-1
sub correctlabel() {
@($c000) = 0
}
}
; all block and subroutines below should NOT be found in asm because they're only substrings of the names in there, or in a comment
locknam {
sub rout() {
@($c000) = 0
}
sub orrectlab() {
@($c000) = 0
}
}