176 lines
5.8 KiB
Python
176 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard _PyObject_DebugMallocStats C89 declarations and evaluation order."""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
source = Path(sys.argv[1]).read_text()
|
|
match = re.search(
|
|
r"\n_PyObject_DebugMallocStats\(FILE \*out\)\s*\{(.*?)\n\}",
|
|
source, re.S,
|
|
)
|
|
assert match, "_PyObject_DebugMallocStats definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
|
declarations = r"""
|
|
uint i;
|
|
uint numclasses;
|
|
size_t numpools[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
|
size_t numblocks[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
|
size_t numfreeblocks[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
|
size_t allocated_bytes;
|
|
size_t available_bytes;
|
|
uint numfreepools;
|
|
size_t arena_alignment;
|
|
size_t pool_header_bytes;
|
|
size_t quantization;
|
|
size_t narenas;
|
|
size_t total;
|
|
char buf[128];
|
|
"""
|
|
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
|
"_PyObject_DebugMallocStats must hoist only uninitialized declarations "
|
|
"before the pymalloc guard, preserving array extents"
|
|
)
|
|
|
|
# Freeze the guard before all scalar assignments, their original order, and
|
|
# the entire remaining body, including array zeroing and conditional paths.
|
|
expected = declarations + r"""
|
|
if (!_PyMem_PymallocEnabled()) {
|
|
return 0;
|
|
}
|
|
numclasses = SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT;
|
|
allocated_bytes = 0;
|
|
available_bytes = 0;
|
|
numfreepools = 0;
|
|
arena_alignment = 0;
|
|
pool_header_bytes = 0;
|
|
quantization = 0;
|
|
narenas = 0;
|
|
fprintf(out, "Small block threshold = %d, in %u size classes.\n",
|
|
SMALL_REQUEST_THRESHOLD, numclasses);
|
|
|
|
for (i = 0; i < numclasses; ++i)
|
|
numpools[i] = numblocks[i] = numfreeblocks[i] = 0;
|
|
|
|
|
|
for (i = 0; i < maxarenas; ++i) {
|
|
uintptr_t base = arenas[i].address;
|
|
|
|
|
|
if (arenas[i].address == (uintptr_t)NULL)
|
|
continue;
|
|
narenas += 1;
|
|
|
|
numfreepools += arenas[i].nfreepools;
|
|
|
|
|
|
if (base & (uintptr_t)POOL_SIZE_MASK) {
|
|
arena_alignment += POOL_SIZE;
|
|
base &= ~(uintptr_t)POOL_SIZE_MASK;
|
|
base += POOL_SIZE;
|
|
}
|
|
|
|
|
|
assert(base <= (uintptr_t) arenas[i].pool_address);
|
|
for (; base < (uintptr_t) arenas[i].pool_address; base += POOL_SIZE) {
|
|
poolp p = (poolp)base;
|
|
const uint sz = p->szidx;
|
|
uint freeblocks;
|
|
|
|
if (p->ref.count == 0) {
|
|
|
|
#ifdef Py_DEBUG
|
|
assert(pool_is_in_list(p, arenas[i].freepools));
|
|
#endif
|
|
continue;
|
|
}
|
|
++numpools[sz];
|
|
numblocks[sz] += p->ref.count;
|
|
freeblocks = NUMBLOCKS(sz) - p->ref.count;
|
|
numfreeblocks[sz] += freeblocks;
|
|
#ifdef Py_DEBUG
|
|
if (freeblocks > 0)
|
|
assert(pool_is_in_list(p, usedpools[sz + sz]));
|
|
#endif
|
|
}
|
|
}
|
|
assert(narenas == narenas_currently_allocated);
|
|
|
|
fputc('\n', out);
|
|
fputs("class size num pools blocks in use avail blocks\n"
|
|
"----- ---- --------- ------------- ------------\n",
|
|
out);
|
|
|
|
for (i = 0; i < numclasses; ++i) {
|
|
size_t p = numpools[i];
|
|
size_t b = numblocks[i];
|
|
size_t f = numfreeblocks[i];
|
|
uint size = INDEX2SIZE(i);
|
|
if (p == 0) {
|
|
assert(b == 0 && f == 0);
|
|
continue;
|
|
}
|
|
fprintf(out, "%5u %6u %11zu %15zu %13zu\n",
|
|
i, size, p, b, f);
|
|
allocated_bytes += b * size;
|
|
available_bytes += f * size;
|
|
pool_header_bytes += p * POOL_OVERHEAD;
|
|
quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size);
|
|
}
|
|
fputc('\n', out);
|
|
#ifdef PYMEM_DEBUG_SERIALNO
|
|
if (_PyMem_DebugEnabled()) {
|
|
(void)printone(out, "# times object malloc called", serialno);
|
|
}
|
|
#endif
|
|
(void)printone(out, "# arenas allocated total", ntimes_arena_allocated);
|
|
(void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas);
|
|
(void)printone(out, "# arenas highwater mark", narenas_highwater);
|
|
(void)printone(out, "# arenas allocated current", narenas);
|
|
|
|
PyOS_snprintf(buf, sizeof(buf),
|
|
"%zu arenas * %d bytes/arena",
|
|
narenas, ARENA_SIZE);
|
|
(void)printone(out, buf, narenas * ARENA_SIZE);
|
|
|
|
fputc('\n', out);
|
|
|
|
|
|
total = printone(out, "# bytes in allocated blocks", allocated_bytes);
|
|
total += printone(out, "# bytes in available blocks", available_bytes);
|
|
|
|
PyOS_snprintf(buf, sizeof(buf),
|
|
"%u unused pools * %d bytes", numfreepools, POOL_SIZE);
|
|
total += printone(out, buf, (size_t)numfreepools * POOL_SIZE);
|
|
|
|
total += printone(out, "# bytes lost to pool headers", pool_header_bytes);
|
|
total += printone(out, "# bytes lost to quantization", quantization);
|
|
total += printone(out, "# bytes lost to arena alignment", arena_alignment);
|
|
(void)printone(out, "Total", total);
|
|
assert(narenas * ARENA_SIZE == total);
|
|
|
|
#if WITH_PYMALLOC_RADIX_TREE
|
|
fputs("\narena map counts\n", out);
|
|
#ifdef USE_INTERIOR_NODES
|
|
(void)printone(out, "# arena map mid nodes", arena_map_mid_count);
|
|
(void)printone(out, "# arena map bot nodes", arena_map_bot_count);
|
|
fputc('\n', out);
|
|
#endif
|
|
total = printone(out, "# bytes lost to arena map root", sizeof(arena_map_root));
|
|
#ifdef USE_INTERIOR_NODES
|
|
total += printone(out, "# bytes lost to arena map mid",
|
|
sizeof(arena_map_mid_t) * arena_map_mid_count);
|
|
total += printone(out, "# bytes lost to arena map bot",
|
|
sizeof(arena_map_bot_t) * arena_map_bot_count);
|
|
(void)printone(out, "Total", total);
|
|
#endif
|
|
#endif
|
|
|
|
return 1;"""
|
|
assert body.split() == expected.split(), (
|
|
"preserve _PyObject_DebugMallocStats early return, initialization/evaluation "
|
|
"points, array contents, and complete operation order"
|
|
)
|
|
print("obmalloc _PyObject_DebugMallocStats C89 declarations and operation order: PASS")
|