simplify & fix recursion detector

This commit is contained in:
Irmen de Jong 2022-07-21 22:56:25 +02:00
parent f2d27403c5
commit 05f935b598
4 changed files with 50 additions and 59 deletions

View File

@ -197,10 +197,7 @@ class TestCallgraph: FunSpec({
callgraph.checkRecursiveCalls(errors) callgraph.checkRecursiveCalls(errors)
errors.errors.size shouldBe 0 errors.errors.size shouldBe 0
errors.warnings.size shouldBe 4 errors.warnings.size shouldBe 4
errors.warnings[0] shouldContain "contains recursive subroutine calls" errors.warnings[0] shouldContain "contains recursive subroutines"
errors.warnings[1] shouldContain "start at"
errors.warnings[2] shouldContain "recurse1 at"
errors.warnings[3] shouldContain "recurse2 at"
} }
test("no recursion warning if reference isn't a call") { test("no recursion warning if reference isn't a call") {

View File

@ -129,54 +129,35 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
} }
fun checkRecursiveCalls(errors: IErrorReporter) { fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles() val recursiveSubroutines = recursionCycles()
if(cycles.any()) { if(recursiveSubroutines.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", cycles[0][0].position) errors.warn("Program contains recursive subroutines. These only works in very specific limited scenarios!", recursiveSubroutines.first().position)
val printed = mutableSetOf<Subroutine>() for(subroutine in recursiveSubroutines) {
for(chain in cycles) { errors.warn("recursive subroutine '${subroutine.name}'", subroutine.position)
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", chain[0].position)
printed.add(chain[0])
}
} }
} }
} }
private fun recursionCycles(): List<List<Subroutine>> { private fun recursionCycles(): Set<Subroutine> {
val chains = mutableListOf<MutableList<Subroutine>>() val cycles = mutableSetOf<Subroutine>()
for(caller in calls.keys) { for(caller in calls.keys) {
val visited = calls.keys.associateWith { false }.toMutableMap() if(hasRecursionCycle(caller))
val recStack = calls.keys.associateWith { false }.toMutableMap() cycles.add(caller)
val chain = mutableListOf<Subroutine>()
if(hasCycle(caller, visited, recStack, chain))
chains.add(chain)
} }
return chains return cycles
} }
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean { private fun hasRecursionCycle(sub: Subroutine): Boolean {
// mark current node as visited and add to recursion stack val callCloud = calls.getValue(sub).toMutableSet()
if(recStack[sub]==true) var previousCloudSize = -1
return true while(callCloud.size > previousCloudSize && sub !in callCloud) {
if(visited[sub]==true) previousCloudSize = callCloud.size
return false for(element in callCloud.toList()) {
callCloud.addAll(calls.getValue(element))
// mark visited and add to recursion stack
visited[sub] = true
recStack[sub] = true
// recurse for all neighbours
for(called in calls.getValue(sub)) {
if(hasCycle(called, visited, recStack, chain)) {
chain.add(called)
return true
} }
} }
return sub in callCloud
// pop from recursion stack
recStack[sub] = false
return false
} }
fun unused(module: Module) = module !in usedModules fun unused(module: Module) = module !in usedModules

View File

@ -3,8 +3,14 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- can the recursive cycle detector print the actual LINES that do the call? - what to do with the rouding difference in signed divide by 2 / 4 (double ror)? it rounds towards minus infinity (so -5 / 2 = -3)
while the NON-optimized routine produces -2 . Virtual machine also produces -3?
What rounding do we want?
- add item to XyzZeropage that enables an option that if zeropage=FULL or KERNALSAFE, moves the cx16 virtual registers to ZP, same location as on x16
(can be done on C64 only for now) Remove those addresses from the ZP free pool = allocate them in ZP like Cx16Zeropage does
Adapt the code in AstPreprocessor that relocates the registers as well.
- for uword pointer variables: allow pointer[uword] array indexing >255 , rewrite it to @(pointer+index)
DO NOT allow this for regular array indexing because normal arrays can never exceed size 256
... ...
@ -18,13 +24,6 @@ Need help with
Future Things and Ideas Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Compiler: Compiler:
- add item to XZeropage that enables an option that if zeropage=FULL or KERNALSAFE, moves the cx16 virtual registers to ZP, same location as on x16
(can be done on C64 only for now) Remove those addresses from the ZP free pool = allocate them in ZP like Cx16Zeropage does
Adapt the code in AstPreprocessor that relocates the registers as well.
- for uword pointer variables: allow pointer[uword] array indexing >255 , rewrite it to @(pointer+index)
DO NOT allow this for regular array indexing because normal arrays can never exceed size 256
- vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is. - vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is.
this info is needed for more advanced optimizations and later code generation steps. this info is needed for more advanced optimizations and later code generation steps.
- vm: implement remaining sin/cos functions in math.p8 - vm: implement remaining sin/cos functions in math.p8

View File

@ -1,16 +1,30 @@
%import textio %import textio
%import string
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
ubyte ci word bb = -15
ubyte from=10 bb /= 4
ubyte end=1 txt.print_w(bb)
for ci in from to end {
txt.print_ub(ci)
txt.spc()
}
txt.nl() txt.nl()
bb = 15
bb /= 4
txt.print_w(bb)
txt.nl()
uword ubb = 15
ubb /= 4
txt.print_uw(ubb)
txt.nl()
recurse1()
}
sub recurse1() {
recurse2()
}
sub recurse2() {
start()
} }
} }