Skip to content

Improve spill cost calculation with execution frequency and loop depth #292

Description

@jserv

Description

The current spill cost calculation uses simple heuristics that don't account for execution frequency or loop nesting depth. This leads to poor spilling decisions where frequently-used variables in hot loops get spilled while rarely-used variables stay in registers.

Current Problem

// Current: all variables treated equally
void bad_spill_decision() {
    int rarely_used = get_value();  // Used once
    
    for (int i = 0; i < 1000000; i++) {
        int frequently_used = i * 2;  // Used million times
        // Current allocator might spill frequently_used!
        process(frequently_used);
    }
    
    return rarely_used;  // Keep in register whole time?
}

Improved Spill Cost Model

1. Execution Frequency Analysis

typedef struct {
    int static_count;     // Number of uses/defs in code
    int dynamic_count;    // Estimated execution count
    int loop_depth;       // Nesting level (0 = not in loop)
    bool in_hot_path;     // Profile-guided info
} use_info_t;

typedef struct {
    var_t *var;
    use_info_t *uses;     // Array of use sites
    int num_uses;
    use_info_t *defs;     // Array of definition sites
    int num_defs;
    float total_cost;     // Computed spill cost
} spill_info_t;

2. Loop Depth Tracking

void compute_loop_depths(func_t *func) {
    // Find natural loops
    loop_forest_t *loops = find_natural_loops(func);
    
    // Assign depth to each basic block
    for (loop in loops) {
        for (bb in loop->blocks) {
            bb->loop_depth = loop->nesting_level;
            
            // Mark loop headers for special treatment
            if (bb == loop->header) {
                bb->is_loop_header = true;
                bb->estimated_iterations = estimate_loop_iterations(loop);
            }
        }
    }
}

int estimate_loop_iterations(loop_t *loop) {
    // Simple heuristics
    if (has_constant_bound(loop)) {
        return get_constant_bound(loop);
    } else if (is_while_loop(loop)) {
        return 10;  // Conservative estimate
    } else {
        return 100; // Default for complex loops
    }
}

3. Spill Cost Calculation

float calculate_spill_cost_advanced(spill_info_t *info) {
    float cost = 0.0;
    
    // Cost of spilling definitions (stores)
    for (int i = 0; i < info->num_defs; i++) {
        use_info_t *def = &info->defs[i];
        float def_cost = STORE_COST;
        
        // Exponential weight for loop depth
        def_cost *= pow(LOOP_WEIGHT, def->loop_depth);
        
        // Estimated dynamic count
        def_cost *= estimate_execution_count(def);
        
        cost += def_cost;
    }
    
    // Cost of spilling uses (loads)
    for (int i = 0; i < info->num_uses; i++) {
        use_info_t *use = &info->uses[i];
        float use_cost = LOAD_COST;
        
        // Loop depth weight
        use_cost *= pow(LOOP_WEIGHT, use->loop_depth);
        
        // Execution count
        use_cost *= estimate_execution_count(use);
        
        // Special cases
        if (use->in_hot_path) {
            use_cost *= HOT_PATH_WEIGHT;
        }
        
        cost += use_cost;
    }
    
    // Normalize by live range length
    int range_length = info->var->live_end - info->var->live_start;
    cost /= max(1, range_length);
    
    // Special adjustments
    if (info->var->is_constant) {
        cost *= REMATERIALIZABLE_DISCOUNT;  // Can recreate instead of load
    }
    
    if (info->var->crosses_call) {
        cost *= CALL_CROSSING_DISCOUNT;  // Likely spilled anyway
    }
    
    return cost;
}

4. Rematerialization Detection

bool is_rematerializable(var_t *var) {
    if (var->is_constant) return true;
    
    // Check if variable comes from simple computation
    insn_t *def = get_single_definition(var);
    if (!def) return false;
    
    switch (def->op) {
    case OP_LOAD_CONST:
    case OP_GET_ADDR:
        return true;
        
    case OP_ADD:
    case OP_SUB:
        // Rematerializable if operands are available
        return operands_available_at_uses(def);
        
    default:
        return false;
    }
}

float get_rematerialization_cost(var_t *var) {
    if (var->is_constant) {
        return 1.0;  // Single instruction
    }
    
    insn_t *def = get_single_definition(var);
    return count_instructions(def);
}

5. Profile-Guided Spill Costs

typedef struct {
    char *function_name;
    int bb_id;
    long execution_count;
} profile_data_t;

void apply_profile_data(func_t *func, profile_data_t *profile) {
    for (entry in profile) {
        if (strcmp(entry->function_name, func->name) == 0) {
            basic_block_t *bb = find_bb_by_id(func, entry->bb_id);
            bb->execution_count = entry->execution_count;
            bb->is_hot = entry->execution_count > HOT_THRESHOLD;
        }
    }
}

float estimate_execution_count(use_info_t *use) {
    if (use->bb->execution_count > 0) {
        // Use profile data
        return use->bb->execution_count;
    } else {
        // Use static estimate
        return pow(10, use->loop_depth) * use->bb->static_frequency;
    }
}

6. Spill Priority Queue

typedef struct {
    var_t **vars;
    float *costs;
    int count;
} spill_queue_t;

void build_spill_priority_queue(func_t *func, spill_queue_t *queue) {
    for (var in all_variables) {
        spill_info_t *info = analyze_variable(var);
        float cost = calculate_spill_cost_advanced(info);
        
        // Insert into priority queue (min-heap)
        insert_with_priority(queue, var, cost);
    }
}

var_t *select_spill_candidate(spill_queue_t *queue) {
    // Return variable with minimum spill cost
    return extract_min(queue);
}

Configuration Constants

#define LOAD_COST 3.0          // Cycles for memory load
#define STORE_COST 2.0         // Cycles for memory store
#define LOOP_WEIGHT 10.0       // Multiplier per loop level
#define HOT_PATH_WEIGHT 5.0    // Extra weight for hot paths
#define REMATERIALIZABLE_DISCOUNT 0.1  // 90% discount
#define CALL_CROSSING_DISCOUNT 0.5     // 50% discount
#define HOT_THRESHOLD 1000     // Execution count for "hot"

Test Cases

// Loop depth consideration
void test_loop_spill() {
    int outer = 0;
    for (int i = 0; i < 100; i++) {
        int inner = 0;
        for (int j = 0; j < 100; j++) {
            inner += i * j;  // Should keep inner, i, j in registers
        }
        outer += inner;
    }
    return outer;  // outer less important than loop variables
}

// Rematerialization
void test_rematerialize() {
    const int constant = 42;
    int result = 0;
    for (int i = 0; i < 1000; i++) {
        // Should rematerialize constant rather than spill
        result += constant * i;
    }
    return result;
}

// Profile-guided
void test_profile_guided(int flag) {
    int cold_var = complex_computation();
    
    if (flag) {  // Assume rarely taken
        use(cold_var);
    }
    
    // Hot loop
    for (int i = 0; i < 1000000; i++) {
        int hot_var = i * 2;
        process(hot_var);  // Keep hot_var in register
    }
}

Expected Impact

  • 40-60% better spill decisions
  • Significantly reduced spilling in loops
  • Better performance for hot code paths
  • Smaller performance degradation under register pressure

Success Metrics

  • Loop variables rarely spilled
  • Measurable performance improvement in loops
  • Reduced total spill count
  • Better correlation with profiling data

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions