git @ Cat's Eye Technologies SixtyPical / master src / sixtypical / callgraph.py
master

Tree @master (Download .tar.gz)

callgraph.py @masterraw · history · blame

# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license.  See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical

from sixtypical.ast import Program
from sixtypical.model import RoutineType, VectorType


def find_routines_matching_type(program, type_):
    """Return the subset of routines of the program whose value
    may be assigned to a location of the given type_ (typically
    a vector)."""
    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 = {}

    for routine in program.routines:
        potentially_calls = []
        for (called_routine, called_routine_type) in routine.called_routines:
            if isinstance(called_routine_type, RoutineType):
                potentially_calls.append(called_routine.name)
            elif isinstance(called_routine_type, VectorType):
                for potentially_called in find_routines_matching_type(program, called_routine_type):
                    potentially_calls.append(potentially_called.name)
            else:
                raise NotImplementedError
        graph[routine.name] = {
            'potentially-calls': potentially_calls,
        }

    # Symmetric closure
    # (Note, this information isn't used anywhere yet)

    for routine in program.routines:
        potentially_called_by = []
        for (name, node) in graph.items():
            potential_calls = node['potentially-calls']
            if routine.name in potential_calls:
                potentially_called_by.append(name)
        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


def prune_unreachable_routines(program, callgraph):
    return Program(1, defns=program.defns, routines=[
        r for r in program.routines if callgraph[r.name].get('reachable', False)
    ])