Skip to content

Implement register coalescing to eliminate unnecessary moves #293

@jserv

Description

@jserv

Description

Register coalescing is an optimization that eliminates move instructions by assigning the source and destination of a move to the same register. This reduces instruction count and improves performance by eliminating redundant data movement.

Current Problem

// Current code generation creates many moves
int x = a + b;  // t1 = a + b; x = t1 (MOVE)
int y = x;      // y = x (MOVE)
int z = y * 2;  // t2 = y * 2; z = t2 (MOVE)
return z;       // return z (MOVE to return register)

// After coalescing: eliminate 3-4 moves

Coalescing Algorithm

1. Move Instruction Analysis

typedef struct {
    insn_t *insn;
    var_t *src;
    var_t *dst;
    bool can_coalesce;
    int benefit;  // Estimated benefit of coalescing
} move_info_t;

typedef struct {
    move_info_t **moves;
    int count;
    ig_graph_t *interference_graph;
} coalesce_context_t;

void collect_moves(func_t *func, coalesce_context_t *ctx) {
    for (bb in func->bbs) {
        for (insn in bb->insns) {
            if (is_move(insn)) {
                move_info_t *info = analyze_move(insn);
                add_move(ctx, info);
            }
        }
    }
}

bool is_move(insn_t *insn) {
    return insn->op == OP_MOV || 
           (insn->op == OP_ASSIGN && insn->rs2 == NULL);
}

2. Interference Check

bool can_coalesce_safely(var_t *src, var_t *dst, ig_graph_t *graph) {
    // Cannot coalesce if they interfere
    if (interferes(graph, src, dst)) {
        return false;
    }
    
    // Check George's criterion: conservative coalescing
    // For each neighbor of src, it must either:
    // - Already interfere with dst, or
    // - Have low degree (< K colors)
    for (neighbor in get_neighbors(graph, src)) {
        if (!interferes(graph, neighbor, dst)) {
            if (degree(graph, neighbor) >= NUM_REGISTERS) {
                return false;  // Would create high-degree node
            }
        }
    }
    
    return true;
}

bool interferes(ig_graph_t *graph, var_t *v1, var_t *v2) {
    // Check if live ranges overlap
    if (v1->live_end < v2->live_start || 
        v2->live_end < v1->live_start) {
        return false;  // Disjoint ranges
    }
    
    // Check interference graph
    return has_edge(graph, v1, v2);
}

3. Coalescing Strategy

typedef enum {
    COALESCE_AGGRESSIVE,  // Try all possible coalescing
    COALESCE_CONSERVATIVE, // Only guaranteed safe
    COALESCE_OPTIMISTIC   // Aggressive with backoff
} coalesce_strategy_t;

void coalesce_moves(coalesce_context_t *ctx, coalesce_strategy_t strategy) {
    // Sort moves by benefit
    sort_moves_by_benefit(ctx->moves);
    
    bool changed = true;
    while (changed) {
        changed = false;
        
        for (move in ctx->moves) {
            if (move->can_coalesce) {
                bool should_coalesce = false;
                
                switch (strategy) {
                case COALESCE_CONSERVATIVE:
                    should_coalesce = can_coalesce_safely(
                        move->src, move->dst, ctx->interference_graph);
                    break;
                    
                case COALESCE_AGGRESSIVE:
                    should_coalesce = !interferes(
                        ctx->interference_graph, move->src, move->dst);
                    break;
                    
                case COALESCE_OPTIMISTIC:
                    should_coalesce = evaluate_coalesce_benefit(move) > 0;
                    break;
                }
                
                if (should_coalesce) {
                    perform_coalescing(move, ctx);
                    changed = true;
                }
            }
        }
    }
}

4. Performing Coalescing

void perform_coalescing(move_info_t *move, coalesce_context_t *ctx) {
    var_t *src = move->src;
    var_t *dst = move->dst;
    
    // Merge variables
    var_t *merged = merge_variables(src, dst);
    
    // Update interference graph
    merge_nodes(ctx->interference_graph, src, dst, merged);
    
    // Update all uses and defs
    replace_all_occurrences(src, merged);
    replace_all_occurrences(dst, merged);
    
    // Remove move instruction
    remove_instruction(move->insn);
    
    // Update live ranges
    merged->live_start = min(src->live_start, dst->live_start);
    merged->live_end = max(src->live_end, dst->live_end);
}

void merge_nodes(ig_graph_t *graph, var_t *src, var_t *dst, var_t *merged) {
    // Union of neighbors
    for (neighbor in get_neighbors(graph, src)) {
        add_edge(graph, merged, neighbor);
    }
    for (neighbor in get_neighbors(graph, dst)) {
        add_edge(graph, merged, neighbor);
    }
    
    // Remove old nodes
    remove_node(graph, src);
    remove_node(graph, dst);
    
    // Add merged node
    add_node(graph, merged);
}

5. Benefit Analysis

int calculate_coalesce_benefit(move_info_t *move) {
    int benefit = 1;  // Base benefit of eliminating move
    
    // Higher benefit for moves in loops
    benefit *= pow(10, move->insn->loop_depth);
    
    // Higher benefit for frequently executed moves
    if (move->insn->bb->execution_count > 0) {
        benefit *= log(move->insn->bb->execution_count);
    }
    
    // Consider register pressure
    if (move->insn->bb->register_pressure > HIGH_PRESSURE_THRESHOLD) {
        benefit *= 2;  // More valuable when pressure is high
    }
    
    // Penalty for increasing interference
    int new_edges = count_new_interference_edges(move);
    benefit -= new_edges * INTERFERENCE_PENALTY;
    
    return benefit;
}

6. Special Cases

// Phi-node coalescing
void coalesce_phi_nodes(func_t *func) {
    for (bb in func->bbs) {
        for (phi in bb->phi_nodes) {
            // Try to assign phi result same register as operands
            for (operand in phi->operands) {
                if (can_coalesce_safely(phi->result, operand)) {
                    perform_coalescing(phi->result, operand);
                    break;
                }
            }
        }
    }
}

// Copy propagation integration
void coalesce_with_copy_propagation(func_t *func) {
    // First do copy propagation to expose more moves
    run_copy_propagation(func);
    
    // Then coalesce remaining moves
    coalesce_context_t ctx = build_context(func);
    coalesce_moves(&ctx, COALESCE_CONSERVATIVE);
}

// Two-address instruction handling (x86-style)
void handle_two_address_instructions(insn_t *insn) {
    // For instructions like: dst = dst op src
    // Try to coalesce dst with its previous value
    if (is_two_address(insn) && insn->rd == insn->rs1) {
        // Already coalesced
        return;
    }
    
    // Insert move and try to coalesce
    insert_move(insn->rd, insn->rs1);
    mark_for_coalescing(insn->rd, insn->rs1);
}

Test Cases

// Simple move chain
int test_move_chain() {
    int a = 5;
    int b = a;  // Should coalesce b with a
    int c = b;  // Should coalesce c with b/a
    return c;   // All use same register
}

// Loop with moves
int test_loop_moves(int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        int temp = i;      // Should coalesce
        int doubled = temp * 2;
        sum += doubled;
    }
    return sum;
}

// Phi coalescing
int test_phi_coalesce(int x, int y) {
    int result;
    if (x > y) {
        result = x;  // Phi operand 1
    } else {
        result = y;  // Phi operand 2
    }
    return result;   // Phi result - try to coalesce
}

// Cannot coalesce (interference)
int test_no_coalesce() {
    int a = 5;
    int b = 10;
    int c = a;   // Cannot coalesce c with a
    a = 20;      // a still live, interferes with c
    return a + c;
}

Integration with Register Allocator

void integrated_register_allocation(func_t *func) {
    // Phase 1: Build initial interference graph
    ig_graph_t *graph = build_interference_graph(func);
    
    // Phase 2: Coalesce moves
    coalesce_context_t ctx = {
        .interference_graph = graph,
        .moves = collect_moves(func)
    };
    coalesce_moves(&ctx, COALESCE_CONSERVATIVE);
    
    // Phase 3: Color the graph
    color_graph(graph);
    
    // Phase 4: Assign registers
    assign_registers(func, graph);
}

Expected Benefits

  • 10-30% reduction in move instructions
  • Better register utilization
  • Improved performance (fewer instructions)
  • Reduced code size

Success Metrics

  • Move instruction count reduction
  • No increase in spill code
  • Correct program semantics preserved
  • Performance improvement on benchmarks

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions