1317 lines
71 KiB
Plaintext
1317 lines
71 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import string
|
||
import atom
|
||
import viperio
|
||
import stdio
|
||
import stdlib
|
||
|
||
# ============================================================
|
||
# memhub - Unified memory management library
|
||
# MemManager (polymorphic base class, vtable)
|
||
# ├─ MemPool (arena bump allocator, no support for individual free)
|
||
# ├─ MemSlab (fixed-size block pool, alloc/free tracked via bitmap)
|
||
# └─ MemBuddy (buddy system, supports alloc/free/realloc with coalescing)
|
||
# All subclasses override alloc/free/reset via vtable, invokable polymorphically through MemManager pointer
|
||
# ============================================================
|
||
|
||
MEMHUB_ALIGN: t.CDefine = 8
|
||
|
||
# MemSlab bitmap constants
|
||
MEMSLAB_MIN_BLOCK: t.CDefine = 16
|
||
MEMSLAB_BITMAP_BYTES: t.CDefine = 256
|
||
|
||
# MemBuddy buddy constants
|
||
MEMBUDDY_MIN_BLOCK: t.CDefine = 32
|
||
MEMBUDDY_MAX_ORDERS: t.CDefine = 32
|
||
MEMBUDDY_HEADER_SIZE: t.CDefine = 8
|
||
# 块尾 canary:alloc 时在块尾(用户数据区之外)写 8 字节哨兵,
|
||
# check_alive_blocks 校验——越界写穿数据区会破坏 canary,把
|
||
# "崩溃点漂移/事发地遥远"变成"确定性定位到越界写发生的文件"
|
||
MEMBUDDY_CANARY: t.CDefine = 8
|
||
|
||
# 分配守卫(诊断,SlotsBugTest 二代编 list 头覆盖排查):
|
||
# alloc 时把用户数据区填充 0xCD 哨兵。若对象初始化(list.__init__ 等)
|
||
# 未执行,其字段保持 0xCDCD...,CTORDBG/VDBG 打印字段时一眼可辨
|
||
# "未初始化(0xCDCD)" 与 "被覆盖(其他值)"。0=关闭(性能),1=开启。
|
||
# 热点优化(实测):默认开启时每次 alloc 都 memset 用户数据区(约 2278 万次,
|
||
# 占全程序 memset 的 68%),纯诊断用途。默认改为 0(关闭);排查未初始化读取
|
||
# 时置 1 即可——开启后填充代码逐字节不变(沿用 MEMBUDDY_THREAD_SAFE 的编译期
|
||
# 开关范式)。关闭不破坏 self_check/canary 不变量:块尾哨兵取自
|
||
# MEMBUDDY_CANARY/MEMBUDDY_CANARY_PATTERN,free 后填充由 detection 分支
|
||
# (detection!=0)控制,二者与 FILL_GUARD 解耦。
|
||
MEMBUDDY_FILL_GUARD: t.CDefine = 0
|
||
MEMBUDDY_FILL_PATTERN: t.CDefine = 0xCD
|
||
|
||
# [DET] 分配检测(alloc 前后 2 字节 0xCDCD 标记 + 记录表探测越界写):
|
||
# 检测模式(MemBuddy(..., detection=1))下每次 alloc 布局为
|
||
# [0xCDCD][请求大小内存][0xCDCD](实际比请求多 4 字节)
|
||
# 返回指针 = 实际用户区 + 2(前标记之后);回收时向前偏移 2 字节。
|
||
# 每次 alloc 后探测记录表全部后标记(ptr+size 处)——被破坏 = 越界写,
|
||
# 立即汇报 ptr/size 并终止(定位"存活块被部分覆盖"的写入者,
|
||
# 如 SlotsBugTest KwEntry cargs 只写 Data/IterIndex 的块归属)。
|
||
MEMBUDDY_DET_MARKER: t.CDefine = 0xCDCD
|
||
# 检测记录区默认大小(32MB,每条记录 {ptr u64, size u64} = 16B)
|
||
MEMBUDDY_DET_RECORD_SIZE: t.CDefine = 33554432
|
||
# 采样周期(2 的幂):每 N 次 alloc 做一次全表扫描(分配重叠 + 探测)。
|
||
# 记录追加本身 O(1) 不受影响;损坏是持久性的,不依赖即时扫描,采样
|
||
# 不损失检测能力——2M 次 alloc 若每次全表 O(n) 扫描 = O(n²) 太慢
|
||
# (用户反馈 detection 模式过慢),采样把扫描次数降为 1/N。
|
||
# 运行期覆盖:MemBuddy 实例字段 deep_detect != 0(命令行 --detection-deep,
|
||
# 见 main.vp 的 strstr 取值与 MemBuddy(...) 传参)时忽略本周期、等价于 1
|
||
# (= 每次 alloc 都探测),判定点见本文件 alloc 内 L856(_det_probe)与
|
||
# L800-801(周期全表扫描)两处。默认 32:未开该开关时掩码 x & 31 与历史
|
||
# 实现逐字节一致(deep_detect 恒 0,见 L801 / L856 两处判定写法)。
|
||
MEMBUDDY_DET_PROBE_PERIOD: t.CDefine = 32
|
||
|
||
# [热点] 周期全表扫描总开关(编译期,默认 0=关闭;范式同 MEMBUDDY_THREAD_SAFE/
|
||
# MEMBUDDY_FILL_GUARD——常量比较不依赖运行期取值,默认 0 时该分支不执行)。
|
||
# alloc 每 MEMBUDDY_DET_PROBE_PERIOD 次分配会执行两项 O(n) 全扫描,其耗时全部
|
||
# 落在 MemBuddy.alloc 的 self 内(无外部调用可分摊):
|
||
# 1. 记录表重叠检测:while 遍历 _det_records[0 .. _det_count),_det_count ≈ 未回收
|
||
# 的分配数。量级实测:reset_to 每次回收约 3.15e4 块(_coalesce 22.547e6 次 /
|
||
# 716 次 reset_to)、check_alive_blocks 每次校验约 3.94e4 活块
|
||
# (_block_size_at_order 6.27e6 次 / 159 次),即 _det_count ~3e4;
|
||
# 2. [MEMDBG] 空闲链残留扫描:遍历本 order free list(受 _memdbg_enable 控制)。
|
||
# 实测 MemBuddy.alloc self=681.85e9 tick / 25,419,348 次 ≈ 26,824 tick/次,而该
|
||
# 采样块每 32 次才进入一次 ⇒ 单次采样块约 8.6e5 tick,alloc 函数体其余部分
|
||
# (O(1) 块头/canary 写入 + chk_stack/记录追加)占比可忽略;按 32 采样只把次数
|
||
# 降为 1/32,并不改变 O(n²/PERIOD) 的量级(n=_det_count)。
|
||
# ⇒ 默认 0:alloc 回到纯 O(1) 分配路径(该分支不执行)。排查重复分配 / free list
|
||
# 残留 / 越界写时置 1(同时需 _memdbg_enable 才有第 2 项检查)。
|
||
# 置 1 时该分支与历史实现逐字节一致;该分支除诊断 printf+fflush+exit 外无任何
|
||
# 状态写入,故关闭只少诊断、不改变分配器语义。
|
||
# 运行期覆盖:MemBuddy 实例字段 deep_detect != 0(命令行 --detection-deep)可在
|
||
# 不改本编译期常量的前提下打开该扫描,判定点见本文件 alloc 内 L800 的
|
||
# "MEMBUDDY_PERIOD_SCAN != 0 or self.deep_detect != 0"(配合 L801 掩码短路
|
||
# → 每次 alloc 都跑该全扫描)。默认 0 = 与历史实现一致:该诊断分支不执行。
|
||
MEMBUDDY_PERIOD_SCAN: t.CDefine = 0
|
||
|
||
# [MEMDBG] 临时插桩(2026-09-04,定位 free list 重复 put / 悬空 free / alloc
|
||
# 重复出块;定位后整体移除,勿长期保留)。改动点:
|
||
# 1. alloc:采样分支内校验"新出块仍残留于同 order free list";
|
||
# 2. free :归还前校验"同 order free list 已含本块"(double-put 实锤);
|
||
# 3. free :无效/悬空指针(池内但 header 偶数=已释放)不再静默忽略;
|
||
# 4. 既有 [DET]/[MEM] 终止路径补 stdio.fflush(None)(stdout 重定向丢输出)。
|
||
MEMBUDDY_DBG: t.CDefine = 1
|
||
# [MEMDBG] 运行时开关:默认开(随 MEMBUDDY_DBG 编译默认值)。
|
||
# 需要让负向用例(如 BuddyTest 故意双重 free,期望"静默拒绝")不终止时,
|
||
# 可在运行期关闭(沿用 ast._mbuddy/sys._mbuddy 式跨模块全局写法):
|
||
# import memhub
|
||
# memhub._memdbg_enable = 0
|
||
_memdbg_enable: int = MEMBUDDY_DBG
|
||
|
||
# [锁] 线程安全编译期开关:默认 0 = 单线程(关闭自旋锁,_lock/_unlock 为空体)。
|
||
# 自举编译等单线程场景下每次 alloc 省去一次原子 xchg + 一次原子 store
|
||
# (实测 _lock/_unlock/__atomic_test_and_set/__atomic_clear 各约 2026 万次,
|
||
# 且在单线程下为纯开销)。置 1 = 多线程(保留原有原子自旋加解锁)。
|
||
# 选编译期(t.CDefine 常量)而非运行期开关的理由:常量比较在 HandleIf 处被
|
||
# 常量折叠,未取分支整体不生成代码 ⇒ _lock/_unlock 退化为空函数,真正消除
|
||
# 原子开销;运行期开关仍需每次分配做一次全局读 + 分支。既有 _memdbg_enable
|
||
# 用运行期是因为它要支持"同一次运行内切换"(负向用例),而线程模型在编译期
|
||
# 即已固定。多线程使用 MemBuddy 必须置 1(语义与历史实现逐字节一致)。
|
||
MEMBUDDY_THREAD_SAFE: t.CDefine = 0
|
||
|
||
# 块尾 canary 常量(0xA5A5A5A5A5A5A5A5,64 位)——热点优化(实测):原
|
||
# _canary_pattern() 函数被调用约 2658 万次(每次 alloc 写块尾 + check_alive_blocks
|
||
# 校验),改为 t.CDefine 后调用点直接内联为立即数,消除全部函数调用。
|
||
# 值构造:lo = 0xA5A5A5A5(低 32 位,其高 32 位为 0);(lo << 32) 恰好填满高 32 位,
|
||
# 再 | lo 填低 32 位 ⇒ 0xA5A5A5A5A5A5A5A5,与原函数 (lo << 32) | lo 逐位相同。
|
||
# 注意:不写十进制大字面量(11936128518282651045)——本仓库踩过"大十进制字面量
|
||
# 被解析截断"的坑;此处沿用原函数的移位组合,由 ConstEvaluator 求值(值
|
||
# >0x7FFFFFFF 时 _make_define_constant 升 i64,与原函数返回的 t.CUInt64T 同位宽)。
|
||
MEMBUDDY_CANARY_PATTERN: t.CDefine = (0xA5A5A5A5 << 32) | 0xA5A5A5A5
|
||
|
||
|
||
def _canary_pattern() -> t.CUInt64T:
|
||
"""块尾 canary 值(0xA5A5A5A5A5A5A5A5,64 位)——统一引用编译期常量"""
|
||
return t.CUInt64T(MEMBUDDY_CANARY_PATTERN)
|
||
|
||
def _align_up(val: t.CSizeT, align: t.CSizeT) -> t.CSizeT:
|
||
if align == 0: return val
|
||
return (val + align - 1) & ~(align - 1)
|
||
|
||
|
||
def _largest_pow2_le(val: t.CSizeT) -> t.CSizeT:
|
||
if val == 0: return 0
|
||
p: t.CSizeT = 1
|
||
while p * 2 <= val:
|
||
p = p * 2
|
||
return p
|
||
|
||
|
||
def _block_size_at_order(order: t.CInt) -> t.CSizeT:
|
||
"""第 order 阶的块大小(等价于 MEMBUDDY_MIN_BLOCK << order)
|
||
|
||
热点优化(实测):本函数被调用约 1.008 亿次(占全程序调用 4.67%,第 7 名),
|
||
原实现 `for i in range(order): bs = bs << 1` 每次做 order 次移位;语义上等价于
|
||
一次左移,故直接合成,把 O(order) 降为 O(1)。
|
||
边界:order <= 0 时返回最小块——与原 range() 空循环的行为完全一致,
|
||
同时避免「负数移位」的未定义行为。
|
||
"""
|
||
if order <= 0:
|
||
return MEMBUDDY_MIN_BLOCK
|
||
return MEMBUDDY_MIN_BLOCK << t.CSizeT(order)
|
||
|
||
|
||
# ============================================================
|
||
# MemManager - Polymorphic base class
|
||
# @t.CVTable explicit annotation to ensure recognition as vtable class during cross-module imports
|
||
# (HandlesImports only detects IsCVTable via decorator; automatic inference is unavailable across modules)
|
||
# ============================================================
|
||
@t.CVTable
|
||
class MemManager:
|
||
__provides__: list[str] = ['__memmgr__']
|
||
base: t.CPtr # Starting address of memory region
|
||
size: t.CSizeT # Total size of memory region
|
||
|
||
def __init__(self, base: t.CPtr, size: t.CSizeT):
|
||
self.base = base
|
||
self.size = size
|
||
|
||
# === Virtual functions (overridden by subclasses) ===
|
||
|
||
def alloc(self, size: t.CSizeT) -> t.CPtr:
|
||
# Default: allocation not supported
|
||
return None
|
||
|
||
def free(self, ptr: t.CPtr) -> int:
|
||
# Default: no operation
|
||
return 0
|
||
|
||
def reset(self) -> int:
|
||
# Default: no operation
|
||
return 0
|
||
|
||
# === Concrete methods (invoke virtual functions for automatic polymorphic dispatch) ===
|
||
|
||
def calloc(self, count: t.CSizeT, size: t.CSizeT) -> t.CPtr:
|
||
total: t.CSizeT = count * size
|
||
ptr: t.CPtr = self.alloc(total)
|
||
if ptr is not None:
|
||
string.memset(ptr, 0, total)
|
||
return ptr
|
||
|
||
def realloc(self, ptr: t.CPtr, new_size: t.CSizeT) -> t.CPtr:
|
||
if ptr is None:
|
||
return self.alloc(new_size)
|
||
if new_size == 0:
|
||
self.free(ptr)
|
||
return None
|
||
new_ptr: t.CPtr = self.alloc(new_size)
|
||
if new_ptr is None:
|
||
return ptr
|
||
# Base class cannot track original allocation size; data will not be copied. Subclasses may override to preserve data
|
||
self.free(ptr)
|
||
return new_ptr
|
||
|
||
def __enter__(self) -> 'MemManager' | t.CPtr:
|
||
return self
|
||
|
||
def __exit__(self):
|
||
self.reset()
|
||
|
||
def alloc_buf(self, capacity: t.CSizeT) -> viperio.Buf | t.CPtr:
|
||
buf: viperio.Buf | t.CPtr = self.alloc(viperio.Buf.__sizeof__())
|
||
if buf is None:
|
||
return None
|
||
data_ptr: t.CPtr = self.alloc(capacity)
|
||
if data_ptr is None:
|
||
return None
|
||
buf.__before_init__()
|
||
buf.__init__(t.CChar(t.CUInt64T(data_ptr), t.CPtr), capacity)
|
||
return buf
|
||
|
||
|
||
# ============================================================
|
||
# MemPool - Arena bump allocator
|
||
# Individual free operations are unsupported; reset reclaims all memory at once
|
||
# ============================================================
|
||
class MemPool(MemManager):
|
||
offset: t.CSizeT # Bump pointer offset
|
||
high_water: t.CSizeT # High-water mark
|
||
|
||
def __init__(self, base: t.CPtr, size: t.CSizeT):
|
||
self.base = base
|
||
self.size = size
|
||
self.offset = 0
|
||
self.high_water = 0
|
||
|
||
def alloc(self, size: t.CSizeT) -> t.CPtr:
|
||
if size == 0: return None
|
||
aligned: t.CSizeT = _align_up(size, MEMHUB_ALIGN)
|
||
if self.offset + aligned > self.size:
|
||
stdio.printf("\n[OOM] 内存不足 (Out of Memory)\n")
|
||
stdio.printf("[OOM] 尝试分配: %zu 字节,失败\n", size)
|
||
stdio.printf("[OOM] MemPool: total=%zu used=%zu (bump allocator, 无释放)\n",
|
||
self.size, self.offset)
|
||
stdio.printf("[OOM] 说明: 内存池已耗尽。可能原因: (1) 池大小不足,需增大 POOL_SIZE; (2) 存在内存泄漏。\n")
|
||
stdlib.exit(1)
|
||
ptr: t.CPtr = t.CPtr(t.CUInt64T(self.base) + self.offset)
|
||
self.offset += aligned
|
||
if self.offset > self.high_water:
|
||
self.high_water = self.offset
|
||
return ptr
|
||
|
||
def free(self, ptr: t.CPtr) -> t.CInt:
|
||
# Bump allocator does not support individual free operations
|
||
return 1
|
||
|
||
def reset(self) -> t.CInt:
|
||
self.offset = 0
|
||
self.high_water = 0
|
||
return 1
|
||
|
||
|
||
# ============================================================
|
||
# MemSlab - Fixed-size block pool
|
||
# Bitmap stored at the start of arena; subsequent area split into block_size units managed by free list
|
||
# ============================================================
|
||
class MemSlab(MemManager):
|
||
block_size: t.CSizeT # Block size (aligned)
|
||
block_count: t.CSizeT # Total block count
|
||
used_count: t.CSizeT # Used block count
|
||
free_list: t.CPtr # Free list head
|
||
alloc_map: t.CUInt8T | t.CPtr # Allocation bitmap (at start of arena)
|
||
alloc_map_size: t.CSizeT # Bitmap byte count
|
||
usable: t.CPtr # Usable block area start (after bitmap)
|
||
usable_size: t.CSizeT # Usable block area size
|
||
|
||
def __init__(self, base: t.CPtr, size: t.CSizeT, block_size: t.CSizeT):
|
||
self.base = base
|
||
self.size = size
|
||
self.block_size = 0
|
||
self.block_count = 0
|
||
self.used_count = 0
|
||
self.free_list = None
|
||
self.alloc_map = None
|
||
self.alloc_map_size = 0
|
||
self.usable = None
|
||
self.usable_size = 0
|
||
|
||
if base is None: return
|
||
|
||
bs: t.CSizeT = _align_up(block_size, MEMHUB_ALIGN)
|
||
if bs < MEMSLAB_MIN_BLOCK: bs = MEMSLAB_MIN_BLOCK
|
||
|
||
map_bytes: t.CSizeT = _align_up(MEMSLAB_BITMAP_BYTES, MEMHUB_ALIGN)
|
||
if size < map_bytes + bs: return
|
||
|
||
self.alloc_map = base
|
||
self.alloc_map_size = map_bytes
|
||
self.usable = t.CPtr(t.CUInt64T(base) + map_bytes)
|
||
self.usable_size = size - map_bytes
|
||
self.block_size = bs
|
||
|
||
# Clear bitmap
|
||
idx: t.CSizeT = 0
|
||
while idx < map_bytes:
|
||
self.alloc_map[idx] = 0
|
||
idx += 1
|
||
|
||
self.block_count = self.usable_size / bs
|
||
if self.block_count == 0: return
|
||
|
||
# Construct free list: the first 8 bytes of each block store pointer to next block
|
||
self.free_list = None
|
||
i: t.CSizeT = 0
|
||
while i < self.block_count:
|
||
block: t.CPtr = t.CPtr(t.CUInt64T(self.usable) + i * bs)
|
||
c.DerefAs(block, self.free_list)
|
||
self.free_list = block
|
||
i += 1
|
||
|
||
def alloc(self, size: t.CSizeT) -> t.CPtr:
|
||
# Slab mode: allocate one block only if requested size <= block_size
|
||
if self.free_list is None:
|
||
stdio.printf("\n[OOM] 内存不足 (Out of Memory)\n")
|
||
stdio.printf("[OOM] 尝试分配: %zu 字节,失败(free_list 已空)\n", size)
|
||
stdio.printf("[OOM] MemSlab: block_size=%zu block_count=%zu used=%zu\n",
|
||
self.block_size, self.block_count, self.used_count)
|
||
stdio.printf("[OOM] 说明: 内存池已耗尽。可能原因: (1) 池大小不足,需增大 POOL_SIZE; (2) 存在内存泄漏。\n")
|
||
stdlib.exit(1)
|
||
if size > self.block_size:
|
||
# 超块大小请求 = 调用方错误:优雅失败返回 NULL(类 malloc 语义,调用方自行判断)。
|
||
# 与 free_list 耗尽(真正的 OOM,打印后 exit(1))区分开。
|
||
stdio.printf("[WARN] MemSlab: 请求 %zu 字节超过 block_size=%zu,分配失败返回 NULL;应改用 MemBuddy/MemPool。\n",
|
||
size, self.block_size)
|
||
return None
|
||
block: t.CPtr = self.free_list
|
||
self.free_list = t.CPtr(c.Deref(t.CUInt64T(block)))
|
||
self.used_count += 1
|
||
idx: t.CSizeT = (t.CUInt64T(block) - t.CUInt64T(self.usable)) / self.block_size
|
||
self.alloc_map[idx / 8] = self.alloc_map[idx / 8] | t.CUInt8T(1 << (idx % 8))
|
||
return block
|
||
|
||
def free(self, ptr: t.CPtr) -> t.CInt:
|
||
if ptr is None: return 0
|
||
p: t.CUInt64T = t.CUInt64T(ptr)
|
||
if p < t.CUInt64T(self.usable): return 0
|
||
if p >= t.CUInt64T(self.usable) + self.block_count * self.block_size: return 0
|
||
if (p - t.CUInt64T(self.usable)) % self.block_size != 0: return 0
|
||
idx: t.CSizeT = (p - t.CUInt64T(self.usable)) / self.block_size
|
||
byte_idx: t.CSizeT = idx / 8
|
||
bit_idx: t.CInt = t.CInt(idx % 8)
|
||
if (self.alloc_map[byte_idx] >> bit_idx) & 1 == 0: return 0
|
||
self.alloc_map[byte_idx] = self.alloc_map[byte_idx] & t.CUInt8T(~(1 << bit_idx))
|
||
c.DerefAs(ptr, self.free_list)
|
||
self.free_list = ptr
|
||
self.used_count -= 1
|
||
return 1
|
||
|
||
def reset(self) -> t.CInt:
|
||
self.used_count = 0
|
||
self.free_list = None
|
||
i: t.CSizeT = 0
|
||
while i < self.block_count:
|
||
block: t.CPtr = t.CPtr(t.CUInt64T(self.usable) + i * self.block_size)
|
||
c.DerefAs(block, self.free_list)
|
||
self.free_list = block
|
||
if i / 8 < self.alloc_map_size:
|
||
self.alloc_map[i / 8] = 0
|
||
i += 1
|
||
return 1
|
||
|
||
|
||
# ============================================================
|
||
# MemBuddy - Buddy system allocator
|
||
# Free list head array stored at arena start; subsequent memory managed in power-of-two sized blocks
|
||
# Thread-safety is compile-time gated by MEMBUDDY_THREAD_SAFE:0=单线程默认(关闭自旋锁,
|
||
# 消除每次分配一次的原子加解锁开销);多线程使用必须置 1(保留原子自旋)。
|
||
# supports block coalescing
|
||
#
|
||
# Checkpoint 支持(供编译器等长任务回收阶段性分配):
|
||
# mark() → 返回当前分配水位(不分配内存)
|
||
# reset_to(m) → 释放水位 m 之后分配的所有块(回收到 free list)
|
||
# 记录栈从 usable 顶部预留 _CHK_RESERVE 字节,不参与 buddy 分配。
|
||
# ============================================================
|
||
class MemBuddy(MemManager):
|
||
# Override parent __provides__: MemBuddy provides both __mbuddy__ and __memmgr__.
|
||
# Within `with MemBuddy(...)` context, classes (e.g. _str) with __requires__=['__mbuddy__']
|
||
# can be automatically injected via _find_provider
|
||
__provides__: list[str] = ['__mbuddy__', '__memmgr__']
|
||
max_order: t.CInt # Maximum order
|
||
free_lists: t.CUInt64T | t.CPtr # Free list head array (located at arena start)
|
||
lock_val: t.CVolatile | t.CInt # Spinlock
|
||
usable: t.CPtr # Start of usable memory region (skips free_lists)
|
||
usable_size: t.CSizeT # Size of usable memory (power of two)
|
||
chk_stack: t.CUInt64T | t.CPtr # Checkpoint 分配记录栈(存 block 地址)
|
||
chk_cap: t.CSizeT # 记录栈容量(条目数)
|
||
chk_depth: t.CSizeT # 当前记录深度
|
||
chk_base: t.CPtr # 记录栈起始地址(由 stdlib.malloc 分配,独立于 buddy 区)
|
||
big_alloc_count: t.CSizeT # ≥8KB 大分配计数(OOM 诊断:定位大缓冲反复分配)
|
||
# [DET] 分配检测(默认关,排查越界写/存活块覆盖时开启):
|
||
# detection=1 → alloc 前后 2 字节 0xCDCD 标记 + 记录表 + 每次 alloc 后探测
|
||
detection: t.CInt = 0
|
||
# 运行期重诊断开关(命令行 --detection-deep → main.vp strstr → 构造传参):
|
||
# deep_detect=1 ⇒ 忽略 MEMBUDDY_DET_PROBE_PERIOD(等价 1,每次 alloc 都探测)
|
||
# + 打开 MEMBUDDY_PERIOD_SCAN 的周期全表扫描。仅诊断用,极慢。
|
||
# 默认 0 = 与历史实现完全一致(范式同上方 detection 字段)。
|
||
deep_detect: t.CInt = 0
|
||
detection_size: t.CSizeT = MEMBUDDY_DET_RECORD_SIZE
|
||
_det_arena: t.CPtr = None # 记录区基址(stdlib.malloc,供 realloc 扩容)
|
||
_det_records: t.CUInt64T | t.CPtr = None # 记录表(每 2×u64 = 1 条:{ptr, size})
|
||
_det_count: t.CSizeT = 0 # 已记录条数
|
||
_det_cap: t.CSizeT = 0 # 记录容量(条)
|
||
|
||
def __init__(self, base: t.CPtr, size: t.CSizeT,
|
||
detection: t.CInt = 0, deep_detect: t.CInt = 0):
|
||
self.base = base
|
||
self.size = size
|
||
self.detection = detection
|
||
# 默认形参(范式同上 detection):既有全部构造点(2 参 / 3 参)无需改动
|
||
self.deep_detect = deep_detect
|
||
# 显式赋值(不能依赖字段默认值——TPC 类字段默认值对 CDefine 常量
|
||
# 可能不生效,垃圾 detection_size → malloc(垃圾) 1-byte 记录区 →
|
||
# 扩容 realloc double-free,ASAN 已捕获)
|
||
self.detection_size = MEMBUDDY_DET_RECORD_SIZE
|
||
self._det_arena = None
|
||
self._det_records = None
|
||
self._det_count = 0
|
||
self._det_cap = 0
|
||
self.max_order = 0
|
||
self.free_lists = None
|
||
self.lock_val = 0
|
||
self.usable = None
|
||
self.usable_size = 0
|
||
self.chk_stack = None
|
||
self.chk_cap = 0
|
||
self.chk_depth = 0
|
||
self.chk_base = None
|
||
self.big_alloc_count = 0
|
||
|
||
self.lock_val = 0
|
||
fl_bytes: t.CSizeT = (MEMBUDDY_MAX_ORDERS + 1) * 8
|
||
self.free_lists = base
|
||
|
||
# Initialize all free list heads to be 0
|
||
i: t.CInt
|
||
for i in range(MEMBUDDY_MAX_ORDERS + 1):
|
||
self.free_lists[i] = 0
|
||
|
||
if size <= fl_bytes:
|
||
self.usable = None
|
||
self.usable_size = 0
|
||
self.max_order = 0
|
||
return
|
||
|
||
remaining: t.CSizeT = size - fl_bytes
|
||
|
||
# 注意:不能在 __init__ 里预留 checkpoint 记录栈!
|
||
# 记录栈改为 mark() 时从 buddy 池懒分配(见 mark())。
|
||
#
|
||
# 非 2 的幂 arena 支持:主池 1.2GB(非 2 幂)不再被
|
||
# _largest_pow2_le 砍成 1GB 浪费 0.2GB。整个区域按二进制
|
||
# 分解拆成若干个 2 的幂空闲块压入对应 free list;buddy 合并
|
||
# 按 offset^bs 匹配,跨分解边界的块因 order 不匹配自然停止
|
||
# 合并,不会破坏 buddy 一致性。remaining 恰为 2 的幂时分解
|
||
# 结果与原实现(单个 max_order 块)完全等价。
|
||
max_blk: t.CSizeT = _largest_pow2_le(remaining)
|
||
if max_blk < MEMBUDDY_MIN_BLOCK:
|
||
self.usable = None
|
||
self.usable_size = 0
|
||
self.max_order = 0
|
||
return
|
||
|
||
self.usable = t.CPtr(t.CUInt64T(base) + fl_bytes)
|
||
|
||
# 计算 max_order: log2(max_blk / MIN_BLOCK)
|
||
self.max_order = 0
|
||
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK
|
||
while bs < max_blk:
|
||
bs = bs << 1
|
||
self.max_order += 1
|
||
|
||
# 二进制分解:把 [usable, usable+remaining) 拆成 2 的幂空闲块。
|
||
# 尾部碎片(< MIN_BLOCK)不入 free list;usable_size 记实际
|
||
# 可分配字节(used/free 统计不含碎片)。
|
||
pos: t.CSizeT = 0
|
||
rem: t.CSizeT = remaining
|
||
used_area: t.CSizeT = 0
|
||
while rem >= MEMBUDDY_MIN_BLOCK:
|
||
blk: t.CSizeT = _largest_pow2_le(rem)
|
||
o: t.CInt = 0
|
||
ob: t.CSizeT = MEMBUDDY_MIN_BLOCK
|
||
while ob < blk:
|
||
ob = ob << 1
|
||
o += 1
|
||
self._fl_push(o, t.CPtr(t.CUInt64T(self.usable) + pos))
|
||
pos += blk
|
||
used_area += blk
|
||
rem -= blk
|
||
self.usable_size = used_area
|
||
|
||
# 分配记录区:stdlib.malloc 独立分配(不占用 buddy 区,
|
||
# 与 chk_stack 同策略)。容量 = detection_size // 16(每条 {ptr,size})。
|
||
# 2026-09-04:记录+重叠检测从 detection 解耦——非 detection 也分配
|
||
# 记录区并启用同地址重复分配检测(不做前后标记、不改 alloc 布局),
|
||
# 定位 free-list/重复分配类损坏(memhub 翻译早期容器 Count 槽被写坏)。
|
||
det_arena: t.CPtr = t.CPtr(stdlib.malloc(self.detection_size))
|
||
if det_arena is not None:
|
||
self._det_arena = det_arena
|
||
self._det_records = (t.CUInt64T | t.CPtr)(det_arena)
|
||
self._det_cap = self.detection_size // 16
|
||
self._det_count = 0
|
||
|
||
# === Checkpoint support ===
|
||
|
||
def mark(self) -> t.CSizeT:
|
||
"""返回当前分配水位(checkpoint)
|
||
|
||
记录栈用 stdlib.malloc 分配(独立于 buddy 区,避免占用 buddy 池、
|
||
避免 reset_to 误释放栈自身,也避免栈内存与 buddy 块重叠)。
|
||
初始容量 2M 条目(16MB):单文件翻译(parse AST + 生成 IR)可产生
|
||
几十万次 alloc,64K 初始容量会在首个大文件就触发 realloc 扩容,
|
||
扩容失败时块不再记录 → reset_to 永久 no-op → 所有分配泄漏 →
|
||
1GB 主池 OOM(NewAstTest Phase2 根因)。
|
||
"""
|
||
if self.chk_stack is None:
|
||
cap: t.CSizeT = 2 * 1024 * 1024
|
||
base: t.CPtr = t.CPtr(stdlib.malloc(cap * 8))
|
||
if base is None:
|
||
return 0
|
||
self.chk_base = base
|
||
self.chk_stack = (t.CUInt64T | t.CPtr)(base)
|
||
self.chk_cap = cap
|
||
self.chk_depth = 0
|
||
return self.chk_depth
|
||
|
||
def reset_to(self, mark: t.CSizeT):
|
||
"""释放水位 mark 之后分配的所有块,回收到 free list
|
||
|
||
栈中每个 block:若 header 仍标记为已分配(奇数),_coalesce 归还;
|
||
若已被单独 free(header==0),跳过避免重复归还。
|
||
"""
|
||
if self.chk_stack is None:
|
||
return
|
||
if mark > self.chk_depth:
|
||
# mark 大于当前深度:调用方 mark 值异常(可能读错字段/跨实例),
|
||
# reset 将 no-op → 之后分配全泄漏 → 1GB 主池 OOM。fail-fast 暴露。
|
||
stdio.printf(
|
||
"[MEM] reset_to mark=%zu > chk_depth=%zu(mark 异常,reset 失效,终止)\n",
|
||
mark, self.chk_depth)
|
||
return
|
||
if mark >= self.chk_depth:
|
||
return
|
||
self._lock()
|
||
while self.chk_depth > mark:
|
||
self.chk_depth -= 1
|
||
block: t.CPtr = t.CPtr(self.chk_stack[self.chk_depth])
|
||
if block is None:
|
||
continue
|
||
stored: t.CPtr = t.CPtr(c.Deref(t.CUInt64T(block, t.CPtr)))
|
||
stored_val: t.CUInt64T = t.CUInt64T(stored)
|
||
if (stored_val & 1) == 0:
|
||
continue # 已被单独 free,跳过
|
||
order: t.CInt = t.CInt(stored_val >> 1)
|
||
if order < 0 or order > self.max_order:
|
||
continue
|
||
self._coalesce(block, order)
|
||
# [DET] reset-aware:记录表与 chk 栈等长(每次 alloc 各追加一条,
|
||
# free 只置标志不删),截断 chk_depth 必须同步截断 _det_count,
|
||
# 否则 reset 后旧记录仍标 active → 下一轮 alloc 复用同一块被
|
||
# 误报为"分配重叠/越界写"(SlotsBugTest 7B buf + 72B Token 误报根因)。
|
||
# 2026-09-04 记录表已与 detection 解耦(detection=0 也启用记录+重叠
|
||
# 检测),截断条件不能挂 detection——否则检测模式下 reset_to 后
|
||
# 旧记录残留 active,复用块被误报重叠(记录[647] sz=72 误报例)。
|
||
if self._det_records is not None:
|
||
self._det_count = mark
|
||
self._unlock()
|
||
|
||
def reset_all(self):
|
||
"""回收全部已分配块(等价 reset_to(0))"""
|
||
if self.chk_stack is None:
|
||
return
|
||
self.reset_to(0)
|
||
|
||
# === Free list operations ===
|
||
|
||
def _fl_push(self, order: t.CInt, block: t.CPtr):
|
||
old_head: t.CUInt64T = self.free_lists[order]
|
||
c.DerefAs(block, t.CPtr(old_head))
|
||
self.free_lists[order] = t.CUInt64T(block)
|
||
|
||
def _fl_pop(self, order: t.CInt) -> t.CPtr:
|
||
head_val: t.CUInt64T = self.free_lists[order]
|
||
if head_val == 0:
|
||
return None
|
||
block: t.CPtr = t.CPtr(head_val)
|
||
next_ptr: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block)))
|
||
self.free_lists[order] = t.CUInt64T(next_ptr)
|
||
return block
|
||
|
||
def _fl_find_and_remove(self, order: t.CInt, target: t.CPtr) -> t.CInt:
|
||
head_val: t.CUInt64T = self.free_lists[order]
|
||
if head_val == 0:
|
||
return 0
|
||
head: t.CPtr = t.CPtr(head_val)
|
||
if t.CUInt64T(head) == t.CUInt64T(target):
|
||
next_ptr: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(head)))
|
||
self.free_lists[order] = t.CUInt64T(next_ptr)
|
||
return 1
|
||
prev: t.CPtr = head
|
||
cur: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(head)))
|
||
while t.CUInt64T(cur) != 0:
|
||
if t.CUInt64T(cur) == t.CUInt64T(target):
|
||
next_ptr: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(cur)))
|
||
c.DerefAs(prev, next_ptr)
|
||
return 1
|
||
prev = cur
|
||
cur = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(cur)))
|
||
return 0
|
||
|
||
# === Buddy system core ===
|
||
|
||
def _buddy_of(self, block: t.CPtr, order: t.CInt) -> t.CPtr:
|
||
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
|
||
# order 恒 ≥ 0(调用链:_coalesce 的 order 源自合法 header (stored>>1) 且只递增,
|
||
# 起点为已释放块的阶)。内联左移等价于 _block_size_at_order(order),省去每次
|
||
# 调用的 call/ret(_buddy_of 实测 3517 万次,为 _block_size_at_order 主要来源)。
|
||
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(order)
|
||
buddy_offset: t.CSizeT = offset ^ bs
|
||
return t.CPtr(t.CUInt64T(self.usable) + buddy_offset)
|
||
|
||
def _order_for_size(self, size: t.CSizeT) -> t.CInt:
|
||
order: t.CInt = 0
|
||
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK
|
||
while bs < size:
|
||
bs = bs << 1
|
||
order += 1
|
||
return order
|
||
|
||
def _split_to_order(self, to_order: t.CInt) -> t.CPtr:
|
||
found_order: t.CInt = to_order
|
||
while found_order <= self.max_order:
|
||
if self.free_lists[found_order] != 0:
|
||
break
|
||
found_order += 1
|
||
if found_order > self.max_order:
|
||
return None
|
||
block: t.CPtr = self._fl_pop(found_order)
|
||
while found_order > to_order:
|
||
found_order -= 1
|
||
# 循环条件保证 found_order > to_order ≥ 0,故递减后 found_order ≥ 0;
|
||
# 内联左移等价于 _block_size_at_order(found_order),省去每次拆分的 call/ret。
|
||
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(found_order)
|
||
buddy: t.CPtr = t.CPtr(t.CUInt64T(block) + bs)
|
||
self._fl_push(found_order, buddy)
|
||
return block
|
||
|
||
def _coalesce(self, block: t.CPtr, order: t.CInt):
|
||
while order < self.max_order:
|
||
# 提前退出(不改变语义,仅省去"注定找不到"的两次调用):本阶空闲链表
|
||
# 为空 ⇒ _fl_find_and_remove 入口即判 head==0 返回 0 ⇒ 本阶不可能合并。
|
||
# 这里先判空,可省去本轮 _buddy_of(内部含 _block_size_at_order)与
|
||
# _fl_find_and_remove 两次调用;判据与 _fl_find_and_remove 的入口条件
|
||
# 逐字相同,且两函数均无副作用,故对空闲链表语义与顺序完全等价。
|
||
if self.free_lists[order] == 0:
|
||
break
|
||
buddy: t.CPtr = self._buddy_of(block, order)
|
||
found: t.CInt = self._fl_find_and_remove(order, buddy)
|
||
if found == 0:
|
||
break
|
||
if t.CUInt64T(buddy) < t.CUInt64T(block):
|
||
block = buddy
|
||
order += 1
|
||
self._fl_push(order, block)
|
||
|
||
def _is_valid_ptr(self, ptr: t.CPtr) -> t.CInt:
|
||
if ptr is None: return 0
|
||
if self.usable is None: return 0
|
||
block: t.CPtr = t.CPtr(t.CUInt64T(ptr) - MEMBUDDY_HEADER_SIZE)
|
||
if t.CUInt64T(block) < t.CUInt64T(self.usable): return 0
|
||
if t.CUInt64T(block) >= t.CUInt64T(self.usable) + self.usable_size: return 0
|
||
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
|
||
if offset % MEMBUDDY_MIN_BLOCK != 0: return 0
|
||
stored: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block)))
|
||
stored_val: t.CUInt64T = t.CUInt64T(stored)
|
||
if (stored_val & 1) == 0: return 0
|
||
order: t.CInt = t.CInt(stored_val >> 1)
|
||
if order < 0: return 0
|
||
if order > self.max_order: return 0
|
||
# 记录栈一致性校验:分配时(alloc)每个 block 地址都会压入 chk_stack。
|
||
# reset_to(mark) 会把 [mark, chk_depth) 的块弹栈并归还 free list,
|
||
# 之后这些地址会被重新分配。若一个 header 恰好是合法奇数(假阳性),
|
||
# 但地址不在当前 chk_stack[0..chk_depth) 存活区间内,说明已被回收,
|
||
# 不能当作有效指针(否则 _format_func_type_str/TypePrint 会沿垃圾链
|
||
# 无限分配导致 OOM)。
|
||
# 注意:chk_stack 容量满(64K)后 alloc 不再记录新块(chk_depth==chk_cap),
|
||
# 此时无法区分,跳过校验(退化到仅 header 校验,避免误判正常 free)。
|
||
if self.chk_stack is not None and self.chk_depth < self.chk_cap:
|
||
alive: t.CInt = 0
|
||
ci: t.CSizeT = 0
|
||
while ci < self.chk_depth:
|
||
if self.chk_stack[ci] == t.CUInt64T(block):
|
||
alive = 1
|
||
break
|
||
ci += 1
|
||
if alive == 0:
|
||
return 0
|
||
return 1
|
||
|
||
# === Spinlock ===
|
||
def _lock(self): # can use the spinlock library for decoupling
|
||
# 单线程编译(MEMBUDDY_THREAD_SAFE=0)下本 if 被常量折叠,函数体为空;
|
||
# 多线程(=1)下保留原有原子自旋加锁,行为与历史实现逐字节一致。
|
||
if MEMBUDDY_THREAD_SAFE != 0:
|
||
while atom.__atomic_test_and_set(c.Addr(self.lock_val), atom.ATOMIC_ACQUIRE):
|
||
pass
|
||
|
||
def _unlock(self):
|
||
if MEMBUDDY_THREAD_SAFE != 0:
|
||
atom.__atomic_clear(c.Addr(self.lock_val), atom.ATOMIC_RELEASE)
|
||
|
||
# === Virtual function overrides ===
|
||
def alloc(self, size: t.CSizeT) -> t.CPtr:
|
||
self._lock()
|
||
result: t.CPtr = None
|
||
if size != 0 and self.usable is not None:
|
||
# +MEMBUDDY_CANARY:预留块尾 8 字节哨兵(用户数据区之外),越界写定位用
|
||
needed: t.CSizeT = size + MEMBUDDY_HEADER_SIZE + MEMBUDDY_CANARY
|
||
if self.detection != 0:
|
||
# [DET] 前后各 2 字节 0xCDCD 标记,实际比请求多 4 字节
|
||
needed = needed + 4
|
||
order: t.CInt = self._order_for_size(needed)
|
||
if order <= self.max_order:
|
||
block: t.CPtr = self._split_to_order(order)
|
||
if block is not None:
|
||
c.DerefAs(block, t.CPtr(t.CUInt64T((order << 1) | 1)))
|
||
result = t.CPtr(t.CUInt64T(block) + MEMBUDDY_HEADER_SIZE)
|
||
# 写块尾 canary(block + block_size - CANARY)
|
||
# order 恒 ≥ 0(_order_for_size 返回值),内联左移等价于
|
||
# _block_size_at_order(order),省去每次 alloc 的 call/ret。
|
||
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(order)
|
||
c.DerefAs(t.CPtr(t.CUInt64T(block) + bs - MEMBUDDY_CANARY),
|
||
t.CPtr(MEMBUDDY_CANARY_PATTERN))
|
||
# 分配守卫:填充用户数据区 0xCD 哨兵(未初始化检测)。
|
||
# 调用方初始化(list.__init__ 等)覆盖前,字段保持 0xCDCD...
|
||
# CTORDBG 打印 d0-d40 时:0xCDCD...=未初始化,其他=被覆盖。
|
||
if MEMBUDDY_FILL_GUARD != 0:
|
||
fill_size: t.CSizeT = bs - MEMBUDDY_HEADER_SIZE - MEMBUDDY_CANARY
|
||
if fill_size > 0:
|
||
string.memset(result, MEMBUDDY_FILL_PATTERN, fill_size)
|
||
# 记录到 checkpoint 栈(供 reset_to 回收)
|
||
# 栈满时 stdlib.realloc 翻倍扩容(防爆满后记录缺失
|
||
# → reset 不回收 + _is_valid_ptr 栈校验退化的连锁问题)
|
||
if self.chk_stack is not None:
|
||
if self.chk_depth >= self.chk_cap:
|
||
new_cap: t.CSizeT = self.chk_cap * 2
|
||
new_stack: t.CPtr = t.CPtr(stdlib.realloc(self.chk_base, new_cap * 8))
|
||
if new_stack is not None:
|
||
self.chk_base = new_stack
|
||
self.chk_stack = (t.CUInt64T | t.CPtr)(t.CPtr(new_stack))
|
||
self.chk_cap = new_cap
|
||
else:
|
||
# realloc 失败:块不再记录 → reset_to 无法回收该块 →
|
||
# 渐进泄漏最终 1GB 主池 OOM。fail-fast 暴露而非静默。
|
||
stdio.printf(
|
||
"[MEM] 记录栈 realloc 失败 cap=%zu depth=%zu(reset 将失效,终止)\n",
|
||
self.chk_cap, self.chk_depth)
|
||
if self.chk_depth < self.chk_cap:
|
||
self.chk_stack[self.chk_depth] = t.CUInt64T(block)
|
||
self.chk_depth += 1
|
||
if result is not None and self._det_records is not None:
|
||
base_ret_d: t.CUInt64T = t.CUInt64T(result)
|
||
# 返回指针(detection 布局下 = 用户区 + 2,前标记之后)
|
||
rec_ptr_d: t.CUInt64T = base_ret_d
|
||
if self.detection != 0:
|
||
# [DET] 布局 [0xCDCD][请求大小内存][0xCDCD]:返回指针 = 用户区 + 2
|
||
# (前标记之后);记录 {返回 ptr, size} 到独立记录区;随后探测全部
|
||
# 存活块的后标记——被破坏 = 越界写,立即汇报并终止。
|
||
m_f_d: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CPtr(base_ret_d))
|
||
m_f_d[0] = t.CUInt16T(MEMBUDDY_DET_MARKER)
|
||
tail_d: t.CUInt64T = base_ret_d + 2 + t.CUInt64T(size)
|
||
m_t_d: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CPtr(tail_d))
|
||
m_t_d[0] = t.CUInt16T(MEMBUDDY_DET_MARKER)
|
||
rec_ptr_d = base_ret_d + 2
|
||
# 采样:每 MEMBUDDY_DET_PROBE_PERIOD 次 alloc 做一次全表扫描
|
||
# (分配重叠 + [MEMDBG] 残留探测),其余只追加记录(O(1))。损坏是
|
||
# 持久性的,不依赖即时扫描——若每次全表 O(n) 则整体 O(n²)。该采样块
|
||
# 即 MemBuddy.alloc 的 O(n) 热点(量级见 MEMBUDDY_PERIOD_SCAN 定义),
|
||
# 故整体挂在编译期总开关下,默认 0 = 不执行该分支(历史行为)。
|
||
# 运行期重诊断(--detection-deep → self.deep_detect != 0)额外开此
|
||
# 扫描,并把采样掩码短路成"每次都扫":两个判定点(此处总开关 +
|
||
# 下方采样)各加一个 deep_detect 分支,deep_detect 恒 0 时两条
|
||
# 表达式与历史实现逐字节相同。
|
||
if MEMBUDDY_PERIOD_SCAN != 0 or self.deep_detect != 0:
|
||
if (self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0 or self.deep_detect != 0:
|
||
# 分配重叠即时检测:新块地址是否已在 active 记录(同一块重复
|
||
# 分配实锤——SlotsBugTest 7B buf 与 72B Token 同 ptr、cargs
|
||
# 只写 0/40 根因)。重叠 = MemBuddy free list 损坏(active 块
|
||
# 混入 free list),在第一次重叠处拦截并打印两个 size。
|
||
ff_d: t.CUInt64T = t.CUInt64T(1) << 63
|
||
dup_i: t.CSizeT = 0
|
||
while dup_i < self._det_count:
|
||
drp: t.CUInt64T = self._det_records[dup_i * 2]
|
||
drs: t.CUInt64T = self._det_records[dup_i * 2 + 1]
|
||
if drp == rec_ptr_d and (drs & ff_d) == 0:
|
||
stdio.printf("[DET] 分配重叠: 新alloc(size=%llu)与记录[%zu](sz=%llu)同地址 ptr=%llu(同一块重复分配,free list 损坏)\n",
|
||
t.CUInt64T(size), dup_i, drs & (ff_d - 1), rec_ptr_d)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
dup_i += 1
|
||
if _memdbg_enable != 0:
|
||
# [MEMDBG] 新出块是否仍残留于同 order free list(此前的重复 put
|
||
# 使链表含两条本块 → pop 后还有残条 → 本块将被二次分配,双向写穿)。
|
||
# 记录表重叠检测要等下次 pop 同地址才撞见,这里在出块当场提前拦截。
|
||
dbg_block: t.CPtr = t.CPtr(base_ret_d - MEMBUDDY_HEADER_SIZE)
|
||
dbg_hdr: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(dbg_block)))
|
||
dbg_order: t.CInt = t.CInt(t.CUInt64T(dbg_hdr) >> 1)
|
||
dbg_cur: t.CPtr = t.CPtr(self.free_lists[dbg_order])
|
||
dbg_n: t.CSizeT = 0
|
||
while t.CUInt64T(dbg_cur) != 0:
|
||
if t.CUInt64T(dbg_cur) == t.CUInt64T(dbg_block):
|
||
stdio.printf("[MEMDBG] alloc 重复出块: size=%llu block=%llu 仍残留 free list order=%d(重复 put 实锤,终止)\n",
|
||
t.CUInt64T(size), t.CUInt64T(dbg_block), dbg_order)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
dbg_n += 1
|
||
if dbg_n > self.usable_size / MEMBUDDY_MIN_BLOCK + 2:
|
||
stdio.printf("[MEMDBG] alloc 扫描 free list order=%d 成环/超长(自检异常,终止)\n", dbg_order)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
dbg_cur = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(dbg_cur)))
|
||
if self._det_count >= self._det_cap:
|
||
# 记录表满:stdlib.realloc 翻倍扩容(防 is_alive/_det_probe
|
||
# 因容量耗尽漏检——SlotsBugTest 编译 includes 超 2M 次 alloc,
|
||
# 满后新块无记录 → is_alive 误报"读取悬空")
|
||
new_cap: t.CSizeT = self._det_cap * 2
|
||
new_rec: t.CPtr = t.CPtr(stdlib.realloc(self._det_arena, new_cap * 16))
|
||
if new_rec is not None:
|
||
self._det_arena = new_rec
|
||
self._det_records = (t.CUInt64T | t.CPtr)(t.CPtr(new_rec))
|
||
self._det_cap = new_cap
|
||
if self._det_count < self._det_cap:
|
||
self._det_records[self._det_count * 2] = rec_ptr_d
|
||
self._det_records[self._det_count * 2 + 1] = t.CUInt64T(size)
|
||
self._det_count += 1
|
||
# 探测判定:detection 关闭时不进入(故未开 --detection 的默认路径
|
||
# 不额外读 deep_detect);开 detection 时 deep_detect != 0 → 每次
|
||
# alloc 都探测,否则退回历史采样(每 MEMBUDDY_DET_PROBE_PERIOD 次)。
|
||
if self.detection != 0 and ((self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0 or self.deep_detect != 0):
|
||
self._det_probe()
|
||
if self.detection != 0:
|
||
result = t.CPtr(base_ret_d + 2)
|
||
if result is None:
|
||
# 分配失败诊断(定位"异常 size/池耗尽导致 None → 调用方解引用段错误")
|
||
# size == 0 与"池耗尽"是两回事:alloc 入口的 `size != 0` 守卫会直接
|
||
# 跳过 → 返回 NULL,调用方却只看到"分配失败",无从判断真因。此处
|
||
# 显式区分:0 尺寸几乎总来自调用处的尺寸表达式被静默求值为 0
|
||
# (如未解析/被遮蔽的 t.CDefine:Phase1.MAX_CLASSES * Phase1.SHA1_LEN
|
||
# → 0)。本项目禁止静默回退,故给出可区分的诊断(不 exit,沿用本函数
|
||
# "alloc 失败返回 None 由调用方处理"的既有契约)。
|
||
if size == 0:
|
||
stdio.printf("[MEM] alloc 请求尺寸为 0:返回 NULL(非池耗尽!)——"
|
||
"请检查调用处尺寸表达式是否被静默求值为 0"
|
||
"(常量未解析/被同名符号遮蔽/被算成 0)\n")
|
||
stdio.fflush(None)
|
||
elif self.usable is not None:
|
||
needed2: t.CSizeT = size + MEMBUDDY_HEADER_SIZE + MEMBUDDY_CANARY
|
||
order2: t.CInt = self._order_for_size(needed2)
|
||
stdio.printf("[MEM] alloc 失败 size=%zu needed=%zu order=%d max_order=%d used=%zu pool_base=%llu\n",
|
||
size, needed2, order2, self.max_order, self.used_bytes(),
|
||
t.CUInt64T(self.usable))
|
||
# 越界写诊断:free list 结构损坏(self_check!=0)说明有越界写穿
|
||
# 破坏块头/free list next 指针 → alloc 找不到块(used 虚高非真实耗尽)
|
||
sc: t.CInt = self.self_check()
|
||
stdio.printf("[MEM] alloc 失败 self_check=%d free_bytes=%zu free_count=%zu chk_depth=%zu big=%zu\n",
|
||
sc, self.free_bytes(), self.free_count(), self.chk_depth, self.big_alloc_count)
|
||
# chk_depth 巨大(数百万)→ 分配次数爆炸(每语句/每调用反复分配);
|
||
# chk_depth 不大但 used 巨大 → 单次大分配(缓冲/表扩容)反复发生
|
||
if order2 > self.max_order:
|
||
stdio.printf("[MEM] 原因:请求 size 超 pool 最大块(order %d > max %d)\n",
|
||
order2, self.max_order)
|
||
else:
|
||
stdio.printf("[MEM] 原因:pool 耗尽(空闲 %zu 字节不足)\n", self.free_bytes())
|
||
# 分配失败(alloc(0) 或池耗尽):解锁并返回 None,由调用者决定后续处理。
|
||
# 不能 exit——测试依赖 alloc 失败返回 None 后继续 free+重试
|
||
# (如 OOM section: 耗尽后 free 一块再 alloc 应成功)。
|
||
self._unlock()
|
||
return None
|
||
self._unlock()
|
||
return result
|
||
|
||
def free(self, ptr: t.CPtr) -> t.CInt:
|
||
self._lock()
|
||
if ptr is not None:
|
||
p_eff: t.CPtr = ptr
|
||
if self.detection != 0:
|
||
# [DET] 返回指针比实际用户区靠后 2 字节,回收时向前偏移 2
|
||
p_eff = t.CPtr(t.CUInt64T(ptr) - 2)
|
||
# 记录表置 free 标志(解耦于 detection,2026-09-04):非 detection
|
||
# 也需置位,否则 free 后同地址再 alloc(正常 reuse)被误报重叠。
|
||
if self._det_records is not None:
|
||
# 从记录表取本条真实 size(detection 下 free 后填 0xCD,UAF
|
||
# 写入检测:free 后块被写穿 → 探测时 0xCD 被破坏 → 报警);
|
||
# 记录保留并置 free 标志(探测时对仍 free 的块验证 0xCD 完整)。
|
||
ri: t.CSizeT = 0
|
||
while ri < self._det_count:
|
||
rp: t.CUInt64T = self._det_records[ri * 2]
|
||
rs: t.CUInt64T = self._det_records[ri * 2 + 1]
|
||
if rp == t.CUInt64T(ptr) and (rs & (t.CUInt64T(1) << 63)) == 0:
|
||
if self.detection != 0 and rs > 0:
|
||
string.memset(ptr, MEMBUDDY_FILL_PATTERN, rs)
|
||
self._det_records[ri * 2 + 1] = rs | (t.CUInt64T(1) << 63)
|
||
break
|
||
ri += 1
|
||
if self._is_valid_ptr(p_eff) != 0:
|
||
block: t.CPtr = t.CPtr(t.CUInt64T(p_eff) - MEMBUDDY_HEADER_SIZE)
|
||
stored: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block)))
|
||
stored_val: t.CUInt64T = t.CUInt64T(stored)
|
||
order: t.CInt = t.CInt(stored_val >> 1)
|
||
if (stored_val & 1) == 0:
|
||
# 双重 free:块已释放(header 偶)。debug 关 = 按原语义静默忽略
|
||
# (不再次 coalesce,避免 free list 重复 put 污染);debug 开 =
|
||
# 终止并暴露悬空/重复 free 调用点。
|
||
if _memdbg_enable != 0:
|
||
stdio.printf("[MEMDBG] 悬空/重复 free: ptr=%llu block=%llu header=0x%llX(偶数=已释放/未初始化,终止)\n",
|
||
t.CUInt64T(ptr), t.CUInt64T(block), stored_val)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
self._unlock()
|
||
return 1
|
||
if _memdbg_enable != 0:
|
||
# [MEMDBG] 重复归还检测:活跃块(header 奇数)绝不该已在本 order
|
||
# free list;命中 = 同块被 put 两次(free list 重复 put 实锤)。
|
||
dbg_f: t.CPtr = t.CPtr(self.free_lists[order])
|
||
dbg_fn: t.CSizeT = 0
|
||
while t.CUInt64T(dbg_f) != 0:
|
||
if t.CUInt64T(dbg_f) == t.CUInt64T(block):
|
||
stdio.printf("[MEMDBG] 重复归还: free(ptr=%llu) block=%llu 已在 free list order=%d(double-put 实锤,终止)\n",
|
||
t.CUInt64T(ptr), t.CUInt64T(block), order)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
dbg_fn += 1
|
||
if dbg_fn > self.usable_size / MEMBUDDY_MIN_BLOCK + 2:
|
||
stdio.printf("[MEMDBG] free 扫描 free list order=%d 成环/超长(自检异常,终止)\n", order)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
dbg_f = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(dbg_f)))
|
||
c.DerefAs(block, t.CPtr(0))
|
||
self._coalesce(block, order)
|
||
elif _memdbg_enable != 0:
|
||
# [MEMDBG] 无效/悬空 free(原实现静默忽略):块在本池范围内但 header
|
||
# 为偶 = 已被释放/未初始化 → 悬空重复 free(stale 指针)。范围外
|
||
# (非本池指针)是正常多池场景,不报。
|
||
dbg_eff: t.CUInt64T = t.CUInt64T(p_eff)
|
||
dbg_blk: t.CUInt64T = dbg_eff - MEMBUDDY_HEADER_SIZE
|
||
if (dbg_eff >= MEMBUDDY_HEADER_SIZE and dbg_blk >= t.CUInt64T(self.usable)
|
||
and dbg_blk < t.CUInt64T(self.usable) + self.usable_size):
|
||
dbg_hv: t.CUInt64T = t.CUInt64T(t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(dbg_blk))))
|
||
if (dbg_hv & 1) == 0:
|
||
stdio.printf("[MEMDBG] 悬空/重复 free: ptr=%llu block=%llu header=0x%llX(偶数=已释放/未初始化,终止)\n",
|
||
t.CUInt64T(ptr), dbg_blk, dbg_hv)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
self._unlock()
|
||
return 1
|
||
|
||
def _rebuild_free_lists(self):
|
||
"""按二进制分解重建空闲链表(reset 恢复初始分配格局)
|
||
|
||
与 __init__ 的初始化分解一致:非 2 的幂 arena 会拆成多个
|
||
2 的幂空闲块(buddy 合并跨分解边界时因 order 不匹配自然
|
||
停止,安全);2 的幂 arena 等价于单个 max_order 块。
|
||
"""
|
||
i: t.CInt
|
||
for i in range(MEMBUDDY_MAX_ORDERS + 1):
|
||
self.free_lists[i] = 0
|
||
pos: t.CSizeT = 0
|
||
rem: t.CSizeT = self.usable_size
|
||
while rem >= MEMBUDDY_MIN_BLOCK:
|
||
blk: t.CSizeT = _largest_pow2_le(rem)
|
||
o: t.CInt = 0
|
||
ob: t.CSizeT = MEMBUDDY_MIN_BLOCK
|
||
while ob < blk:
|
||
ob = ob << 1
|
||
o += 1
|
||
self._fl_push(o, t.CPtr(t.CUInt64T(self.usable) + pos))
|
||
pos += blk
|
||
rem -= blk
|
||
|
||
def reset(self) -> t.CInt:
|
||
if self.usable is None:
|
||
return 0
|
||
self._rebuild_free_lists()
|
||
# [DET] reset 重建全部 free list = 全部块释放:记录表同步清空,
|
||
# 防止后续 alloc 复用旧块被误报(与 reset_to 截断同因)。
|
||
# 2026-09-04 记录表已与 detection 解耦,清空条件同样不能挂 detection。
|
||
if self._det_records is not None:
|
||
self._det_count = 0
|
||
return 1
|
||
|
||
# realloc override: buddy system reads original order from block header; old_size is not required
|
||
def realloc(self, ptr: t.CPtr, new_size: t.CSizeT) -> t.CPtr:
|
||
if ptr is None:
|
||
return self.alloc(new_size)
|
||
if new_size == 0:
|
||
self.free(ptr)
|
||
return None
|
||
p_eff: t.CPtr = ptr
|
||
if self.detection != 0:
|
||
# [DET] 与 free 一致:先向前偏移 2 字节再做块级计算
|
||
p_eff = t.CPtr(t.CUInt64T(ptr) - 2)
|
||
if self._is_valid_ptr(p_eff) == 0:
|
||
return None
|
||
block: t.CPtr = t.CPtr(t.CUInt64T(p_eff) - MEMBUDDY_HEADER_SIZE)
|
||
stored: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block)))
|
||
stored_val: t.CUInt64T = t.CUInt64T(stored)
|
||
old_order: t.CInt = t.CInt(stored_val >> 1)
|
||
needed: t.CSizeT = new_size + MEMBUDDY_HEADER_SIZE
|
||
if self.detection != 0:
|
||
needed = needed + 4 # [DET] 前后标记
|
||
new_order: t.CInt = self._order_for_size(needed)
|
||
if new_order <= old_order:
|
||
return ptr
|
||
new_ptr: t.CPtr = self.alloc(new_size)
|
||
if new_ptr is None:
|
||
return ptr
|
||
old_block_size: t.CSizeT = _block_size_at_order(old_order)
|
||
old_data_size: t.CSizeT = old_block_size - MEMBUDDY_HEADER_SIZE
|
||
cp_len: t.CSizeT = old_data_size
|
||
if self.detection != 0:
|
||
cp_len = old_data_size - 4 # [DET] 排除前后 2 字节标记
|
||
string.memcpy(new_ptr, ptr, cp_len)
|
||
self.free(ptr)
|
||
return new_ptr
|
||
|
||
# ============================================================
|
||
# [DET] is_alive - 读取侧存活校验:ptr 是否在当前记录表的 active
|
||
# 记录中(alloc 后未 free)。返回 1=存活 / 0=悬空(不在记录或已
|
||
# free)。detection 关闭或记录表不可用时返回 1(不拦截)。
|
||
# 补 _det_probe 盲区:写入侧检测(标记/0xCD)拦不住 use-after-free
|
||
# 读取(如 SlotsBugTest KwEntry cargs=cl.args 悬空指向复用块)。
|
||
# ============================================================
|
||
def is_alive(self, ptr: t.CPtr) -> t.CInt:
|
||
if self.detection == 0:
|
||
return 1
|
||
if self._det_records is None:
|
||
return 1
|
||
target: t.CUInt64T = t.CUInt64T(ptr)
|
||
free_flag: t.CUInt64T = t.CUInt64T(1) << 63
|
||
i: t.CSizeT = 0
|
||
while i < self._det_count:
|
||
rp: t.CUInt64T = self._det_records[i * 2]
|
||
rs: t.CUInt64T = self._det_records[i * 2 + 1]
|
||
if rp == target and (rs & free_flag) == 0:
|
||
return 1
|
||
i += 1
|
||
return 0
|
||
|
||
# ============================================================
|
||
# [DET] _det_probe - 分配检测探测:遍历记录表,验证每个存活块的
|
||
# 后标记(返回 ptr + size 处 2 字节)仍为 0xCDCD。
|
||
# 被破坏 = 越界写(数据区写穿),立即汇报 ptr/size 并终止——
|
||
# 把"随机崩溃/字段被覆盖"变成"确定性定位到越界写发生的分配块"。
|
||
# ============================================================
|
||
def _det_probe(self):
|
||
if self._det_records is None:
|
||
return
|
||
free_flag: t.CUInt64T = t.CUInt64T(1) << 63
|
||
i: t.CSizeT = 0
|
||
while i < self._det_count:
|
||
p: t.CUInt64T = self._det_records[i * 2]
|
||
s: t.CUInt64T = self._det_records[i * 2 + 1]
|
||
if (s & free_flag) != 0:
|
||
# free 记录:跳过——buddy 分配器合并/重分配使 free 块内存不排他
|
||
# (块合并进更大块后,新分配的用户区可覆盖旧记录的前标记区而旧块
|
||
# 头仍为 0),前标记校验会误报(SlotsBugTest 1024B 块 hdr=0x0、
|
||
# 前标记被 2048 合并分配数据写零的误报例,与 w0/w1 0xCD 校验同类)。
|
||
# 只校验 active 记录(存活块独占,header LSB=1 不会合并,
|
||
# 前/后标记可靠)。
|
||
i += 1
|
||
continue
|
||
if s == 0:
|
||
i += 1
|
||
continue # 无 size 记录(理论不发生,防御)
|
||
# active 记录:前标记(下溢检测)+ 后标记(上溢检测)
|
||
m_fa: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CPtr(p - 2))
|
||
mfv_a: t.CUInt16T = m_fa[0]
|
||
if mfv_a != t.CUInt16T(MEMBUDDY_DET_MARKER):
|
||
stdio.printf("[DET] 下溢写: ptr=%llu size=%llu 前标记=0x%04X(期望 0xCDCD)\n",
|
||
p, s, t.CUInt32T(mfv_a))
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
m16: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CPtr(p + s))
|
||
mv: t.CUInt16T = m16[0]
|
||
if mv != t.CUInt16T(MEMBUDDY_DET_MARKER):
|
||
stdio.printf("[DET] 越界写: ptr=%llu size=%llu 后标记=0x%04X(期望 0xCDCD)块内容前16B: ",
|
||
p, s, t.CUInt32T(mv))
|
||
bi3: t.CInt = 0
|
||
while bi3 < 16:
|
||
bv3: t.CUInt8T = (t.CUInt8T | t.CPtr)(t.CPtr(p + t.CUInt64T(bi3)))[0]
|
||
stdio.printf("%02X ", t.CUInt32T(bv3))
|
||
bi3 += 1
|
||
# 越界区(ptr+size 起 24 字节):0x00=被写穿 0xCD=未写 其它=写入值,
|
||
# 用于判断写入者写多长、往哪个方向
|
||
stdio.printf("越界区: ")
|
||
bj3: t.CInt = 0
|
||
while bj3 < 24:
|
||
bvj: t.CUInt8T = (t.CUInt8T | t.CPtr)(t.CPtr(p + s + t.CUInt64T(bj3)))[0]
|
||
stdio.printf("%02X ", t.CUInt32T(bvj))
|
||
bj3 += 1
|
||
# 记录表上下文:该条前 3 + 后 3 的 {ptr,size},推断越界块分配阶段
|
||
stdio.printf("\n记录上下文: ")
|
||
ci2: t.CInt = -3
|
||
while ci2 <= 3:
|
||
idx2: t.CSizeT = t.CSizeT(t.CInt(i) + ci2)
|
||
if idx2 < self._det_count:
|
||
cip: t.CUInt64T = self._det_records[idx2 * 2]
|
||
cis: t.CUInt64T = self._det_records[idx2 * 2 + 1]
|
||
stdio.printf("[%d]ptr=%llu sz=%llu ", ci2, cip, cis & (free_flag - 1))
|
||
ci2 += 1
|
||
# 重叠检测:同一 ptr 是否有多条记录(alloc 重叠实锤——不同 size
|
||
# 请求拿到同一块 → 互相覆盖;如 Token(72B) 与字符串 buf(7B) 共用块)
|
||
stdio.printf("\n同ptr记录: ")
|
||
ci3: t.CSizeT = 0
|
||
while ci3 < self._det_count:
|
||
cp3: t.CUInt64T = self._det_records[ci3 * 2]
|
||
cs3: t.CUInt64T = self._det_records[ci3 * 2 + 1]
|
||
if cp3 == p and (cs3 & free_flag) == 0:
|
||
stdio.printf("[%zu]sz=%llu ", ci3, cs3 & (free_flag - 1))
|
||
ci3 += 1
|
||
stdio.printf("\n")
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
i += 1
|
||
|
||
# === Status Queries ===
|
||
|
||
@property
|
||
def mem_size(self) -> t.CSizeT:
|
||
# Total usable region size (power of two). Maximum allocatable size = mem_size - MEMBUDDY_HEADER_SIZE
|
||
return self.usable_size
|
||
|
||
def stats(self) -> t.CSizeT:
|
||
# Return total usable region size, for status statistics
|
||
return self.usable_size
|
||
|
||
def free_count(self) -> t.CSizeT:
|
||
# Count total blocks in all order lists for status statistics
|
||
count: t.CSizeT = 0
|
||
i: t.CInt
|
||
for i in range(MEMBUDDY_MAX_ORDERS + 1):
|
||
head_val: t.CUInt64T = self.free_lists[i]
|
||
cur: t.CPtr = t.CPtr(head_val)
|
||
while t.CUInt64T(cur) != 0:
|
||
count += 1
|
||
cur = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(cur)))
|
||
return count
|
||
|
||
def free_bytes(self) -> t.CSizeT:
|
||
# Count total bytes in all free blocks for status statistics
|
||
total: t.CSizeT = 0
|
||
i: t.CInt
|
||
for i in range(MEMBUDDY_MAX_ORDERS + 1):
|
||
head_val: t.CUInt64T = self.free_lists[i]
|
||
cur: t.CPtr = t.CPtr(head_val)
|
||
while t.CUInt64T(cur) != 0:
|
||
total += _block_size_at_order(i)
|
||
cur = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(cur)))
|
||
return total
|
||
|
||
def used_bytes(self) -> t.CSizeT:
|
||
# Used bytes = total usable region size - free bytes in all free blocks
|
||
return self.usable_size - self.free_bytes()
|
||
|
||
def dump_stats(self, label: t.CChar | t.CPtr):
|
||
# Print memory usage statistics
|
||
fb: t.CSizeT = self.free_bytes()
|
||
ub: t.CSizeT = self.usable_size - fb
|
||
stdio.printf("[MEM] %s: total=%zu used=%zu (%zu%%) free=%zu free_blocks=%zu\n",
|
||
label, self.usable_size, ub,
|
||
(ub * 100) / self.usable_size if self.usable_size > 0 else 0,
|
||
fb, self.free_count())
|
||
|
||
def self_check(self) -> t.CInt:
|
||
# Validate buddy allocator internal consistency, return 0=OK, non-0=damage
|
||
if self.usable is None:
|
||
return 0
|
||
# 环检测:free list 被越界写破坏 next 指针后可能成环,
|
||
# 无步数上限会导致无限循环(崩溃点漂移排查哨兵必须能终止)。
|
||
max_steps: t.CSizeT = self.usable_size / MEMBUDDY_MIN_BLOCK + 2
|
||
i: t.CInt
|
||
for i in range(MEMBUDDY_MAX_ORDERS + 1):
|
||
head_val: t.CUInt64T = self.free_lists[i]
|
||
cur: t.CPtr = t.CPtr(head_val)
|
||
steps: t.CSizeT = 0
|
||
while t.CUInt64T(cur) != 0:
|
||
steps += 1
|
||
if steps > max_steps:
|
||
return 4 # free list 成环或节点数超上限(next 指针被破坏)
|
||
# Check pointer is in usable region range
|
||
if t.CUInt64T(cur) < t.CUInt64T(self.usable):
|
||
return 1
|
||
if t.CUInt64T(cur) >= t.CUInt64T(self.usable) + self.usable_size:
|
||
return 2
|
||
# Check alignment: order i blocks must be aligned to size _block_size_at_order(i)
|
||
offset: t.CSizeT = t.CUInt64T(cur) - t.CUInt64T(self.usable)
|
||
bs: t.CSizeT = _block_size_at_order(i)
|
||
if offset % bs != 0:
|
||
return 3
|
||
cur = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(cur)))
|
||
return 0
|
||
|
||
def check_alive_blocks(self, from_mark: t.CSizeT) -> t.CInt:
|
||
# 检查 [from_mark, chk_depth) 记录的所有已分配块 header 完整性。
|
||
# 越界写(参数错位/下标 GEP 越界)会破坏相邻块 header(奇偶位/order 错乱),
|
||
# 返回 0=OK,非 0=损坏(rc 语义见调用方注释)。
|
||
# 用于 reset_to 前哨兵:把"崩溃点漂移"变成"确定性定位到损坏发生的文件"。
|
||
if self.chk_stack is None:
|
||
return 0
|
||
if from_mark > self.chk_depth:
|
||
# from_mark 大于当前深度:调用方 mark 值异常(可能读错字段/跨实例),
|
||
# 校验将 no-op → 越界写漏检。fail-fast 暴露。
|
||
stdio.printf(
|
||
"[MEM] check_alive_blocks from_mark=%zu > chk_depth=%zu(mark 异常)\n",
|
||
from_mark, self.chk_depth)
|
||
return 0
|
||
if from_mark >= self.chk_depth:
|
||
return 0
|
||
if self.usable is None:
|
||
return 0
|
||
ci: t.CSizeT = from_mark
|
||
while ci < self.chk_depth:
|
||
block: t.CPtr = t.CPtr(self.chk_stack[ci])
|
||
if block is not None:
|
||
if t.CUInt64T(block) < t.CUInt64T(self.usable):
|
||
# [TEMP-DIAG] 现场:块地址低于池起点(%llu/%zu 格式先例见 L861/L1216)
|
||
stdio.printf("[MEM] 存活块损坏 rc=1(块地址低于 usable): block=%llu usable=%llu ci=%zu from_mark=%zu\n",
|
||
t.CUInt64T(block), t.CUInt64T(self.usable), ci, from_mark)
|
||
return 1
|
||
if t.CUInt64T(block) >= t.CUInt64T(self.usable) + self.usable_size:
|
||
# [TEMP-DIAG] 现场:块地址超出池末尾
|
||
stdio.printf("[MEM] 存活块损坏 rc=2(块地址超出池末尾): block=%llu usable=%llu ci=%zu from_mark=%zu\n",
|
||
t.CUInt64T(block), t.CUInt64T(self.usable), ci, from_mark)
|
||
return 2
|
||
stored: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block)))
|
||
stored_val: t.CUInt64T = t.CUInt64T(stored)
|
||
if (stored_val & 1) != 0:
|
||
# 活块:header 必须是 (order<<1)|1,order 合法且块对齐到该 order 大小
|
||
order: t.CInt = t.CInt(stored_val >> 1)
|
||
if order < 0:
|
||
# [TEMP-DIAG] 现场:order 为负(header 被写穿)
|
||
stdio.printf("[MEM] 存活块损坏 rc=3(order 为负): block=%llu order=%d ci=%zu from_mark=%zu\n",
|
||
t.CUInt64T(block), order, ci, from_mark)
|
||
return 3
|
||
if order > self.max_order:
|
||
# [TEMP-DIAG] 现场:order 超 max_order(%d 先例见 L861 max_order=%d)
|
||
stdio.printf("[MEM] 存活块损坏 rc=4(order 超 max_order): block=%llu order=%d max_order=%d ci=%zu from_mark=%zu\n",
|
||
t.CUInt64T(block), order, self.max_order, ci, from_mark)
|
||
return 4
|
||
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
|
||
bs: t.CSizeT = _block_size_at_order(order)
|
||
if offset % bs != 0:
|
||
# [TEMP-DIAG] 现场:块未对齐到该 order 的块大小
|
||
stdio.printf("[MEM] 存活块损坏 rc=5(块未对齐 order 大小): block=%llu order=%d bs=%zu offset=%zu ci=%zu from_mark=%zu\n",
|
||
t.CUInt64T(block), order, bs, offset, ci, from_mark)
|
||
return 5
|
||
# 块尾 canary 校验:越界写穿数据区会破坏块尾哨兵,
|
||
# 立即定位到越界写发生的文件(崩溃点漂移的关键哨兵)。
|
||
# 与 alloc 写入点统一使用编译期常量 MEMBUDDY_CANARY_PATTERN(同值同宽)。
|
||
can_stored: t.CPtr = t.CPtr(c.Deref((t.CUInt64T | t.CPtr)(block) + bs - MEMBUDDY_CANARY))
|
||
if t.CUInt64T(can_stored) != t.CUInt64T(MEMBUDDY_CANARY_PATTERN):
|
||
# [TEMP-DIAG] 越界写现场:块尾 canary 被写穿(rc=6 的判据)。
|
||
# 原实现只回 rc(int),调用方拿不到块/阶/实际值 → 无法定位。
|
||
# 此处补齐现场字段(全部为当前作用域既有变量,不新增取值路径):
|
||
# block/order/bs 见上方 L1241-1253 计算;ci/from_mark 为
|
||
# 校验下标与窗口起点;canary vs 期望均以十六进制打印。
|
||
# printf 形式与格式符沿用本文件既有先例:
|
||
# stdio.printf(...) / stdio.fflush(None) 见 L800-802、L816-817
|
||
# %zu ← t.CSizeT(L1216 from_mark/chk_depth、L765 chk_cap/depth)
|
||
# %llu ← t.CUInt64T(L800 size/ptr、L861 pool_base)
|
||
# %d ← t.CInt(L816 dbg_order、L822)
|
||
# %llx ← t.CUInt64T 十六进制(L917 header=0x%llX)
|
||
stdio.printf(
|
||
"[MEM] canary 损坏: block=%llu order=%d bs=%zu ci=%zu from_mark=%zu canary=0x%llx 期望=0x%llx\n",
|
||
t.CUInt64T(block), order, bs, ci, from_mark,
|
||
t.CUInt64T(can_stored), t.CUInt64T(MEMBUDDY_CANARY_PATTERN))
|
||
# [TEMP-DIAG] 位宽诊断:把上面同一对值(canary 实际读回值 / 期望
|
||
# 常量)再按高 32 位、低 32 位分别打印,用于在现场直接分辨:
|
||
# (i) 写入端只落了低 32 位(高 32 位为 0):canary_hi == 0x0;
|
||
# (ii) 比较端把 32 位常量做了符号扩展:期望_hi == 0xffffffff;
|
||
# 若 canary_hi 与 期望_hi 不一致,即"同一个编译期常量被两种不同
|
||
# 扩展方式抬到 64 位"的代码生成缺陷实锤(既有字段与 rc 不变)。
|
||
# 写法先例(均为本文件既有):
|
||
# t.CUInt64T 局部变量 + 移位:L806/L1056 ff_d: t.CUInt64T = t.CUInt64T(1) << 63
|
||
# CUInt64T 域内算术:L916 (rs & (t.CUInt64T(1) << 63))、L919 rs | ...
|
||
# %llx ← t.CUInt64T 十六进制:上行 canary=0x%llx / L932 header=0x%llX
|
||
# 低 32 位用"减去高半部"求得,不引入 >0x7FFFFFFF 的大字面量
|
||
# (本仓库踩过大字面量被解析截断的坑,见本文件 L120-122)。
|
||
can_d: t.CUInt64T = t.CUInt64T(can_stored)
|
||
exp_d: t.CUInt64T = t.CUInt64T(MEMBUDDY_CANARY_PATTERN)
|
||
can_hi: t.CUInt64T = can_d >> 32
|
||
can_lo: t.CUInt64T = can_d - (can_hi << 32)
|
||
exp_hi: t.CUInt64T = exp_d >> 32
|
||
exp_lo: t.CUInt64T = exp_d - (exp_hi << 32)
|
||
stdio.printf(
|
||
"[MEM] canary 位宽诊断: canary_lo=0x%llx canary_hi=0x%llx 期望_lo=0x%llx 期望_hi=0x%llx\n",
|
||
can_lo, can_hi, exp_lo, exp_hi)
|
||
# 调用方(Phase1Main)随后打印并 exit;此处先 fflush 保证
|
||
# 重定向下现场不丢(同 [DET] 终止路径,本文件 L802/L818)。
|
||
stdio.fflush(None)
|
||
return 6
|
||
ci += 1
|
||
return 0
|
||
|