diff --git a/HISTORY.md b/HISTORY.md index d5ca93c..318419c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,9 +5,9 @@ History of SixtyPical ---- * The reference implementation constructs a callgraph and - approximates the set of routines which are not called - by any other routine, with an eye to omitting them from - the final executable. + determines the set of routines which are not reachable + (directly or indirectly) from `main`, with an eye to + omitting them from the final executable. * A routine can be declared `preserved`, which prevents a compiler from omitting it from the final executable, even if it determines it is not called by any other routine. diff --git a/TODO.md b/TODO.md index 1761bde..036555c 100644 --- a/TODO.md +++ b/TODO.md @@ -102,9 +102,6 @@ Once we have a call graph we can omit routines that we're sure aren't called. This would let us use include-files and standard-libraries nicely: any routines they define, but that you don't use, don't get included. -Analyzing the set of possible routines that a vector can take on would help -this immensely. - Implementation -------------- diff --git a/src/sixtypical/callgraph.py b/src/sixtypical/callgraph.py index bf8b39d..b91e312 100644 --- a/src/sixtypical/callgraph.py +++ b/src/sixtypical/callgraph.py @@ -8,6 +8,15 @@ def find_routines_matching_type(program, type_): return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)] +def mark_as_reachable(graph, routine_name): + node = graph[routine_name] + if node.get('reachable', False): + return + node['reachable'] = True + for next_routine_name in node['potentially-calls']: + mark_as_reachable(graph, next_routine_name) + + def construct_callgraph(program): graph = {} @@ -33,8 +42,19 @@ def construct_callgraph(program): potential_calls = node['potentially-calls'] if routine.name in potential_calls: potentially_called_by.append(name) - if getattr(routine, 'preserved', None) or routine.name == 'main': - potentially_called_by.append('*preserved*') graph[routine.name]['potentially-called-by'] = potentially_called_by + # Root set + + root_set = set() + + for routine in program.routines: + if getattr(routine, 'preserved', False) or routine.name == 'main': + root_set.add(routine) + + # Reachability + + for routine in root_set: + mark_as_reachable(graph, routine.name) + return graph diff --git a/tests/SixtyPical Callgraph.md b/tests/SixtyPical Callgraph.md index 62d5ff6..b703511 100644 --- a/tests/SixtyPical Callgraph.md +++ b/tests/SixtyPical Callgraph.md @@ -11,22 +11,23 @@ called. -> Tests for functionality "Dump callgraph info for SixtyPical program" The `main` routine is always called. The thing that it will -be called by is the system, but the callgraph analyzer will -simply consider it to be "marked as preserved". +be called by is the system, but the callgraph analyzer simply +considers it to be "reachable". | define main routine | { | } = { = "main": { - = "potentially-called-by": [ - = "*preserved*" - = ], - = "potentially-calls": [] + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true = } = } If a routine is called by another routine, this fact will be noted. +If it is reachable (directly or indirectly) from `main`, this will +be noted as well. | define main routine | { @@ -38,25 +39,25 @@ If a routine is called by another routine, this fact will be noted. | } = { = "main": { - = "potentially-called-by": [ - = "*preserved*" - = ], + = "potentially-called-by": [], = "potentially-calls": [ = "other" - = ] + = ], + = "reachable": true = }, = "other": { = "potentially-called-by": [ = "main" = ], - = "potentially-calls": [] + = "potentially-calls": [], + = "reachable": true = } = } -If a routine is not called by another routine, and it is not `main` -and it is not explicitly marked as preserved, this absence will be -noted, and a compiler or linker will be permitted to omit it from -the final executable. +If a routine is not potentially called by any other routine that is +ultimately potentially called by `main`, this absence will be noted +— the routine will not be considered reachable — and a compiler or +linker will be permitted to omit it from the final executable. | define main routine | { @@ -67,10 +68,9 @@ the final executable. | } = { = "main": { - = "potentially-called-by": [ - = "*preserved*" - = ], - = "potentially-calls": [] + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true = }, = "other": { = "potentially-called-by": [], @@ -79,11 +79,11 @@ the final executable. = } If a routine is not called by another routine, but it is declared -explicitly as `preserved`, then it will not be considered unused, -and a compiler or linker will not be permitted to omit it from -the final executable. This is useful for interrupt routines and -such that really are used by some part of the system, even if not -directly by another SixtyPical routine. +explicitly as `preserved`, then it will still be considered +reachable, and a compiler or linker will not be permitted to omit it +from the final executable. This is useful for interrupt routines +and such that really are used by some part of the system, even if +not directly by another SixtyPical routine. | define main routine | { @@ -94,22 +94,58 @@ directly by another SixtyPical routine. | } = { = "main": { - = "potentially-called-by": [ - = "*preserved*" - = ], - = "potentially-calls": [] + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true = }, = "other": { - = "potentially-called-by": [ - = "*preserved*" - = ], - = "potentially-calls": [] + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true = } = } -If two routines potentially call each other, this will be noted, -even if nothing else potentially calls either of those routines. -This may change in the future. +If a routine is called from a preserved routine, that routine is +reachable. + + | define main routine + | { + | } + | + | define other1 preserved routine + | { + | call other2 + | } + | + | define other2 preserved routine + | { + | } + = { + = "main": { + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true + = }, + = "other1": { + = "potentially-called-by": [], + = "potentially-calls": [ + = "other2" + = ], + = "reachable": true + = }, + = "other2": { + = "potentially-called-by": [ + = "other1" + = ], + = "potentially-calls": [], + = "reachable": true + = } + = } + +If a group of routines potentially call each other, but neither is +found to be reachable (directly or indirectly) from `main` or a +`preserved` routine, the routines in the group will not be considered +reachable. | define main routine | { @@ -126,10 +162,9 @@ This may change in the future. | } = { = "main": { - = "potentially-called-by": [ - = "*preserved*" - = ], - = "potentially-calls": [] + = "potentially-called-by": [], + = "potentially-calls": [], + = "reachable": true = }, = "other1": { = "potentially-called-by": [