diff options
Diffstat (limited to 'build-aux/measurestack/analyze.py')
-rw-r--r-- | build-aux/measurestack/analyze.py | 123 |
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(): |