1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-11-27 20:52:43 +00:00

Callgraph uses reachability.

This commit is contained in:
Chris Pressey 2019-10-22 09:07:16 +01:00
parent 1098347fa5
commit 1df6941b01
4 changed files with 99 additions and 47 deletions

View File

@ -5,9 +5,9 @@ History of SixtyPical
---- ----
* The reference implementation constructs a callgraph and * The reference implementation constructs a callgraph and
approximates the set of routines which are not called determines the set of routines which are not reachable
by any other routine, with an eye to omitting them from (directly or indirectly) from `main`, with an eye to
the final executable. omitting them from the final executable.
* A routine can be declared `preserved`, which prevents a * A routine can be declared `preserved`, which prevents a
compiler from omitting it from the final executable, even compiler from omitting it from the final executable, even
if it determines it is not called by any other routine. if it determines it is not called by any other routine.

View File

@ -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 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. 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 Implementation
-------------- --------------

View File

@ -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_)] 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): def construct_callgraph(program):
graph = {} graph = {}
@ -33,8 +42,19 @@ def construct_callgraph(program):
potential_calls = node['potentially-calls'] potential_calls = node['potentially-calls']
if routine.name in potential_calls: if routine.name in potential_calls:
potentially_called_by.append(name) 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 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 return graph

View File

@ -11,22 +11,23 @@ called.
-> Tests for functionality "Dump callgraph info for SixtyPical program" -> Tests for functionality "Dump callgraph info for SixtyPical program"
The `main` routine is always called. The thing that it will The `main` routine is always called. The thing that it will
be called by is the system, but the callgraph analyzer will be called by is the system, but the callgraph analyzer simply
simply consider it to be "marked as preserved". considers it to be "reachable".
| define main routine | define main routine
| { | {
| } | }
= { = {
= "main": { = "main": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*" = "potentially-calls": [],
= ], = "reachable": true
= "potentially-calls": []
= } = }
= } = }
If a routine is called by another routine, this fact will be noted. 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 | define main routine
| { | {
@ -38,25 +39,25 @@ If a routine is called by another routine, this fact will be noted.
| } | }
= { = {
= "main": { = "main": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*"
= ],
= "potentially-calls": [ = "potentially-calls": [
= "other" = "other"
= ] = ],
= "reachable": true
= }, = },
= "other": { = "other": {
= "potentially-called-by": [ = "potentially-called-by": [
= "main" = "main"
= ], = ],
= "potentially-calls": [] = "potentially-calls": [],
= "reachable": true
= } = }
= } = }
If a routine is not called by another routine, and it is not `main` If a routine is not potentially called by any other routine that is
and it is not explicitly marked as preserved, this absence will be ultimately potentially called by `main`, this absence will be noted
noted, and a compiler or linker will be permitted to omit it from — the routine will not be considered reachable — and a compiler or
the final executable. linker will be permitted to omit it from the final executable.
| define main routine | define main routine
| { | {
@ -67,10 +68,9 @@ the final executable.
| } | }
= { = {
= "main": { = "main": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*" = "potentially-calls": [],
= ], = "reachable": true
= "potentially-calls": []
= }, = },
= "other": { = "other": {
= "potentially-called-by": [], = "potentially-called-by": [],
@ -79,11 +79,11 @@ the final executable.
= } = }
If a routine is not called by another routine, but it is declared If a routine is not called by another routine, but it is declared
explicitly as `preserved`, then it will not be considered unused, explicitly as `preserved`, then it will still be considered
and a compiler or linker will not be permitted to omit it from reachable, and a compiler or linker will not be permitted to omit it
the final executable. This is useful for interrupt routines and from the final executable. This is useful for interrupt routines
such that really are used by some part of the system, even if not and such that really are used by some part of the system, even if
directly by another SixtyPical routine. not directly by another SixtyPical routine.
| define main routine | define main routine
| { | {
@ -94,22 +94,58 @@ directly by another SixtyPical routine.
| } | }
= { = {
= "main": { = "main": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*" = "potentially-calls": [],
= ], = "reachable": true
= "potentially-calls": []
= }, = },
= "other": { = "other": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*" = "potentially-calls": [],
= ], = "reachable": true
= "potentially-calls": []
= } = }
= } = }
If two routines potentially call each other, this will be noted, If a routine is called from a preserved routine, that routine is
even if nothing else potentially calls either of those routines. reachable.
This may change in the future.
| 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 | define main routine
| { | {
@ -126,10 +162,9 @@ This may change in the future.
| } | }
= { = {
= "main": { = "main": {
= "potentially-called-by": [ = "potentially-called-by": [],
= "*preserved*" = "potentially-calls": [],
= ], = "reachable": true
= "potentially-calls": []
= }, = },
= "other1": { = "other1": {
= "potentially-called-by": [ = "potentially-called-by": [