diff options
-rw-r--r-- | build-aux/measurestack/analyze.py | 46 | ||||
-rw-r--r-- | build-aux/measurestack/util.py | 4 |
2 files changed, 45 insertions, 5 deletions
diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index 86c51f2..3601707 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -3,6 +3,7 @@ # Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later +import random import re import sys import typing @@ -13,6 +14,8 @@ from . import vcg dbg_dumpgraph = False # Whether to print "//dbg-nstatic:" lines that trace nstatic() execution dbg_nstatic = False +# Whether to sort things for consistently-ordered execution, or shuffle things to detect bugs +dbg_sort: typing.Literal["unsorted", "sorted", "shuffled"] = "unsorted" # pylint: disable=unused-variable __all__ = [ @@ -20,6 +23,7 @@ __all__ = [ "QName", "UsageKind", "Node", + "maybe_sorted", "AnalyzeResultVal", "AnalyzeResultGroup", "AnalyzeResult", @@ -306,6 +310,39 @@ class _Graph: return self._resolve_cache[funcname] +if typing.TYPE_CHECKING: + from _typeshed import SupportsRichComparisonT as _T_sortable + +_T = typing.TypeVar("_T") + + +@typing.overload +def maybe_sorted( + unsorted: typing.Iterable["_T_sortable"], /, *, key: None = None +) -> typing.Iterable["_T_sortable"]: ... +@typing.overload +def maybe_sorted( + unsorted: typing.Iterable[_T], /, *, key: typing.Callable[[_T], "_T_sortable"] +) -> typing.Iterable[_T]: ... + + +def maybe_sorted( + unsorted: typing.Iterable[_T], + /, + *, + key: typing.Callable[[_T], "_T_sortable"] | None = None, +) -> typing.Iterable[_T]: + match dbg_sort: + case "unsorted": + return unsorted + case "sorted": + return sorted(unsorted, key=key) # type: ignore + case "shuffled": + ret = [*unsorted] + random.shuffle(ret) + return ret + + def _make_graph( ci_fnames: typing.Collection[str], app: Application, @@ -368,7 +405,7 @@ def _make_graph( raise ValueError(f"unknown caller: {caller}") if callee == QName("__indirect_call"): callees, missing_ok = app.indirect_callees(elem) - for callee in callees: + for callee in maybe_sorted(callees): if callee not in graph[caller].calls: graph[caller].calls[callee] = missing_ok else: @@ -376,12 +413,15 @@ def _make_graph( case _: raise ValueError(f"unknown elem type {elem.typ!r}") - for ci_fname in ci_fnames: + for ci_fname in maybe_sorted(ci_fnames): with open(ci_fname, "r", encoding="utf-8") as fh: for elem in vcg.parse_vcg(fh): handle_elem(elem) - for node in app.extra_nodes(): + def sort_key(node: Node) -> QName: + return node.funcname + + for node in maybe_sorted(app.extra_nodes(), key=sort_key): if node.funcname in graph: raise ValueError(f"duplicate node {node.funcname}") graph[node.funcname] = node diff --git a/build-aux/measurestack/util.py b/build-aux/measurestack/util.py index 47b2617..0af3d02 100644 --- a/build-aux/measurestack/util.py +++ b/build-aux/measurestack/util.py @@ -7,7 +7,7 @@ import re import typing from . import analyze, vcg -from .analyze import BaseName, Node, QName +from .analyze import BaseName, Node, QName, maybe_sorted # pylint: disable=unused-variable __all__ = [ @@ -32,7 +32,7 @@ def synthetic_node( n.nstatic = nstatic n.ndynamic = 0 - n.calls = dict((QName(c), False) for c in calls) + n.calls = dict((QName(c), False) for c in maybe_sorted(calls)) return n |