diff --git a/memhub.vp b/memhub.vp index 5ebe64b..ca7105a 100644 --- a/memhub.vp +++ b/memhub.vp @@ -1444,12 +1444,15 @@ class MemBuddy(MemManager): order: int = int(stored_val >> 1) if order < 0: return 0 if order > self.max_order: return 0 - # Live-set check: every alloc pushes its block onto chk_stack, and reset_to(mark) - # pops [mark, chk_depth) and frees those blocks. A stale pointer with a still-odd - # header would pass the checks above, so require the block to sit in the live - # window [0, chk_depth). Once chk_stack is full (chk_depth == chk_cap) new blocks - # are no longer recorded, so the check is skipped to avoid rejecting valid frees. - if self.chk_stack is not None and self.chk_depth < self.chk_cap: + # Live-set check (diagnostic, --detection only): every alloc pushes its block onto + # chk_stack, and reset_to(mark) pops [mark, chk_depth) and frees those blocks. A stale + # pointer with a still-odd header would pass the checks above, so require the block to + # sit in the live window [0, chk_depth). The scan is O(chk_depth): a pool that is never + # reset (PermPool) grows the window without bound (5M entries observed), so running it on + # every free made the type-interning frees O(N^2) and stretched one file's translation to + # minutes. The check is the same class of aid as --detection, so it is gated on it; the + # production path stays O(1) and still rejects re-frees via the header parity test above. + if self.detection != 0 and self.chk_stack is not None and self.chk_depth < self.chk_cap: alive: int = 0 for ci in range(int(self.chk_depth)): if self.chk_stack[ci] == uint64(block):