Files
Viper_VRTE/memhub.vp
T
2026-09-16 16:07:35 +08:00

1317 lines
71 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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