summaryrefslogtreecommitdiff
path: root/build-aux/measurestack/analyze.py
diff options
context:
space:
mode:
Diffstat (limited to 'build-aux/measurestack/analyze.py')
-rw-r--r--build-aux/measurestack/analyze.py123
1 files changed, 90 insertions, 33 deletions
diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py
index 8100813..a93874f 100644
--- a/build-aux/measurestack/analyze.py
+++ b/build-aux/measurestack/analyze.py
@@ -150,12 +150,38 @@ class AnalyzeResult(typing.NamedTuple):
included_funcs: set[QName]
+class SkipModel(typing.NamedTuple):
+ """Running the skipmodel calls `.fn(chain, ...)` with the chain
+ consisting of the last `.nchain` items (if .nchain is an int), or
+ the chain starting with the *last* occurance of `.nchain` (if
+ .nchain is a collection). If the chain is not that long or does
+ not contain a member of the collection, then .fn is not called and
+ the call is *not* skipped.
+
+ """
+
+ nchain: int | typing.Collection[BaseName]
+ fn: typing.Callable[[typing.Sequence[QName], QName], bool]
+
+ def __call__(self, chain: typing.Sequence[QName], call: QName) -> tuple[bool, int]:
+ if isinstance(self.nchain, int):
+ if len(chain) >= self.nchain:
+ _chain = chain[-self.nchain :]
+ return self.fn(_chain, call), len(_chain)
+ else:
+ for i in reversed(range(len(chain))):
+ if chain[i].base() in self.nchain:
+ _chain = chain[i - 1 :]
+ return self.fn(_chain, call), len(_chain)
+ return False, 0
+
+
class Application(typing.Protocol):
def extra_nodes(self) -> typing.Collection[Node]: ...
def indirect_callees(
self, elem: vcg.VCGElem
) -> tuple[typing.Collection[QName], bool]: ...
- def skip_call(self, chain: typing.Sequence[QName], funcname: QName) -> bool: ...
+ def skipmodels(self) -> dict[BaseName, SkipModel]: ...
# code #########################################################################
@@ -315,31 +341,20 @@ def analyze(
track_inclusion: bool = True
- def nstatic(
- orig_funcname: QName,
- chain: typing.Sequence[QName] = (),
- missing_ok: bool = False,
- ) -> int:
+ skipmodels = app.skipmodels()
+ for name, model in skipmodels.items():
+ if isinstance(model.nchain, int):
+ assert model.nchain > 1
+ else:
+ assert len(model.nchain) > 0
+
+ _nstatic_cache: dict[QName, int] = {}
+
+ def _nstatic(chain: list[QName], funcname: QName) -> tuple[int, int]:
nonlocal dbg
nonlocal track_inclusion
- funcname = graphdata.resolve_funcname(orig_funcname)
- if not funcname:
- if chain and app.skip_call(chain, orig_funcname):
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tskip missing")
- return 0
- if not missing_ok:
- missing.add(orig_funcname)
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tmissing")
- return 0
- if chain and app.skip_call(chain, funcname):
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tskip")
- return 0
-
- if len(chain) == cfg_max_call_depth:
- raise ValueError(f"max call depth exceeded: {[*chain, funcname]}")
+
+ assert funcname in graphdata.graph
node = graphdata.graph[funcname]
if dbg:
@@ -348,15 +363,57 @@ def analyze(
dynamic.add(funcname)
if track_inclusion:
included_funcs.add(funcname)
- return node.nstatic + max(
- [
- 0,
- *[
- nstatic(call, [*chain, funcname], missing_ok)
- for call, missing_ok in node.calls.items()
- ],
- ]
- )
+
+ max_call_nstatic = 0
+ max_call_nchain = 0
+
+ if node.calls:
+ skipmodel = skipmodels.get(funcname.base())
+ chain.append(funcname)
+ if len(chain) == cfg_max_call_depth:
+ raise ValueError(f"max call depth exceeded: {chain}")
+ for call_orig_qname, call_missing_ok in node.calls.items():
+ skip_nchain = 0
+ # 1. Resolve
+ call_qname = graphdata.resolve_funcname(call_orig_qname)
+ if not call_qname:
+ if skipmodel:
+ skip, _ = skipmodel(chain, call_orig_qname)
+ if skip:
+ if dbg:
+ print(
+ f"//dbg: {'- '*len(chain)}{call_orig_qname}\tskip missing"
+ )
+ continue
+ if not call_missing_ok:
+ missing.add(call_orig_qname)
+ if dbg:
+ print(f"//dbg: {'- '*len(chain)}{call_orig_qname}\tmissing")
+ continue
+
+ # 2. Skip
+ if skipmodel:
+ skip, skip_nchain = skipmodel(chain, call_qname)
+ max_call_nchain = max(max_call_nchain, skip_nchain)
+ if skip:
+ if dbg:
+ print(f"//dbg: {'- '*len(chain)}{call_qname}\tskip")
+ continue
+
+ # 3. Call
+ if skip_nchain == 0 and call_qname in _nstatic_cache:
+ max_call_nstatic = max(max_call_nstatic, _nstatic_cache[call_qname])
+ else:
+ call_nstatic, call_nchain = _nstatic(chain, call_qname)
+ max_call_nstatic = max(max_call_nstatic, call_nstatic)
+ max_call_nchain = max(max_call_nchain, call_nchain)
+ if skip_nchain == 0 and call_nchain == 0:
+ _nstatic_cache[call_qname] = call_nstatic
+ chain.pop()
+ return node.nstatic + max_call_nstatic, max(0, max_call_nchain - 1)
+
+ def nstatic(funcname: QName) -> int:
+ return _nstatic([], funcname)[0]
groups: dict[str, AnalyzeResultGroup] = {}
for grp_name, grp_filter in app_func_filters.items():