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

19 KiB
Raw Blame History

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()

// 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

// 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 リセット

// 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()

// 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 チェック

// 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 ループ

// 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 への重複登録防止

// 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() は通知のみ

// 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() は記録のみ

// 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() は読み込みと提示のみ

// 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 + 通知

// 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 厳守 ✓ 以下のコメント参照:

// 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 + リセット+利用のみ

// 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 境界で実施

// 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 チェック漏れ" は以下で修正済み:

// 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

// 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 時)

// 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 を走査

テスト・デバッグ提案

# 散発的なバグを局所化
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化できる可能性あり。