Skip to content

Commit d32c7dc

Browse files
committed
Improve regalloc neighbor spilling heuristic
Neighbors that have only a single instruction can now be spilled as well, as long as they won't penalize code elsewhere. This fixes the redundant connector ranges frequently found near the beginnings/ends of spills.
1 parent 5b80350 commit d32c7dc

14 files changed

+5699
-5676
lines changed

crates/codegen/src/regalloc/assign.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,12 @@ impl<M: MachineRegalloc> RegAllocContext<'_, M> {
280280
fn spill_fragment_and_neighbors(&mut self, fragment: LiveSetFragment) {
281281
self.spill_fragment(fragment);
282282

283-
// Spill any neighbors (in both directions) that don't have any attached instructions.
283+
// Spill any neighbors (in both directions) that don't have any attached instructions, or
284+
// have only attached instructions that are worth spilling anyway.
284285

285286
let mut cursor = fragment;
286287
while let Some(prev) = self.live_set_fragments[cursor].prev_split_neighbor.expand() {
287-
if self.fragment_has_instrs(prev) {
288+
if !self.should_spill_prev_neighbor(prev) {
288289
break;
289290
}
290291

@@ -298,7 +299,7 @@ impl<M: MachineRegalloc> RegAllocContext<'_, M> {
298299

299300
let mut cursor = fragment;
300301
while let Some(next) = self.live_set_fragments[cursor].next_split_neighbor.expand() {
301-
if self.fragment_has_instrs(next) {
302+
if !self.should_spill_next_neighbor(next) {
302303
break;
303304
}
304305

@@ -311,6 +312,74 @@ impl<M: MachineRegalloc> RegAllocContext<'_, M> {
311312
}
312313
}
313314

315+
fn should_spill_prev_neighbor(&self, prev_neighbor: LiveSetFragment) -> bool {
316+
if !self.fragment_has_instrs(prev_neighbor) {
317+
// Empty neighbors are always worth spilling.
318+
return true;
319+
}
320+
321+
if self.is_fragment_atomic(prev_neighbor) {
322+
// We're going to be touching fragments that have attached instructions now, so make
323+
// sure we never try to spill an atomic one by accident.
324+
return false;
325+
}
326+
327+
// Spill neighbors that contain only a single def instruction, as long as we won't need to
328+
// copy into yet another neighbor and that instruction won't be executed more frequently
329+
// than our current split point.
330+
331+
if self.live_set_fragments[prev_neighbor]
332+
.prev_split_neighbor
333+
.is_some()
334+
{
335+
return false;
336+
}
337+
338+
if let Some(instr) = self.fragment_only_instr(prev_neighbor) {
339+
if instr.is_def() {
340+
let end = self.fragment_hull(prev_neighbor).end.instr();
341+
return get_instr_weight(self.lir, self.cfg_ctx, instr.instr())
342+
<= get_instr_weight(self.lir, self.cfg_ctx, end);
343+
}
344+
}
345+
346+
false
347+
}
348+
349+
fn should_spill_next_neighbor(&self, next_neighbor: LiveSetFragment) -> bool {
350+
if !self.fragment_has_instrs(next_neighbor) {
351+
// Empty neighbors are always worth spilling.
352+
return true;
353+
}
354+
355+
if self.is_fragment_atomic(next_neighbor) {
356+
// We're going to be touching fragments that have attached instructions now, so make
357+
// sure we never try to spill an atomic one by accident.
358+
return false;
359+
}
360+
361+
// Spill neighbors that contain only a single use instruction, as long as we won't need to
362+
// copy into yet another neighbor and that instruction won't be executed more frequently
363+
// than our current split point.
364+
365+
if self.live_set_fragments[next_neighbor]
366+
.next_split_neighbor
367+
.is_some()
368+
{
369+
return false;
370+
}
371+
372+
if let Some(instr) = self.fragment_only_instr(next_neighbor) {
373+
if !instr.is_def() {
374+
let start = self.fragment_hull(next_neighbor).start.instr();
375+
return get_instr_weight(self.lir, self.cfg_ctx, instr.instr())
376+
<= get_instr_weight(self.lir, self.cfg_ctx, start);
377+
}
378+
}
379+
380+
false
381+
}
382+
314383
fn spill_fragment(&mut self, fragment: LiveSetFragment) {
315384
trace!(" spill: {fragment}");
316385

@@ -914,6 +983,15 @@ impl<M: MachineRegalloc> RegAllocContext<'_, M> {
914983
self.live_range_hints.insert(live_range, instr_hints.into());
915984
}
916985

986+
fn fragment_only_instr(&self, fragment: LiveSetFragment) -> Option<LiveRangeInstr> {
987+
let mut instrs = self.fragment_instrs(fragment);
988+
let instr = instrs.next()?;
989+
if instrs.next().is_some() {
990+
return None;
991+
}
992+
Some(instr)
993+
}
994+
917995
fn fragment_has_instrs(&self, fragment: LiveSetFragment) -> bool {
918996
self.fragment_instrs(fragment).next().is_some()
919997
}

crates/filetests/cases/codegen/tdn/array_jagged.spdr

Lines changed: 126 additions & 130 deletions
Large diffs are not rendered by default.

crates/filetests/cases/codegen/tdn/array_jagged_canon.spdr

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func @"System.Int32 Tests.CodeGenBringUpTests::ArrayJagged(System.Int32)":i32(i3
6161
# nextln: 000067: 44 89 39 mov dword ptr [rcx], r15d
6262
# nextln: 00006a: 8b 09 mov ecx, dword ptr [rcx]
6363
# nextln: 00006c: 4c 3b f1 cmp r14, rcx
64-
# nextln: 00006f: 0f 8d de 00 00 00 jge 0x153
64+
# nextln: 00006f: 0f 8d db 00 00 00 jge 0x150
6565
# nextln: 000075: 48 89 d9 mov rcx, rbx
6666
# nextln: 000078: 48 83 c1 10 add rcx, 0x10
6767
# nextln: 00007c: 48 89 c2 mov rdx, rax
@@ -70,7 +70,7 @@ func @"System.Int32 Tests.CodeGenBringUpTests::ArrayJagged(System.Int32)":i32(i3
7070
# nextln: 000088: 89 3a mov dword ptr [rdx], edi
7171
# nextln: 00008a: 8b 09 mov ecx, dword ptr [rcx]
7272
# nextln: 00008c: 48 85 c9 test rcx, rcx
73-
# nextln: 00008f: 0f 8e be 00 00 00 jle 0x153
73+
# nextln: 00008f: 0f 8e bb 00 00 00 jle 0x150
7474
# nextln: 000095: 48 83 c3 18 add rbx, 0x18
7575
# nextln: 000099: 48 89 03 mov qword ptr [rbx], rax
7676
# nextln: 00009c: 48 8b 1c 24 mov rbx, qword ptr [rsp]
@@ -82,14 +82,14 @@ func @"System.Int32 Tests.CodeGenBringUpTests::ArrayJagged(System.Int32)":i32(i3
8282
# nextln: 0000b2: 44 89 39 mov dword ptr [rcx], r15d
8383
# nextln: 0000b5: 8b 11 mov edx, dword ptr [rcx]
8484
# nextln: 0000b7: 48 85 d2 test rdx, rdx
85-
# nextln: 0000ba: 0f 8e 93 00 00 00 jle 0x153
85+
# nextln: 0000ba: 0f 8e 90 00 00 00 jle 0x150
8686
# nextln: 0000c0: 48 89 c2 mov rdx, rax
8787
# nextln: 0000c3: 48 83 c2 14 add rdx, 0x14
8888
# nextln: 0000c7: bf 02 00 00 00 mov edi, 2
8989
# nextln: 0000cc: 89 3a mov dword ptr [rdx], edi
9090
# nextln: 0000ce: 8b 09 mov ecx, dword ptr [rcx]
9191
# nextln: 0000d0: 4c 3b f1 cmp r14, rcx
92-
# nextln: 0000d3: 0f 8d 7a 00 00 00 jge 0x153
92+
# nextln: 0000d3: 0f 8d 77 00 00 00 jge 0x150
9393
# nextln: 0000d9: 48 89 d9 mov rcx, rbx
9494
# nextln: 0000dc: 48 83 c1 10 add rcx, 0x10
9595
# nextln: 0000e0: 48 89 c2 mov rdx, rax
@@ -98,38 +98,37 @@ func @"System.Int32 Tests.CodeGenBringUpTests::ArrayJagged(System.Int32)":i32(i3
9898
# nextln: 0000ec: 89 3a mov dword ptr [rdx], edi
9999
# nextln: 0000ee: 8b 09 mov ecx, dword ptr [rcx]
100100
# nextln: 0000f0: 4c 3b f1 cmp r14, rcx
101-
# nextln: 0000f3: 0f 8d 5a 00 00 00 jge 0x153
101+
# nextln: 0000f3: 0f 8d 57 00 00 00 jge 0x150
102102
# nextln: 0000f9: 48 83 c3 20 add rbx, 0x20
103103
# nextln: 0000fd: 48 89 03 mov qword ptr [rbx], rax
104104
# nextln: 000100: 48 8b 04 24 mov rax, qword ptr [rsp]
105105
# nextln: 000104: 48 89 c1 mov rcx, rax
106106
# nextln: 000107: 48 83 c1 10 add rcx, 0x10
107107
# nextln: 00010b: 8b 09 mov ecx, dword ptr [rcx]
108108
# nextln: 00010d: 4c 3b f1 cmp r14, rcx
109-
# nextln: 000110: 0f 8d 3d 00 00 00 jge 0x153
109+
# nextln: 000110: 0f 8d 3a 00 00 00 jge 0x150
110110
# nextln: 000116: 48 83 c0 20 add rax, 0x20
111111
# nextln: 00011a: 48 8b 00 mov rax, qword ptr [rax]
112112
# nextln: 00011d: 48 89 c1 mov rcx, rax
113113
# nextln: 000120: 48 83 c1 10 add rcx, 0x10
114114
# nextln: 000124: 8b 11 mov edx, dword ptr [rcx]
115-
# nextln: 000126: 48 8b 4c 24 08 mov rcx, qword ptr [rsp + 8]
116-
# nextln: 00012b: 48 63 c9 movsxd rcx, ecx
117-
# nextln: 00012e: 48 3b ca cmp rcx, rdx
118-
# nextln: 000131: 0f 8d 1c 00 00 00 jge 0x153
119-
# nextln: 000137: 48 c1 e1 02 shl rcx, 2
120-
# nextln: 00013b: 48 83 c1 14 add rcx, 0x14
121-
# nextln: 00013f: 48 03 c1 add rax, rcx
122-
# nextln: 000142: 8b 00 mov eax, dword ptr [rax]
123-
# nextln: 000144: 48 83 c4 18 add rsp, 0x18
124-
# nextln: 000148: 5b pop rbx
125-
# nextln: 000149: 41 5c pop r12
126-
# nextln: 00014b: 41 5d pop r13
127-
# nextln: 00014d: 41 5e pop r14
128-
# nextln: 00014f: 41 5f pop r15
129-
# nextln: 000151: 5d pop rbp
130-
# nextln: 000152: c3 ret
131-
# nextln: 000153: e8 00 00 00 00 call 0x158 # RELOC_PC32 -> @throw_index_out_of_range_exception + -4
132-
# nextln: 000158: 0f 0b ud2
115+
# nextln: 000126: 48 63 4c 24 08 movsxd rcx, dword ptr [rsp + 8]
116+
# nextln: 00012b: 48 3b ca cmp rcx, rdx
117+
# nextln: 00012e: 0f 8d 1c 00 00 00 jge 0x150
118+
# nextln: 000134: 48 c1 e1 02 shl rcx, 2
119+
# nextln: 000138: 48 83 c1 14 add rcx, 0x14
120+
# nextln: 00013c: 48 03 c1 add rax, rcx
121+
# nextln: 00013f: 8b 00 mov eax, dword ptr [rax]
122+
# nextln: 000141: 48 83 c4 18 add rsp, 0x18
123+
# nextln: 000145: 5b pop rbx
124+
# nextln: 000146: 41 5c pop r12
125+
# nextln: 000148: 41 5d pop r13
126+
# nextln: 00014a: 41 5e pop r14
127+
# nextln: 00014c: 41 5f pop r15
128+
# nextln: 00014e: 5d pop rbp
129+
# nextln: 00014f: c3 ret
130+
# nextln: 000150: e8 00 00 00 00 call 0x155 # RELOC_PC32 -> @throw_index_out_of_range_exception + -4
131+
# nextln: 000155: 0f 0b ud2
133132

134133
%0:ctrl, %1:i32 = entry
135134
%20:ptr = stackslot 8:8

0 commit comments

Comments
 (0)