// ss_tier_box.h - P-Tier: Utilization-Aware SuperSlab Tiering Box // Purpose: Manage SuperSlab tier transitions based on utilization // License: MIT // Date: 2025-12-04 #ifndef HAK_SS_TIER_BOX_H #define HAK_SS_TIER_BOX_H #include #include #include // for getenv() #include "../superslab/superslab_types.h" // ============================================================================ // P-Tier: Utilization-Aware SuperSlab Tiering Box // ============================================================================ // // Goal: Reduce registry pressure by consolidating allocations to HOT SuperSlabs // and efficiently draining DRAINING SuperSlabs. // // Tier Definitions: // - HOT (>25% utilization): Accept new allocations, actively used // - DRAINING (<=25% utilization): Drain only, no new allocations // - FREE (0% utilization): Ready for LRU cache or munmap // // Strategy: // - Allocations target HOT tier SuperSlabs only // - DRAINING tier SuperSlabs accept no new allocations // - Automatic transitions based on utilization thresholds // - Hysteresis prevents thrashing between HOT and DRAINING // // Expected Benefits: // - Reduced registry size (fewer partially-used SuperSlabs) // - Improved cache locality (concentrated allocations) // - Faster allocation (skip DRAINING SuperSlabs) // - Efficient memory reclamation (clear path to FREE tier) // // Box Contract: // - ss_tier_calc_utilization(): Calculate current utilization [0.0, 1.0] // - ss_tier_check_transition(): Check and perform tier transitions // - ss_tier_get(): Get current tier // - ss_tier_is_hot(): Quick check if SuperSlab accepts allocations // - ss_tier_set(): Force tier change (testing/debug) // // ============================================================================ // Default thresholds (can be overridden by environment variables) #define SS_TIER_DOWN_THRESHOLD_DEFAULT 0.25f // HOT → DRAINING (25% utilization) #define SS_TIER_UP_THRESHOLD_DEFAULT 0.50f // DRAINING → HOT (50% utilization, hysteresis) // Environment variable support for runtime configuration // ENV: HAKMEM_SS_TIER_DOWN_THRESHOLD (default: 0.25) // ENV: HAKMEM_SS_TIER_UP_THRESHOLD (default: 0.50) static inline float ss_tier_get_down_threshold(void) { static float cached = -1.0f; if (__builtin_expect(cached < 0.0f, 0)) { const char* e = getenv("HAKMEM_SS_TIER_DOWN_THRESHOLD"); if (e && *e) { float v = (float)atof(e); cached = (v > 0.0f && v <= 1.0f) ? v : SS_TIER_DOWN_THRESHOLD_DEFAULT; } else { cached = SS_TIER_DOWN_THRESHOLD_DEFAULT; } } return cached; } static inline float ss_tier_get_up_threshold(void) { static float cached = -1.0f; if (__builtin_expect(cached < 0.0f, 0)) { const char* e = getenv("HAKMEM_SS_TIER_UP_THRESHOLD"); if (e && *e) { float v = (float)atof(e); cached = (v > 0.0f && v <= 1.0f) ? v : SS_TIER_UP_THRESHOLD_DEFAULT; } else { cached = SS_TIER_UP_THRESHOLD_DEFAULT; } } return cached; } // ============================================================================ // 1. Utilization Calculation // ============================================================================ // // Calculates current utilization as: total_active_blocks / total_capacity // // Uses: // - ss->total_active_blocks: Atomic counter of all active blocks across slabs // - ss->active_slabs: Number of carved slabs // - ss->slabs[].capacity: Per-slab capacity // // Returns: Utilization ratio [0.0, 1.0] // 0.0 = completely empty (FREE tier candidate) // 1.0 = fully utilized (strong HOT tier) // // Note: Uses relaxed memory order as this is a heuristic for tier classification, // not a safety-critical invariant. // ============================================================================ static inline float ss_tier_calc_utilization(SuperSlab* ss) { if (!ss) return 0.0f; // Get current active blocks (atomic load) uint32_t active = atomic_load_explicit(&ss->total_active_blocks, memory_order_relaxed); // Calculate total capacity across all active slabs // Note: We sum capacity from active_slabs to handle per-slab class assignment (Phase 12) uint32_t total_capacity = 0; uint32_t max_slabs = (1u << ss->lg_size) / SLAB_SIZE; if (max_slabs > SLABS_PER_SUPERSLAB_MAX) { max_slabs = SLABS_PER_SUPERSLAB_MAX; } // Iterate through active slabs and sum capacity for (uint32_t i = 0; i < max_slabs && i < ss->active_slabs; i++) { TinySlabMeta* meta = &ss->slabs[i]; if (meta->capacity > 0) { total_capacity += meta->capacity; } } // Handle edge case: no capacity yet (fresh SuperSlab) if (total_capacity == 0) { return 0.0f; } // Return utilization ratio return (float)active / (float)total_capacity; } // ============================================================================ // 2. Tier Transition Check // ============================================================================ // // Checks current utilization and performs tier transitions if needed. // // Transition Rules: // - HOT → DRAINING: utilization <= down_threshold (default: 25%) // - DRAINING → HOT: utilization >= up_threshold (default: 50%, hysteresis) // - DRAINING → FREE: utilization == 0% (all blocks freed) // - FREE → HOT: First allocation (handled by allocation path, not here) // // Hysteresis Rationale: // - Down threshold (25%) < Up threshold (50%) prevents oscillation // - SuperSlab must demonstrate sustained activity to return to HOT // // Returns: true if tier transition occurred, false otherwise // // Thread Safety: Uses atomic compare_exchange for safe concurrent transitions // ============================================================================ static inline bool ss_tier_check_transition(SuperSlab* ss) { if (!ss) return false; // Calculate current utilization float util = ss_tier_calc_utilization(ss); // Get current tier (atomic load) uint8_t current_tier = atomic_load_explicit(&ss->tier, memory_order_acquire); // Get thresholds (cached after first call) float down_thresh = ss_tier_get_down_threshold(); float up_thresh = ss_tier_get_up_threshold(); // Determine target tier based on utilization and current state uint8_t target_tier = current_tier; switch (current_tier) { case SS_TIER_HOT: // HOT → DRAINING: Drop below down threshold if (util <= down_thresh) { target_tier = SS_TIER_DRAINING; } // HOT → FREE: Complete deallocation (rare, usually via DRAINING) if (util == 0.0f) { target_tier = SS_TIER_FREE; } break; case SS_TIER_DRAINING: // DRAINING → HOT: Rise above up threshold (hysteresis) if (util >= up_thresh) { target_tier = SS_TIER_HOT; } // DRAINING → FREE: Complete deallocation if (util == 0.0f) { target_tier = SS_TIER_FREE; } break; case SS_TIER_FREE: // FREE → HOT: First allocation (util > 0) // Note: Typically handled by allocation path setting tier directly if (util > 0.0f) { target_tier = SS_TIER_HOT; } break; default: // Invalid tier, reset to HOT (defensive) target_tier = SS_TIER_HOT; break; } // If no transition needed, return early if (target_tier == current_tier) { return false; } // Attempt atomic transition (CAS loop for concurrent safety) // Note: We use weak CAS in a loop for efficiency on weak-memory architectures uint8_t expected = current_tier; while (!atomic_compare_exchange_weak_explicit( &ss->tier, &expected, target_tier, memory_order_release, // Success: publish tier change memory_order_relaxed // Failure: retry with updated expected )) { // Concurrent modification detected, re-evaluate // If tier already changed to target, we're done if (expected == target_tier) { return false; // Another thread completed the transition } // Otherwise, retry with new expected value } // Transition successful return true; } // ============================================================================ // 3. Tier State Query // ============================================================================ // // Returns the current tier of the SuperSlab. // // Returns: SS_TIER_HOT, SS_TIER_DRAINING, or SS_TIER_FREE // // Memory Order: Acquire ensures we see all updates made before tier was set // ============================================================================ static inline SSTier ss_tier_get(SuperSlab* ss) { if (!ss) return SS_TIER_FREE; // Defensive: NULL = not usable uint8_t tier = atomic_load_explicit(&ss->tier, memory_order_acquire); return (SSTier)tier; } // ============================================================================ // 4. Hot Tier Check (Allocation Eligibility) // ============================================================================ // // Fast path check: Can this SuperSlab accept new allocations? // // Returns: true if SuperSlab is in HOT tier (accepts allocations) // false otherwise (DRAINING or FREE, skip for allocation) // // Usage: Called by allocation path to filter candidate SuperSlabs // // Memory Order: Relaxed is sufficient as this is a filtering heuristic, // not a safety invariant. Worst case: we occasionally skip // a freshly-promoted HOT SuperSlab (benign race). // ============================================================================ static inline bool ss_tier_is_hot(SuperSlab* ss) { if (!ss) return false; uint8_t tier = atomic_load_explicit(&ss->tier, memory_order_relaxed); return (tier == SS_TIER_HOT); } // ============================================================================ // 5. Tier Force-Set (Testing/Debug Only) // ============================================================================ // // Directly sets the tier without utilization checks. // // WARNING: This bypasses all transition logic and should ONLY be used for: // - Unit tests // - Debug/instrumentation // - Initialization (setting fresh SuperSlab to HOT) // // Do NOT use in production hot paths. // // Memory Order: Release ensures any prior modifications are visible after // the tier change is observed by other threads. // ============================================================================ static inline void ss_tier_set(SuperSlab* ss, SSTier tier) { if (!ss) return; // Validate tier value (defensive) if (tier != SS_TIER_HOT && tier != SS_TIER_DRAINING && tier != SS_TIER_FREE) { return; // Invalid tier, refuse to set } atomic_store_explicit(&ss->tier, (uint8_t)tier, memory_order_release); } #endif // HAK_SS_TIER_BOX_H