Files
hakmem/docs/analysis/BOX_THEORY_VERIFICATION_REPORT.md
Moe Charm (CI) a9ddb52ad4 ENV cleanup: Remove BG/HotMag vars & guard fprintf (Larson 52.3M ops/s)
Phase 1 完了:環境変数整理 + fprintf デバッグガード

ENV変数削除(BG/HotMag系):
- core/hakmem_tiny_init.inc: HotMag ENV 削除 (~131 lines)
- core/hakmem_tiny_bg_spill.c: BG spill ENV 削除
- core/tiny_refill.h: BG remote 固定値化
- core/hakmem_tiny_slow.inc: BG refs 削除

fprintf Debug Guards (#if !HAKMEM_BUILD_RELEASE):
- core/hakmem_shared_pool.c: Lock stats (~18 fprintf)
- core/page_arena.c: Init/Shutdown/Stats (~27 fprintf)
- core/hakmem.c: SIGSEGV init message

ドキュメント整理:
- 328 markdown files 削除(旧レポート・重複docs)

性能確認:
- Larson: 52.35M ops/s (前回52.8M、安定動作)
- ENV整理による機能影響なし
- Debug出力は一部残存(次phase で対応)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 14:45:26 +09:00

523 lines
19 KiB
Markdown
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.

# Box Theory 残り境界の徹底検証レポート
## 調査概要
HAKMEM tiny allocator の Box Theory箱理論における 3つの残り境界Box 3, 2, 4の詳細検証結果。
検証対象ファイル:
- core/hakmem_tiny_free.inc (メイン free ロジック)
- core/slab_handle.h (所有権管理)
- core/tiny_publish.c publish 実装)
- core/tiny_mailbox.c mailbox 実装)
- core/tiny_remote.c remote queue 操作)
- core/hakmem_tiny_superslab.h owner/drain 実装)
- core/hakmem_tiny.c publish/adopt 実装)
---
## Box 3: Same-thread Freelist Push 検証
### 不変条件
**freelist への push は `owner_tid == my_tid` の時のみ**
### 検証結果
#### ✅ 問題なし: slab_handle.h の slab_freelist_push()
```c
// core/slab_handle.h:205-236
static inline int slab_freelist_push(SlabHandle* h, void* ptr) {
if (!h || !h->valid) {
return 0; // Box: No ownership → FAIL
}
// ...
// Ownership guaranteed by valid==1 → safe to modify freelist
*(void**)ptr = h->meta->freelist;
h->meta->freelist = ptr;
// ...
return 1;
}
```
✓ 所有権チェックvalid==1を確認後のみ freelist 操作
✓ 直接 freelist push の唯一の安全な入口
#### ✅ 問題なし: hakmem_tiny_free.inc の same-thread freelist push
```c
// core/hakmem_tiny_free.inc:1044-1076
if (!g_tiny_force_remote && meta->owner_tid != 0 && meta->owner_tid == my_tid) {
// Fast path: Direct freelist push (same-thread)
// ...
if (!tiny_remote_guard_allow_local_push(ss, slab_idx, meta, ptr, "local_free", my_tid)) {
// Fall back to remote if guard fails
int transitioned = ss_remote_push(ss, slab_idx, ptr);
// ...
return;
}
void* prev = meta->freelist;
*(void**)ptr = prev;
meta->freelist = ptr; // ← Safe freelist push
// ...
}
```
✓ owner_tid == my_tid の厳密なチェック
✓ guard check で追加の安全性確保
✓ owner_tid != my_tid の場合は確実に remote_push へ
#### ✅ 問題なし: publish 時の owner_tid リセット
```c
// core/hakmem_tiny.c:639-670 (ss_partial_publish)
for (int s = 0; s < cap_pub; s++) {
uint32_t prev = __atomic_exchange_n(&ss->slabs[s].owner_tid, 0u, __ATOMIC_RELEASE);
// ...記録のみ...
}
```
✓ publish 時に明示的に owner_tid=0 をセット
✓ ATOMIC_RELEASE で memory barrier 確保
**Box 3 評価: ✅ PASS - 境界は堅牢。直接 freelist push は所有権ガード完全。**
---
## Box 2: Remote Push の重複dup_push検証
### 不変条件
**同じノードを remote queue に二重 push しない**
### 検証結果
#### ✅ 問題なし: tiny_remote_queue_contains_guard()
```c
// core/hakmem_tiny_free.inc:10-30
static inline int tiny_remote_queue_contains_guard(SuperSlab* ss, int slab_idx, void* target) {
if (!ss || slab_idx < 0) return 0;
uintptr_t cur = atomic_load_explicit(&ss->remote_heads[slab_idx], memory_order_acquire);
int limit = 8192;
while (cur && limit-- > 0) {
if ((void*)cur == target) {
return 1; // Found duplicate
}
uintptr_t next;
if (__builtin_expect(g_remote_side_enable, 0)) {
next = tiny_remote_side_get(ss, slab_idx, (void*)cur);
} else {
next = atomic_load_explicit((_Atomic uintptr_t*)cur, memory_order_relaxed);
}
cur = next;
}
if (limit <= 0) {
return 1; // fail-safe: treat unbounded traversal as duplicate
}
return 0;
}
```
✓ 8192 ノード上限でループ安全化
✓ Fail-safe: 上限に達したら dup として扱うconservative
✓ remote_side 両対応
#### ✅ 問題なし: free 時の dup_remote チェック
```c
// core/hakmem_tiny_free.inc:1183-1197
int dup_remote = tiny_remote_queue_contains_guard(ss, slab_idx, ptr);
if (!dup_remote && __builtin_expect(g_remote_side_enable, 0)) {
dup_remote = (head_word == TINY_REMOTE_SENTINEL) ||
tiny_remote_side_contains(ss, slab_idx, ptr);
}
// ...
if (dup_remote) {
uintptr_t aux = tiny_remote_pack_diag(0xA214u, ss_base, ss_size, (uintptr_t)ptr);
tiny_remote_watch_mark(ptr, "dup_prevent", my_tid);
tiny_remote_watch_note("dup_prevent", ss, slab_idx, ptr, 0xA214u, my_tid, 0);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return; // ← Prevent double-push
}
```
✓ 二重チェックqueue walk + side table
✓ A214 コードdup_preventで検出を記録
✓ Fail-Fast: 検出後は即座に returnpush しない)
#### ✅ 問題なし: ss_remote_push() の CAS ループ
```c
// core/hakmem_tiny_superslab.h:282-376
_Atomic(uintptr_t)* head = &ss->remote_heads[slab_idx];
uintptr_t old;
do {
old = atomic_load_explicit(head, memory_order_acquire);
if (!g_remote_side_enable) {
*(void**)ptr = (void*)old; // legacy embedding
}
} while (!atomic_compare_exchange_weak_explicit(head, &old, (uintptr_t)ptr,
memory_order_release,
memory_order_relaxed));
```
✓ CAS ループで atomic な single-pop-then-push
✓ ptr は new head になるのみ(二重化不可)
#### ✅ 問題なし: tiny_remote_side_set() で remote_side への重複登録防止
```c
// core/tiny_remote.c:529-575
uint32_t i = hmix(k) & (REM_SIDE_SIZE - 1);
for (uint32_t n=0; n<REM_SIDE_SIZE; n++, i=(i+1)&(REM_SIDE_SIZE-1)) {
uintptr_t expect = 0;
if (atomic_compare_exchange_weak_explicit(&g_rem_side[i].key, &expect, k,
memory_order_acq_rel,
memory_order_relaxed)) {
atomic_store_explicit(&g_rem_side[i].val, next, memory_order_release);
tiny_remote_sentinel_set(node);
return;
} else if (expect == k) {
// ← Duplicate detection
if (__builtin_expect(g_debug_remote_guard, 0)) {
uintptr_t observed = atomic_load_explicit((_Atomic uintptr_t*)node,
memory_order_relaxed);
tiny_remote_report_corruption("dup_push", node, observed);
uintptr_t aux = tiny_remote_pack_diag(0xA212u, base, ss_size, (uintptr_t)node);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, node, aux);
// ...dump + raise...
}
return; // ← Prevent duplicate
}
}
```
✓ Side table の CAS-or-collision チェック
✓ A212 コードdup_pushで検出・記録
✓ 既に key=k の entry があれば即座に return二重登録防止
**Box 2 評価: ✅ PASS - 二重 push は 3 層で防止。A214/A212 コード検出も有効。**
---
## Box 4: Publish/Fetch は通知のみ検証
### 不変条件
**publish/fetch 側から drain や owner_tid を触らない**
### 検証結果
#### ✅ 問題なし: tiny_publish_notify() は通知のみ
```c
// core/tiny_publish.c:13-34
void tiny_publish_notify(int class_idx, SuperSlab* ss, int slab_idx) {
if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) {
tiny_debug_ring_record(TINY_RING_EVENT_SUPERSLAB_ADOPT_FAIL,
(uint16_t)0xEEu, ss, (uintptr_t)class_idx);
return;
}
g_pub_notify_calls[class_idx]++;
tiny_debug_ring_record(TINY_RING_EVENT_SUPERSLAB_PUBLISH,
(uint16_t)class_idx, ss, (uintptr_t)slab_idx);
// ...トレース(副作用なし)...
tiny_mailbox_publish(class_idx, ss, slab_idx); // ← 単なる通知
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ mailbox へ (class_idx, ss, slab_idx) の 3-tuple を記録するのみ
#### ✅ 問題なし: tiny_mailbox_publish() は記録のみ
```c
// core/tiny_mailbox.c:109-119
void tiny_mailbox_publish(int class_idx, SuperSlab* ss, int slab_idx) {
tiny_mailbox_register(class_idx);
// Encode entry locally
uintptr_t ent = ((uintptr_t)ss) | ((uintptr_t)slab_idx & 0x3Fu);
uint32_t slot = g_tls_mailbox_slot[class_idx];
tiny_debug_ring_record(TINY_RING_EVENT_MAILBOX_PUBLISH, ...);
atomic_store_explicit(&g_pub_mailbox_entries[class_idx][slot], ent,
memory_order_release); // ← 単なる記録
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ メモリへの記録のみ
#### ✅ 問題なし: tiny_mailbox_fetch() は読み込みと提示のみ
```c
// core/tiny_mailbox.c:130-252
uintptr_t tiny_mailbox_fetch(int class_idx) {
// ...スロット走査...
uintptr_t ent = atomic_exchange_explicit(mailbox, (uintptr_t)0, memory_order_acq_rel);
if (ent) {
g_pub_mail_hits[class_idx]++;
SuperSlab* ss = (SuperSlab*)(ent & ~((uintptr_t)SUPERSLAB_SIZE_MIN - 1u));
int slab = (int)(ent & 0x3Fu);
tiny_debug_ring_record(TINY_RING_EVENT_MAILBOX_FETCH, ...);
return ent; // ← ヒントを返すのみ
}
return (uintptr_t)0;
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ fetch は単なる "ヒント提供"(候補の推薦)
#### ✅ 問題なし: ss_partial_publish() は owner リセット + unbind + 通知
```c
// core/hakmem_tiny.c:639-717
void ss_partial_publish(int class_idx, SuperSlab* ss) {
if (!ss) return;
// ① owner_tid リセットpublish の一部)
unsigned prev = atomic_exchange_explicit(&ss->listed, 1u, memory_order_acq_rel);
if (prev != 0u) return; // already listed
// ② 所有者をリセットadopt 準備)
int cap_pub = ss_slabs_capacity(ss);
for (int s = 0; s < cap_pub; s++) {
uint32_t prev = __atomic_exchange_n(&ss->slabs[s].owner_tid, 0u, __ATOMIC_RELEASE);
// ...記録のみ...
}
// ③ TLS unbindpublish 側が使わなくするため)
extern __thread TinyTLSSlab g_tls_slabs[];
if (g_tls_slabs[class_idx].ss == ss) {
g_tls_slabs[class_idx].ss = NULL;
g_tls_slabs[class_idx].meta = NULL;
g_tls_slabs[class_idx].slab_base = NULL;
g_tls_slabs[class_idx].slab_idx = 0;
}
// ④ hint 計算(提示用)
// ...hint を計算して ss->publish_hint セット...
// ⑤ ring に登録(通知)
for (int i = 0; i < SS_PARTIAL_RING; i++) {
// ...ring の empty slot を探して登録...
}
}
```
✓ drain 呼び出しなし(重要!)
✓ owner_tid リセットは「publish の責務」の範囲内adopter 準備)
**NOTE: publish 側から drain を呼ばない** ← Box 4 厳守
✓ 以下のコメント参照:
```c
// NOTE: Do NOT drain here! The old SuperSlab may have slabs owned by other threads
// that just adopted from it. Draining without ownership checks causes freelist corruption.
// The adopter will drain when needed (with proper ownership checks in tiny_refill.h).
```
#### ✅ 問題なし: ss_partial_adopt() は fetch + リセット+利用のみ
```c
// core/hakmem_tiny.c:719-742
SuperSlab* ss_partial_adopt(int class_idx) {
for (int i = 0; i < SS_PARTIAL_RING; i++) {
SuperSlab* ss = atomic_exchange_explicit(&g_ss_partial_ring[class_idx][i],
NULL, memory_order_acq_rel);
if (ss) {
// Clear listed flag to allow future publish
atomic_store_explicit(&ss->listed, 0u, memory_order_release);
g_ss_adopt_dbg[class_idx]++;
return ss; // ← 利用側へ返却
}
}
// Fallback: adopt from overflow stack
while (1) {
SuperSlab* head = atomic_load_explicit(&g_ss_partial_over[class_idx],
memory_order_acquire);
if (!head) break;
SuperSlab* next = head->partial_next;
if (atomic_compare_exchange_weak_explicit(&g_ss_partial_over[class_idx], &head, next,
memory_order_acq_rel, memory_order_relaxed)) {
atomic_store_explicit(&head->listed, 0u, memory_order_release);
g_ss_adopt_dbg[class_idx]++;
return head; // ← 利用側へ返却
}
}
return NULL;
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし(すでに publish で 0 にされている)
✓ 単なる slab の検索+返却
#### ✅ 問題なし: adopt 側での drain は refill 境界で実施
```c
// core/hakmem_tiny_free.inc:696-740
// (superslab_refill 内)
SuperSlab* adopt = ss_partial_adopt(class_idx);
if (adopt && adopt->magic == SUPERSLAB_MAGIC) {
// ...best slab 探索...
if (best >= 0) {
uint32_t self = tiny_self_u32();
SlabHandle h = slab_try_acquire(adopt, best, self); // ← Box 3: 所有権取得
if (slab_is_valid(&h)) {
slab_drain_remote_full(&h); // ← Box 2: 所有権ガード下で drain
if (slab_remote_pending(&h)) {
// ...pending check...
slab_release(&h);
}
if (slab_freelist(&h)) {
tiny_tls_bind_slab(tls, h.ss, h.slab_idx); // ← Box 3: bind
return h.ss;
}
slab_release(&h);
}
}
}
```
**drain は採用側の refill 境界で実施** ← Box 4 完全遵守
✓ 所有権取得 → drain → bind の順序が正確
✓ slab_handle.h の slab_drain_remote() でガード
**Box 4 評価: ✅ PASS - publish/fetch は純粋な通知。drain は adopter 側境界でのみ実施。**
---
## 残り問題の検証: TOCTOU バグ(既知)
### 既知: Box 2→3 の TOCTOU バグ(修正済み)
前述の "drain 後に remote_pending チェック漏れ" は以下で修正済み:
```c
// core/hakmem_tiny_free.inc:714-717
SlabHandle h = slab_try_acquire(adopt, best, self);
if (slab_is_valid(&h)) {
slab_drain_remote_full(&h);
if (slab_remote_pending(&h)) { // ← チェック追加(修正)
slab_release(&h);
// continue to next candidate
}
}
```
✓ drain 完了後に remote_pending をチェック
✓ pending がまだあれば acquire を release して次へ
✓ TOCTOU window を最小化
---
## 追加調査: Remote A213/A202 コードの生成源特定
### A213: pre_push corruptionTLS guard scribble
```c
// core/hakmem_tiny_free.inc:1187-1207
if (__builtin_expect(head_word == TINY_REMOTE_SENTINEL && !dup_remote && g_debug_remote_guard, 0)) {
tiny_remote_watch_note("dup_scan_miss", ss, slab_idx, ptr, 0xA215u, my_tid, 0);
}
if (dup_remote) {
// ...A214...
}
if (__builtin_expect(g_remote_side_enable && (head_word & 0xFFFFu) == 0x6261u, 0)) {
// TLS guard scribble detected on the node's first word
uintptr_t aux = tiny_remote_pack_diag(0xA213u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, ptr, aux);
tiny_remote_watch_mark(ptr, "pre_push", my_tid);
tiny_remote_watch_note("pre_push", ss, slab_idx, ptr, 0xA231u, my_tid, 0);
tiny_remote_report_corruption("pre_push", ptr, head_word);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
```
✓ A213: 発見元は hakmem_tiny_free.inc:1198-1206
✓ 原因: node の first word に 0x6261 (ba) scribble が見られた
✓ 意味: 同じ pointer で既に ss_remote_side_set が呼ばれている可能性
✓ 修正: dup_remote チェックで事前に防止(現実装で動作)
### A202: sentinel corruptiondrain 時)
```c
// core/hakmem_tiny_superslab.h:409-427
if (__builtin_expect(g_remote_side_enable, 0)) {
if (!tiny_remote_sentinel_ok(node)) {
uintptr_t aux = tiny_remote_pack_diag(0xA202u, base, ss_size, (uintptr_t)node);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, node, aux);
// ...corruption report...
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
}
tiny_remote_side_clear(ss, slab_idx, node);
}
```
✓ A202: 発見元は hakmem_tiny_superslab.h:410
✓ 原因: drain 時に node の sentinel が不正0xBADA55... ではない)
✓ 意味: node の first word が何らかの理由で上書きされた
✓ 対策: g_remote_side_enable でも sentinel チェック
---
## Box Theory の完全性評価
### Box 境界チェックリスト
| Box | 機能 | 不変条件 | 検証 | 評価 |
|-----|------|---------|------|------|
| **Box 1** | Atomic Ops | CAS/Exchange の秩序付けRelease/Acquire | 記載省略(下層) | ✅ |
| **Box 2** | Remote Queue | push は freelist/owner に触れない | 二重 push: A214/A212 | ✅ PASS |
| **Box 3** | Ownership | acquire/release の正確性 | owner_tid CAS | ✅ PASS |
| **Box 4** | Publish/Adopt | publish から drain 呼ばない | 採用境界分離確認 | ✅ PASS |
| **Box 3↔2** | Drain boundary | ownership 確保後 drain | slab_handle.h 経由 | ✅ PASS |
| **Box 4→3** | Adopt boundary | drain→bind→owner の順序 | refill 1箇所 | ✅ PASS |
### 結論
**✅ Box 境界の不変条件は厳密に守られている。**
1. **Box 3 (Ownership)**:
- freelist push は owner_tid==my_tid のみ
- publish 時の owner リセットが明確
- slab_handle.h の SlabHandle でガード完全
2. **Box 2 (Remote Queue)**:
- 二重 push は 3 層で防止free 側: A214, side-set: A212, traverse limit: fail-safe
- remote_side の sentinel で追加保護
- drain 時の sentinel チェックで corruption 検出
3. **Box 4 (Publish/Fetch)**:
- publish は owner リセット+通知のみ
- drain は publish 側では呼ばない
- 採用側 refill 境界でのみ drainownership ガード下)
4. **remote_invalid の A213/A202 検出**:
- A213: dup_remote チェック1183で事前防止
- A202: sentinel 検査410で drain 時検出
- どちらも fail-fast で即座に報告・停止
---
## 推奨事項
### 現在の状態
**Box Theory の実装は健全です。散発的な remote_invalid は以下に起因する可能性:**
1. **Timing window**
- publish → unlistedcatalog から外れる)→ adopt の間に
- owner=0 のまま別スレッドが allocate する可能性は低いが、エッジケースあり得る
2. **Platform memory ordering**
- x86: Acquire/Release は効くが、他の platform では要注意
- memory_order_acq_rel で CAS してるので current は安全
3. **Rare race in ss_partial_adopt()**
- overflow stack での LIFO pop と新規登録の タイミング
- 概率は低いが、同時並行で複数スレッドが overflow を走査
### テスト・デバッグ提案
```bash
# 散発的なバグを局所化
HAKMEM_TINY_REMOTE_SIDE=1 # Side table 有効化
HAKMEM_DEBUG_COUNTERS=1 # 統計カウント
HAKMEM_TINY_RF_TRACE=1 # publish/fetch の トレース
HAKMEM_TINY_SS_ADOPT=1 # SuperSlab adopt 有効化
# 検出時のダンプ
HAKMEM_TINY_MAILBOX_SLOWDISC=1 # Slow discovery
```
---
## まとめ
**徹底検証の結果、Box 3, 2, 4 の不変条件は守られている。**
- Box 3: freelist push は所有権ガード完全 ✅
- Box 2: 二重 push は 3 層で防止 ✅
- Box 4: publish/fetch は純粋な通知、drain は adopter 側 ✅
remote_invalid (A213/A202) の散発は、Box Theory のバグではなく、
**edge case in timing** である可能性が高い。
TOCTOU window 最小化と memory barrier の強化で、
さらに robust化できる可能性あり。