517 lines
16 KiB
Markdown
517 lines
16 KiB
Markdown
|
|
# FAST_CAP=0 SEGV Root Cause Analysis
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
**Status:** Fix #1 and Fix #2 are implemented correctly BUT are **NOT BEING EXECUTED** in the crash scenario.
|
||
|
|
|
||
|
|
**Root Cause Discovered:** When `FAST_CAP=0` and `g_tls_list_enable=1` (TLS List mode), the free path **BYPASSES the freelist entirely** and stores freed blocks in TLS List cache. These blocks are **NEVER merged into the SuperSlab freelist** until TLS List spills. Meanwhile, the allocation path tries to allocate from the freelist, which contains **stale pointers** from cross-thread frees that were never drained.
|
||
|
|
|
||
|
|
**Critical Flow Bug:**
|
||
|
|
```
|
||
|
|
Thread A:
|
||
|
|
1. free(ptr) → g_fast_cap[cls]=0 → skip fast tier
|
||
|
|
2. g_tls_list_enable=1 → TLS List push (L75-79 in free.inc)
|
||
|
|
3. RETURNS WITHOUT TOUCHING FREELIST (meta->freelist unchanged)
|
||
|
|
4. Remote frees accumulate in remote_heads[] but NEVER get drained
|
||
|
|
|
||
|
|
Thread B:
|
||
|
|
1. alloc() → hak_tiny_alloc_superslab(cls)
|
||
|
|
2. meta->freelist EXISTS (has stale/remote pointers)
|
||
|
|
3. FIX #2 SHOULD drain here (L740-743) BUT...
|
||
|
|
4. has_remote = (remote_heads[idx] != 0) → FALSE (wrong index!)
|
||
|
|
5. Dereferences stale freelist → **SEGV**
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Why Fix #1 and Fix #2 Are Not Executed
|
||
|
|
|
||
|
|
### Fix #1 (superslab_refill L615-620): NOT REACHED
|
||
|
|
|
||
|
|
```c
|
||
|
|
// Fix #1: In superslab_refill() loop
|
||
|
|
for (int i = 0; i < tls_cap; i++) {
|
||
|
|
int has_remote = (atomic_load_explicit(&tls->ss->remote_heads[i], memory_order_acquire) != 0);
|
||
|
|
if (has_remote) {
|
||
|
|
ss_remote_drain_to_freelist(tls->ss, i); // ← This line NEVER executes
|
||
|
|
}
|
||
|
|
if (tls->ss->slabs[i].freelist) { ... }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why it doesn't execute:**
|
||
|
|
|
||
|
|
1. **Larson immediately crashes on first allocation miss**
|
||
|
|
- The allocation path is: `hak_tiny_alloc_superslab()` (L720) → checks existing `meta->freelist` (L737) → SEGV
|
||
|
|
- It **NEVER reaches** `superslab_refill()` (L755) because it crashes first!
|
||
|
|
|
||
|
|
2. **Even if it did reach refill:**
|
||
|
|
- Loop checks ALL slabs `i=0..tls_cap`, but the current TLS slab is `tls->slab_idx` (e.g., 7)
|
||
|
|
- When checking slab `i=0..6`, those slabs don't have `remote_heads[i]` set
|
||
|
|
- When checking slab `i=7`, it finds `freelist` exists and **RETURNS IMMEDIATELY** (L624) without draining!
|
||
|
|
|
||
|
|
### Fix #2 (hak_tiny_alloc_superslab L737-743): CONDITION ALWAYS FALSE
|
||
|
|
|
||
|
|
```c
|
||
|
|
if (meta && meta->freelist) {
|
||
|
|
int has_remote = (atomic_load_explicit(&tls->ss->remote_heads[tls->slab_idx], memory_order_acquire) != 0);
|
||
|
|
if (has_remote) { // ← ALWAYS FALSE!
|
||
|
|
ss_remote_drain_to_freelist(tls->ss, tls->slab_idx);
|
||
|
|
}
|
||
|
|
void* block = meta->freelist; // ← SEGV HERE
|
||
|
|
meta->freelist = *(void**)block;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why `has_remote` is always false:**
|
||
|
|
|
||
|
|
1. **Wrong understanding of remote queue semantics:**
|
||
|
|
- `remote_heads[idx]` is **NOT a flag** indicating "has remote frees"
|
||
|
|
- It's the **HEAD POINTER** of the remote queue linked list
|
||
|
|
- When TLS List mode is active, frees go to TLS List, **NOT to remote_heads[]**!
|
||
|
|
|
||
|
|
2. **Actual remote free flow in TLS List mode:**
|
||
|
|
```
|
||
|
|
hak_tiny_free() → class_idx detected → g_fast_cap=0 → skip fast
|
||
|
|
→ g_tls_list_enable=1 → TLS List push (L75-79)
|
||
|
|
→ RETURNS (L80) WITHOUT calling ss_remote_push()!
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Therefore:**
|
||
|
|
- `remote_heads[idx]` remains `NULL` (never used in TLS List mode)
|
||
|
|
- `has_remote` check is always false
|
||
|
|
- Drain never happens
|
||
|
|
- Freelist contains stale pointers from old allocations
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## The Missing Link: TLS List Spill Path
|
||
|
|
|
||
|
|
When TLS List is enabled, freed blocks flow like this:
|
||
|
|
|
||
|
|
```
|
||
|
|
free() → TLS List cache → [eventually] tls_list_spill_excess()
|
||
|
|
→ WHERE DO THEY GO? → Need to check tls_list_spill implementation!
|
||
|
|
```
|
||
|
|
|
||
|
|
**Hypothesis:** TLS List spill probably returns blocks to Magazine/Registry, **NOT to SuperSlab freelist**. This creates a **disconnect** where:
|
||
|
|
|
||
|
|
1. Blocks are allocated from SuperSlab freelist
|
||
|
|
2. Blocks are freed into TLS List
|
||
|
|
3. TLS List spills to Magazine/Registry (NOT back to freelist)
|
||
|
|
4. SuperSlab freelist becomes stale (contains pointers to freed memory)
|
||
|
|
5. Cross-thread frees accumulate in remote_heads[] but never merge
|
||
|
|
6. Next allocation from freelist → SEGV
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Evidence from Debug Ring Output
|
||
|
|
|
||
|
|
**Key observation:** `remote_drain` events are **NEVER** recorded in debug output.
|
||
|
|
|
||
|
|
**Why?**
|
||
|
|
- `TINY_RING_EVENT_REMOTE_DRAIN` is only recorded in `ss_remote_drain_to_freelist()` (superslab.h:341-344)
|
||
|
|
- But this function is never called because:
|
||
|
|
- Fix #1 not reached (crash before refill)
|
||
|
|
- Fix #2 condition always false (remote_heads[] unused in TLS List mode)
|
||
|
|
|
||
|
|
**What IS recorded:**
|
||
|
|
- `remote_push` events: Yes (cross-thread frees call ss_remote_push in some path)
|
||
|
|
- `remote_drain` events: No (never called)
|
||
|
|
- This confirms the diagnosis: **remote queues fill up but never drain**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Code Paths Verified
|
||
|
|
|
||
|
|
### Free Path (FAST_CAP=0, TLS List mode)
|
||
|
|
|
||
|
|
```
|
||
|
|
hak_tiny_free(ptr)
|
||
|
|
↓
|
||
|
|
hak_tiny_free_with_slab(ptr, NULL) // NULL = SuperSlab mode
|
||
|
|
↓
|
||
|
|
[L14-36] Cross-thread check → if different thread → hak_tiny_free_superslab() → ss_remote_push()
|
||
|
|
↓
|
||
|
|
[L38-51] g_debug_fast0 check → NO (not set)
|
||
|
|
↓
|
||
|
|
[L53-59] g_fast_cap[cls]=0 → SKIP fast tier
|
||
|
|
↓
|
||
|
|
[L61-92] g_tls_list_enable=1 → TLS List push → RETURN ✓
|
||
|
|
↓
|
||
|
|
NEVER REACHES Magazine/freelist code (L94+)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Problem:** Same-thread frees go to TLS List, **never update SuperSlab freelist**.
|
||
|
|
|
||
|
|
### Alloc Path (FAST_CAP=0)
|
||
|
|
|
||
|
|
```
|
||
|
|
hak_tiny_alloc(size)
|
||
|
|
↓
|
||
|
|
[Benchmark path disabled for FAST_CAP=0]
|
||
|
|
↓
|
||
|
|
hak_tiny_alloc_slow(size, cls)
|
||
|
|
↓
|
||
|
|
hak_tiny_alloc_superslab(cls)
|
||
|
|
↓
|
||
|
|
[L727-735] meta->freelist == NULL && used < cap → linear alloc (virgin slab)
|
||
|
|
↓
|
||
|
|
[L737-752] meta->freelist EXISTS → CHECK remote_heads[] (Fix #2)
|
||
|
|
↓
|
||
|
|
has_remote = (remote_heads[idx] != 0) → FALSE (TLS List mode doesn't use it)
|
||
|
|
↓
|
||
|
|
block = meta->freelist → **(void**)block → SEGV 💥
|
||
|
|
```
|
||
|
|
|
||
|
|
**Problem:** Freelist contains pointers to blocks that were:
|
||
|
|
1. Freed by same thread → went to TLS List
|
||
|
|
2. Freed by other threads → went to remote_heads[] but never drained
|
||
|
|
3. Never merged back to freelist
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Additional Problems Found
|
||
|
|
|
||
|
|
### 1. Ultra-Simple Free Path Incompatibility
|
||
|
|
|
||
|
|
When `g_tiny_ultra=1` (HAKMEM_TINY_ULTRA=1), the free path is:
|
||
|
|
|
||
|
|
```c
|
||
|
|
// hakmem_tiny_free.inc:886-908
|
||
|
|
if (g_tiny_ultra) {
|
||
|
|
// Detect class_idx from SuperSlab
|
||
|
|
// Push to TLS SLL (not TLS List!)
|
||
|
|
if (g_tls_sll_count[cls] < sll_cap) {
|
||
|
|
*(void**)ptr = g_tls_sll_head[cls];
|
||
|
|
g_tls_sll_head[cls] = ptr;
|
||
|
|
return; // BYPASSES remote queue entirely!
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Problem:** Ultra mode also bypasses remote queues for same-thread frees!
|
||
|
|
|
||
|
|
### 2. Linear Allocation Mode Confusion
|
||
|
|
|
||
|
|
```c
|
||
|
|
// L727-735: Linear allocation (freelist == NULL)
|
||
|
|
if (meta->freelist == NULL && meta->used < meta->capacity) {
|
||
|
|
void* block = slab_base + (meta->used * block_size);
|
||
|
|
meta->used++;
|
||
|
|
return block; // ✓ Safe (virgin memory)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**This is safe!** Linear allocation doesn't touch freelist at all.
|
||
|
|
|
||
|
|
**But next allocation:**
|
||
|
|
```c
|
||
|
|
// L737-752: Freelist allocation
|
||
|
|
if (meta->freelist) { // ← Freelist exists from OLD allocations
|
||
|
|
// Fix #2 check (always false in TLS List mode)
|
||
|
|
void* block = meta->freelist; // ← STALE POINTER
|
||
|
|
meta->freelist = *(void**)block; // ← SEGV 💥
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Root Cause Summary
|
||
|
|
|
||
|
|
**The fundamental issue:** HAKMEM has **TWO SEPARATE FREE PATHS**:
|
||
|
|
|
||
|
|
1. **SuperSlab freelist path** (original design)
|
||
|
|
- Frees update `meta->freelist` directly
|
||
|
|
- Cross-thread frees go to `remote_heads[]`
|
||
|
|
- Drain merges remote_heads[] → freelist
|
||
|
|
- Alloc pops from freelist
|
||
|
|
|
||
|
|
2. **TLS List/Magazine path** (optimization layer)
|
||
|
|
- Frees go to TLS cache (never touch freelist!)
|
||
|
|
- Spills go to Magazine → Registry
|
||
|
|
- **DISCONNECTED from SuperSlab freelist!**
|
||
|
|
|
||
|
|
**When FAST_CAP=0:**
|
||
|
|
- TLS List path is activated (no fast tier to bypass)
|
||
|
|
- ALL same-thread frees go to TLS List
|
||
|
|
- SuperSlab freelist is **NEVER UPDATED**
|
||
|
|
- Cross-thread frees accumulate in remote_heads[]
|
||
|
|
- remote_heads[] is **NEVER DRAINED** (Fix #2 check fails)
|
||
|
|
- Next alloc from stale freelist → **SEGV**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Why Debug Ring Produces No Output
|
||
|
|
|
||
|
|
**Expected:** SIGSEGV handler dumps Debug Ring before crash
|
||
|
|
|
||
|
|
**Actual:** Immediate crash with no output
|
||
|
|
|
||
|
|
**Possible reasons:**
|
||
|
|
|
||
|
|
1. **Stack corruption before handler runs**
|
||
|
|
- Freelist corruption may have corrupted stack
|
||
|
|
- Signal handler can't execute safely
|
||
|
|
|
||
|
|
2. **Handler not installed (HAKMEM_TINY_TRACE_RING=1 not set)**
|
||
|
|
- Check: `g_tiny_ring_enabled` must be 1
|
||
|
|
- Verify env var is exported BEFORE running Larson
|
||
|
|
|
||
|
|
3. **Fast crash (no time to record events)**
|
||
|
|
- Unlikely (should have at least ALLOC_ENTER events)
|
||
|
|
|
||
|
|
4. **Crash in signal handler itself**
|
||
|
|
- Handler uses async-signal-unsafe functions (write, fprintf)
|
||
|
|
- May fail if heap is corrupted
|
||
|
|
|
||
|
|
**Recommendation:** Add printf BEFORE running Larson to confirm:
|
||
|
|
```bash
|
||
|
|
HAKMEM_TINY_TRACE_RING=1 LD_PRELOAD=./libhakmem.so \
|
||
|
|
bash -c 'echo "Ring enabled: $HAKMEM_TINY_TRACE_RING"; ./larson_hakmem ...'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recommended Fixes
|
||
|
|
|
||
|
|
### Option A: Unconditional Drain in Alloc Path (SAFE, SIMPLE) ⭐⭐⭐⭐⭐
|
||
|
|
|
||
|
|
**Location:** `hak_tiny_alloc_superslab()` L737-752
|
||
|
|
|
||
|
|
**Change:**
|
||
|
|
```c
|
||
|
|
if (meta && meta->freelist) {
|
||
|
|
// UNCONDITIONAL drain: always merge remote frees before using freelist
|
||
|
|
// Cost: ~50-100ns (only when freelist exists, amortized by batch drain)
|
||
|
|
ss_remote_drain_to_freelist(tls->ss, tls->slab_idx);
|
||
|
|
|
||
|
|
// Now safe to use freelist
|
||
|
|
void* block = meta->freelist;
|
||
|
|
meta->freelist = *(void**)block;
|
||
|
|
meta->used++;
|
||
|
|
ss_active_inc(tls->ss);
|
||
|
|
return block;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Guarantees correctness (no stale pointers)
|
||
|
|
- Simple, easy to verify
|
||
|
|
- Only ~50-100ns overhead per allocation miss
|
||
|
|
|
||
|
|
**Cons:**
|
||
|
|
- May drain empty queues (wasted atomic load)
|
||
|
|
- Doesn't fix the root issue (TLS List disconnect)
|
||
|
|
|
||
|
|
### Option B: Force TLS List Spill to SuperSlab Freelist (CORRECT FIX) ⭐⭐⭐⭐
|
||
|
|
|
||
|
|
**Location:** `tls_list_spill_excess()` (need to find this function)
|
||
|
|
|
||
|
|
**Change:** Modify spill path to return blocks to **SuperSlab freelist** instead of Magazine:
|
||
|
|
|
||
|
|
```c
|
||
|
|
void tls_list_spill_excess(int class_idx, TinyTLSList* tls) {
|
||
|
|
SuperSlab* ss = g_tls_slabs[class_idx].ss;
|
||
|
|
if (!ss) { /* fallback to Magazine */ }
|
||
|
|
|
||
|
|
int slab_idx = g_tls_slabs[class_idx].slab_idx;
|
||
|
|
TinySlabMeta* meta = &ss->slabs[slab_idx];
|
||
|
|
|
||
|
|
// Spill half to SuperSlab freelist (under lock)
|
||
|
|
int spill_count = tls->count / 2;
|
||
|
|
for (int i = 0; i < spill_count; i++) {
|
||
|
|
void* ptr = tls_list_pop(tls);
|
||
|
|
// Push to freelist
|
||
|
|
*(void**)ptr = meta->freelist;
|
||
|
|
meta->freelist = ptr;
|
||
|
|
meta->used--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Fixes root cause (reconnects TLS List → SuperSlab)
|
||
|
|
- No allocation path overhead
|
||
|
|
- Maintains cache efficiency
|
||
|
|
|
||
|
|
**Cons:**
|
||
|
|
- Requires lock (spill is already under lock)
|
||
|
|
- Need to identify correct slab for each block (may be from different slabs)
|
||
|
|
|
||
|
|
### Option C: Disable TLS List Mode for FAST_CAP=0 (WORKAROUND) ⭐⭐⭐
|
||
|
|
|
||
|
|
**Location:** `hak_tiny_init()` or free path
|
||
|
|
|
||
|
|
**Change:**
|
||
|
|
```c
|
||
|
|
// In init:
|
||
|
|
if (g_fast_cap_all_zero) {
|
||
|
|
g_tls_list_enable = 0; // Force Magazine path
|
||
|
|
}
|
||
|
|
|
||
|
|
// Or in free path:
|
||
|
|
if (g_tls_list_enable && g_fast_cap[class_idx] == 0) {
|
||
|
|
// Force Magazine path for this class
|
||
|
|
goto use_magazine_path;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Minimal code change
|
||
|
|
- Forces consistent path (Magazine → freelist)
|
||
|
|
|
||
|
|
**Cons:**
|
||
|
|
- Doesn't fix the bug (just avoids it)
|
||
|
|
- Performance may suffer (Magazine has overhead)
|
||
|
|
|
||
|
|
### Option D: Track Freelist Validity (DEFENSIVE) ⭐⭐
|
||
|
|
|
||
|
|
**Add flag:** `meta->freelist_valid` (1 bit in meta)
|
||
|
|
|
||
|
|
**Set valid:** When updating freelist (free, spill)
|
||
|
|
**Clear valid:** When allocating from virgin slab
|
||
|
|
**Check valid:** Before dereferencing freelist
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Catches corruption early
|
||
|
|
- Good for debugging
|
||
|
|
|
||
|
|
**Cons:**
|
||
|
|
- Adds overhead (1 extra check per alloc)
|
||
|
|
- Doesn't fix the bug (just detects it)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recommended Action Plan
|
||
|
|
|
||
|
|
### Immediate (1 hour): Confirm Diagnosis
|
||
|
|
|
||
|
|
1. **Add printf at crash site:**
|
||
|
|
```c
|
||
|
|
// hakmem_tiny_free.inc L745
|
||
|
|
fprintf(stderr, "[ALLOC] freelist=%p remote_heads=%p tls_list_en=%d\n",
|
||
|
|
meta->freelist,
|
||
|
|
(void*)atomic_load_explicit(&tls->ss->remote_heads[tls->slab_idx], memory_order_acquire),
|
||
|
|
g_tls_list_enable);
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Run Larson with FAST_CAP=0:**
|
||
|
|
```bash
|
||
|
|
HAKMEM_TINY_FAST_CAP=0 HAKMEM_LARSON_TINY_ONLY=1 \
|
||
|
|
HAKMEM_TINY_TRACE_RING=1 ./larson_hakmem 2 8 128 1024 1 12345 4 2>&1 | tee crash.log
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Verify output shows:**
|
||
|
|
- `freelist != NULL` (stale freelist exists)
|
||
|
|
- `remote_heads == NULL` (never used in TLS List mode)
|
||
|
|
- `tls_list_en = 1` (TLS List mode active)
|
||
|
|
|
||
|
|
### Short-term (2 hours): Implement Option A
|
||
|
|
|
||
|
|
**Safest, fastest fix:**
|
||
|
|
|
||
|
|
1. Edit `core/hakmem_tiny_free.inc` L737-743
|
||
|
|
2. Change conditional drain to **unconditional**
|
||
|
|
3. `make clean && make`
|
||
|
|
4. Test with Larson FAST_CAP=0
|
||
|
|
5. Verify no SEGV, measure performance impact
|
||
|
|
|
||
|
|
### Medium-term (1 day): Implement Option B
|
||
|
|
|
||
|
|
**Proper fix:**
|
||
|
|
|
||
|
|
1. Find `tls_list_spill_excess()` implementation
|
||
|
|
2. Add path to return blocks to SuperSlab freelist
|
||
|
|
3. Test with all configurations (FAST_CAP=0/64, TLS_LIST=0/1)
|
||
|
|
4. Measure performance vs. current
|
||
|
|
|
||
|
|
### Long-term (1 week): Unified Free Path
|
||
|
|
|
||
|
|
**Ultimate solution:**
|
||
|
|
|
||
|
|
1. Audit all free paths (TLS List, Magazine, Fast, Ultra, SuperSlab)
|
||
|
|
2. Ensure consistency: freed blocks ALWAYS return to owner slab
|
||
|
|
3. Remote frees ALWAYS go through remote queue (or mailbox)
|
||
|
|
4. Drain happens at predictable points (refill, alloc miss, periodic)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing Strategy
|
||
|
|
|
||
|
|
### Minimal Repro Test (30 seconds)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Single-thread (should work)
|
||
|
|
HAKMEM_TINY_FAST_CAP=0 HAKMEM_LARSON_TINY_ONLY=1 \
|
||
|
|
./larson_hakmem 2 8 128 1024 1 12345 1
|
||
|
|
|
||
|
|
# Multi-thread (crashes)
|
||
|
|
HAKMEM_TINY_FAST_CAP=0 HAKMEM_LARSON_TINY_ONLY=1 \
|
||
|
|
./larson_hakmem 2 8 128 1024 1 12345 4
|
||
|
|
```
|
||
|
|
|
||
|
|
### Comprehensive Test Matrix
|
||
|
|
|
||
|
|
| FAST_CAP | TLS_LIST | THREADS | Expected | Notes |
|
||
|
|
|----------|----------|---------|----------|-------|
|
||
|
|
| 0 | 0 | 1 | ✓ | Magazine path, single-thread |
|
||
|
|
| 0 | 0 | 4 | ? | Magazine path, may crash |
|
||
|
|
| 0 | 1 | 1 | ✓ | TLS List, no cross-thread |
|
||
|
|
| 0 | 1 | 4 | ✗ | **CURRENT BUG** |
|
||
|
|
| 64 | 0 | 4 | ✓ | Fast tier absorbs cross-thread |
|
||
|
|
| 64 | 1 | 4 | ✓ | Fast tier + TLS List |
|
||
|
|
|
||
|
|
### Validation After Fix
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# All these should pass:
|
||
|
|
for CAP in 0 64; do
|
||
|
|
for TLS in 0 1; do
|
||
|
|
for T in 1 2 4 8; do
|
||
|
|
echo "Testing FAST_CAP=$CAP TLS_LIST=$TLS THREADS=$T"
|
||
|
|
HAKMEM_TINY_FAST_CAP=$CAP HAKMEM_TINY_TLS_LIST=$TLS \
|
||
|
|
HAKMEM_LARSON_TINY_ONLY=1 \
|
||
|
|
timeout 10 ./larson_hakmem 2 8 128 1024 1 12345 $T || echo "FAIL"
|
||
|
|
done
|
||
|
|
done
|
||
|
|
done
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files to Investigate Further
|
||
|
|
|
||
|
|
1. **TLS List spill implementation:**
|
||
|
|
```bash
|
||
|
|
grep -rn "tls_list_spill" core/
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Magazine spill path:**
|
||
|
|
```bash
|
||
|
|
grep -rn "mag.*spill" core/hakmem_tiny_free.inc
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Remote drain call sites:**
|
||
|
|
```bash
|
||
|
|
grep -rn "ss_remote_drain" core/
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
**Root Cause:** TLS List mode (active when FAST_CAP=0) bypasses SuperSlab freelist for same-thread frees. Freed blocks go to TLS cache → Magazine → Registry, never returning to SuperSlab freelist. Meanwhile, freelist contains stale pointers from old allocations. Cross-thread frees accumulate in remote_heads[] but Fix #2's drain check always fails because TLS List mode doesn't use remote_heads[].
|
||
|
|
|
||
|
|
**Why Fixes Don't Work:**
|
||
|
|
- Fix #1: Never reached (crash before refill)
|
||
|
|
- Fix #2: Condition always false (remote_heads[] unused)
|
||
|
|
|
||
|
|
**Recommended Fix:** Option A (unconditional drain) for immediate safety, Option B (fix spill path) for proper solution.
|
||
|
|
|
||
|
|
**Next Steps:**
|
||
|
|
1. Confirm diagnosis with printf
|
||
|
|
2. Implement Option A
|
||
|
|
3. Test thoroughly
|
||
|
|
4. Plan Option B implementation
|