compiler: Check register assignment pipeline and block scans

91d5c00e4e1e17dac2b2bd77d910ddfa9fe7460fc38c284d9f2713cce66805f2
Alexis Sellier committed ago 1 parent 39698da5
lib/std/lang/gen/regalloc/assign.rad +77 -47
33 33
    assignments: &'scratch [?gen::Reg],
34 34
    /// Bitmask of used callee-saved registers.
35 35
    usedCalleeSaved: u32,
36 36
}
37 37
38 -
/// Per-instruction context for freeing and allocating register uses.
38 +
/// Register state for scanning one block's instructions.
39 39
record InstrCtx: 'scratch + 'step where 'scratch: 'step {
40 40
    current: &'step mut RegMap 'scratch,
41 41
    usedRegs: &'step mut [u32],
42 42
    /// Last operand-use index for each register used in the current block.
43 -
    lastUse: &'step [u32],
43 +
    lastUse: &'step mut [u32],
44 44
    live: &'step liveness::LiveInfo 'scratch,
45 45
    blockIdx: u32,
46 46
    instrIdx: u32,
47 47
    allocatable: *[gen::Reg],
48 48
    calleeSaved: *[gen::Reg],
55 55
    func: &il::Fn,
56 56
    live: &liveness::LiveInfo 'scratch,
57 57
    spillInfo: &spill::SpillInfo 'scratch,
58 58
    config: &super::TargetConfig,
59 59
    storage: &Session 'scratch
60 +
) -> AssignInfo 'scratch throws (alloc::AllocError) {
61 +
    return try assignTables(func.params, func.blocks, live, spillInfo, config, storage);
62 +
}
63 +
64 +
/// Allocate register mappings for borrowed function parameter and block tables.
65 +
fn assignTables 'scratch (
66 +
    params: &[il::Param],
67 +
    blocks: &[il::Block],
68 +
    live: &liveness::LiveInfo 'scratch,
69 +
    spillInfo: &spill::SpillInfo 'scratch,
70 +
    config: &super::TargetConfig,
71 +
    storage: &Session 'scratch,
60 72
) -> AssignInfo 'scratch throws (alloc::AllocError) {
61 73
    let maxReg = live.maxReg;
62 -
    let blockCount = func.blocks.len;
74 +
    let blockCount = blocks.len;
63 75
    let allocatable = config.allocatable;
64 76
65 77
    if maxReg == 0 or blockCount == 0 {
66 78
        return AssignInfo 'scratch {
67 79
            assignments: try storage.fill(nil as ?gen::Reg, 0),
73 85
    let assignments = try storage.fill(nil as ?gen::Reg, maxReg);
74 86
    // Pre-assign function parameters to argument registers.
75 87
    // Cross-call params are NOT pre-assigned here; they will be allocated
76 88
    // to callee-saved registers by the normal path, and isel emits moves
77 89
    // from the arg register to the assigned register at function entry.
78 -
    for param, i in func.params {
90 +
    for param, i in params {
79 91
        if i < config.argRegs.len {
80 92
            if not bitset::contains(spillInfo.calleeClass, param.value.n) {
81 93
                set assignments[param.value.n] = config.argRegs[i];
82 94
            }
83 95
        }
90 102
    let lastUse = try storage.fill(0 as u32, maxReg);
91 103
    let mut current = try createRegMap(storage);
92 104
93 105
    // Phase 2: Linear scan allocation.
94 106
    for b in 0..blockCount {
95 -
        let block = &func.blocks[b];
96 -
97 -
        // Record every operand before allocation. Only current-block operands
98 -
        // query this table, so every read is initialized by this scan.
99 -
        // Entries for other registers need not be cleared between blocks.
100 -
        for instr, i in block.instrs {
101 -
            let mut registers = il::registers(&instr);
102 -
            while let reg = il::nextReg(&mut registers, &instr) {
103 -
                recordLastUse(reg, &mut lastUse[..], i);
104 -
            }
105 -
        }
106 -
107 -
        enterBlock(&mut current, usedRegs, liveness::liveInRow(live, b), block.params, assignments, config, spillInfo);
108 -
109 -
        // Process each instruction.
110 -
        for instr, i in block.instrs {
111 -
            let currentRef: 'step = &mut current, usedRef = &mut usedRegs[..],
112 -
                lastUseRef = &lastUse[..], liveRef = &*live,
113 -
                assignmentsRef = &mut assignments[..], spillRef = &*spillInfo
114 -
            where 'scratch: 'step in {
115 -
                let mut ctx = InstrCtx 'scratch 'step {
116 -
                    current: currentRef,
117 -
                    usedRegs: usedRef,
118 -
                    lastUse: lastUseRef,
119 -
                    live: liveRef,
120 -
                    blockIdx: b,
121 -
                    instrIdx: i,
122 -
                    allocatable,
123 -
                    calleeSaved: config.calleeSaved,
124 -
                    assignments: assignmentsRef,
125 -
                    spillInfo: spillRef,
126 -
                };
127 -
                let mut registers = il::registers(&instr);
128 -
                while let reg = il::nextReg(&mut registers, &instr) {
129 -
                    processInstrReg(reg, &mut ctx);
130 -
                }
131 -
            }
132 -
133 -
            // Allocate destination.
134 -
            if let dst = il::instrDst(instr) {
135 -
                if dst.n < maxReg and not spill::isSpilled(spillInfo, dst) {
136 -
                    set assignments[dst.n] = rallocReg(&mut current, usedRegs, dst.n, allocatable, config.calleeSaved, spillInfo);
137 -
                }
107 +
        let block = &blocks[b];
108 +
        let currentRef: 'step = &mut current, usedRef = &mut usedRegs[..],
109 +
            lastUseRef = &mut lastUse[..], liveRef = &*live,
110 +
            assignmentsRef = &mut assignments[..], spillRef = &*spillInfo
111 +
        where 'scratch: 'step in {
112 +
            let mut ctx = InstrCtx 'scratch 'step {
113 +
                current: currentRef,
114 +
                usedRegs: usedRef,
115 +
                lastUse: lastUseRef,
116 +
                live: liveRef,
117 +
                blockIdx: b,
118 +
                instrIdx: 0,
119 +
                allocatable,
120 +
                calleeSaved: config.calleeSaved,
121 +
                assignments: assignmentsRef,
122 +
                spillInfo: spillRef,
123 +
            };
124 +
            unsafe {
125 +
                assignBlock(block.params, block.instrs, &mut ctx, config);
138 126
            }
139 127
        }
140 128
    }
141 129
    // Compute bitmask of used callee-saved registers.
142 130
    let mut usedCalleeSaved: u32 = 0;
154 142
        assignments: &assignments[..],
155 143
        usedCalleeSaved,
156 144
    };
157 145
}
158 146
147 +
/// Assign one block's source and destination registers in instruction order.
148 +
fn assignBlock 'scratch 'step (
149 +
    params: &[il::Param],
150 +
    instructions: &[il::Instr],
151 +
    ctx: &mut InstrCtx 'scratch 'step,
152 +
    config: &super::TargetConfig,
153 +
) where 'scratch: 'step {
154 +
    // Record every operand before allocation. Only current-block operands
155 +
    // query this table, so every read is initialized by this scan.
156 +
    // Entries for other registers need not be cleared between blocks.
157 +
    for i in 0..instructions.len {
158 +
        let instr = &instructions[i];
159 +
        let mut registers = il::registers(instr);
160 +
        unsafe {
161 +
            while let reg = il::nextReg(&mut registers, instr) {
162 +
                recordLastUse(reg, ctx.lastUse, i);
163 +
            }
164 +
        }
165 +
    }
166 +
167 +
    enterBlock(ctx.current, ctx.usedRegs, liveness::liveInRow(ctx.live, ctx.blockIdx), params, ctx.assignments, config, ctx.spillInfo);
168 +
169 +
    // Process each instruction.
170 +
    for i in 0..instructions.len {
171 +
        let instr = &instructions[i];
172 +
        set ctx.instrIdx = i;
173 +
        let mut registers = il::registers(instr);
174 +
        unsafe {
175 +
            while let reg = il::nextReg(&mut registers, instr) {
176 +
                processInstrReg(reg, ctx);
177 +
            }
178 +
        }
179 +
180 +
        // Allocate destination.
181 +
        if let dst = il::instrDst(*instr) {
182 +
            if dst.n < ctx.assignments.len and not spill::isSpilled(ctx.spillInfo, dst) {
183 +
                set ctx.assignments[dst.n] = rallocReg(ctx.current, ctx.usedRegs, dst.n, ctx.allocatable, ctx.calleeSaved, ctx.spillInfo);
184 +
            }
185 +
        }
186 +
    }
187 +
}
188 +
159 189
/// Reserve live-in registers before assigning the block's parameter registers.
160 190
fn enterBlock 'scratch (
161 191
    current: &mut RegMap 'scratch,
162 192
    usedRegs: &mut [u32],
163 193
    liveIn: &[u32],
lib/std/lang/gen/regalloc/liveness/tests.rad +6 -0
3 3
use std::testing;
4 4
use std::lang::alloc;
5 5
use std::lang::il;
6 6
use std::lang::gen::bitset;
7 7
use std::lang::gen::regalloc::spill;
8 +
use std::lang::gen::regalloc::assign;
9 +
use std::lang::gen::regalloc;
8 10
9 11
10 12
/// Construct a block whose instruction storage belongs to the caller.
11 13
unsafe fn block(name: *[u8], instructions: *unsafe mut [il::Instr]) -> il::Block {
12 14
    return il::Block { label: name, params: &[], instrs: instructions,
31 33
            assert live.defs.len == 0 and live.uses.len == 0;
32 34
            let spills = try! spill::analyze(&function, &live, 0, 0, 8, &analysis);
33 35
            assert spills.maxReg == 0;
34 36
            assert spills.frameSize == 0;
35 37
            assert spills.slots.len == 0 and spills.calleeClass.len == 0;
38 +
            let config = regalloc::TargetConfig { allocatable: &[], argRegs: &[], calleeSaved: &[], slotSize: 8 };
39 +
            let assigned = try! assign::assign(&function, &live, &spills, &config, &analysis);
40 +
            assert assigned.assignments.len == 0;
41 +
            assert assigned.usedCalleeSaved == 0;
36 42
        }
37 43
    }
38 44
}
39 45
40 46
/// Insufficient matrix storage reports an allocation error.