Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 138e8b8

Browse files
author
Jakob Stoklund Olesen
committed
Add support for emergency spill slots.
- Create a new kind of stack slot: emergency_slot. - Add a get_emergency_slot() method which finds a suitable emergency slot given a list of slots already in use. - Use emergency spill slots when schedule_moves needs them.
1 parent 5e5d357 commit 138e8b8

File tree

7 files changed

+154
-10
lines changed

7 files changed

+154
-10
lines changed

filetests/parser/tiny.cton

+2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ function %stack() {
117117
ss2 = local 4
118118
ss3 = incoming_arg 4, offset 8
119119
ss4 = outgoing_arg 4
120+
ss5 = emergency_slot 4
120121

121122
ebb0:
122123
v1 = stack_load.i32 ss10
@@ -129,6 +130,7 @@ ebb0:
129130
; nextln: $ss2 = local 4
130131
; nextln: $ss3 = incoming_arg 4, offset 8
131132
; nextln: $ss4 = outgoing_arg 4
133+
; nextln: $ss5 = emergency_slot 4
132134

133135
; check: ebb0:
134136
; nextln: $v1 = stack_load.i32 $ss10
+24-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
test compile
2-
set is_64bit=1
32
isa intel haswell
43

54
function %pr165() native {
65
ebb0:
7-
v0 = iconst.i64 0x0102_0304_f1f2_f3f4
8-
v1 = iconst.i64 0x1102_0304_f1f2_f3f4
9-
v2 = iconst.i64 0x2102_0304_f1f2_f3f4
6+
v0 = iconst.i32 0x0102_0304
7+
v1 = iconst.i32 0x1102_0304
8+
v2 = iconst.i32 0x2102_0304
109
v20 = ishl v1, v0
1110
v21 = ishl v2, v0
1211
v22 = sshr v1, v0
@@ -17,3 +16,24 @@ ebb0:
1716
istore8 v1, v0+0x2710
1817
return
1918
}
19+
20+
; Same as above, but use so many registers that spilling is required.
21+
; Note: This is also a candidate for using xchg instructions.
22+
function %emergency_spill() native {
23+
ebb0:
24+
v0 = iconst.i32 0x0102_0304
25+
v1 = iconst.i32 0x1102_0304
26+
v2 = iconst.i32 0x2102_0304
27+
v3 = iconst.i32 0x3102_0304
28+
v4 = iconst.i32 0x4102_0304
29+
v20 = ishl v1, v0
30+
v21 = ishl v2, v3
31+
v22 = sshr v1, v0
32+
v23 = sshr v2, v0
33+
v24 = ushr v1, v0
34+
v25 = ushr v2, v0
35+
istore8 v0, v1+0x2710
36+
istore8 v1, v0+0x2710
37+
istore8 v3, v4+0x2710
38+
return
39+
}

lib/cretonne/src/ir/stackslot.rs

+78
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use entity::{PrimaryMap, Keys};
77
use ir::{Type, StackSlot};
8+
use packed_option::PackedOption;
89
use std::fmt;
910
use std::ops::Index;
1011
use std::str::FromStr;
@@ -44,6 +45,12 @@ pub enum StackSlotKind {
4445
/// stack slots are used to represent individual arguments in the outgoing call frame. These
4546
/// stack slots are only valid while setting up a call.
4647
OutgoingArg,
48+
49+
/// An emergency spill slot.
50+
///
51+
/// Emergency slots are allocated late when the register's constraint solver needs extra space
52+
/// to shuffle registers around. The are only used briefly, and can be reused.
53+
EmergencySlot,
4754
}
4855

4956
impl FromStr for StackSlotKind {
@@ -56,6 +63,7 @@ impl FromStr for StackSlotKind {
5663
"spill_slot" => Ok(SpillSlot),
5764
"incoming_arg" => Ok(IncomingArg),
5865
"outgoing_arg" => Ok(OutgoingArg),
66+
"emergency_slot" => Ok(EmergencySlot),
5967
_ => Err(()),
6068
}
6169
}
@@ -69,6 +77,7 @@ impl fmt::Display for StackSlotKind {
6977
SpillSlot => "spill_slot",
7078
IncomingArg => "incoming_arg",
7179
OutgoingArg => "outgoing_arg",
80+
EmergencySlot => "emergency_slot",
7281
})
7382
}
7483
}
@@ -135,6 +144,9 @@ pub struct StackSlots {
135144
/// All the outgoing stack slots, ordered by offset.
136145
outgoing: Vec<StackSlot>,
137146

147+
/// All the emergency slots.
148+
emergency: Vec<StackSlot>,
149+
138150
/// The total size of the stack frame.
139151
///
140152
/// This is the distance from the stack pointer in the current function to the stack pointer in
@@ -152,6 +164,7 @@ impl StackSlots {
152164
StackSlots {
153165
slots: PrimaryMap::new(),
154166
outgoing: Vec::new(),
167+
emergency: Vec::new(),
155168
frame_size: None,
156169
}
157170
}
@@ -160,6 +173,7 @@ impl StackSlots {
160173
pub fn clear(&mut self) {
161174
self.slots.clear();
162175
self.outgoing.clear();
176+
self.emergency.clear();
163177
self.frame_size = None;
164178
}
165179

@@ -243,6 +257,43 @@ impl StackSlots {
243257
self.outgoing.insert(inspos, ss);
244258
ss
245259
}
260+
261+
/// Get an emergency spill slot that can be used to store a `ty` value.
262+
///
263+
/// This may allocate a new slot, or it may reuse an existing emergency spill slot, excluding
264+
/// any slots in the `in_use` list.
265+
pub fn get_emergency_slot(
266+
&mut self,
267+
ty: Type,
268+
in_use: &[PackedOption<StackSlot>],
269+
) -> StackSlot {
270+
let size = ty.bytes();
271+
272+
// Find the smallest existing slot that can fit the type.
273+
if let Some(&ss) = self.emergency
274+
.iter()
275+
.filter(|&&ss| self[ss].size >= size && !in_use.contains(&ss.into()))
276+
.min_by_key(|&&ss| self[ss].size)
277+
{
278+
return ss;
279+
}
280+
281+
// Alternatively, use the largest available slot and make it larger.
282+
if let Some(&ss) = self.emergency
283+
.iter()
284+
.filter(|&&ss| !in_use.contains(&ss.into()))
285+
.max_by_key(|&&ss| self[ss].size)
286+
{
287+
self.slots[ss].size = size;
288+
return ss;
289+
}
290+
291+
// No existing slot found. Make one and insert it into `emergency`.
292+
let data = StackSlotData::new(StackSlotKind::EmergencySlot, size);
293+
let ss = self.slots.push(data);
294+
self.emergency.push(ss);
295+
ss
296+
}
246297
}
247298

248299
#[cfg(test)]
@@ -304,4 +355,31 @@ mod tests {
304355
assert_eq!(slot2.alignment(16), 8);
305356
assert_eq!(slot2.alignment(32), 8);
306357
}
358+
359+
#[test]
360+
fn emergency() {
361+
let mut sss = StackSlots::new();
362+
363+
let ss0 = sss.get_emergency_slot(types::I32, &[]);
364+
assert_eq!(sss[ss0].size, 4);
365+
366+
// When a smaller size is requested, we should simply get the same slot back.
367+
assert_eq!(sss.get_emergency_slot(types::I8, &[]), ss0);
368+
assert_eq!(sss[ss0].size, 4);
369+
assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss0);
370+
assert_eq!(sss[ss0].size, 4);
371+
372+
// Ask for a larger size and the slot should grow.
373+
assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
374+
assert_eq!(sss[ss0].size, 8);
375+
376+
// When one slot is in use, we should get a new one.
377+
let ss1 = sss.get_emergency_slot(types::I32, &[None.into(), ss0.into()]);
378+
assert_eq!(sss[ss0].size, 8);
379+
assert_eq!(sss[ss1].size, 4);
380+
381+
// Now we should get the smallest fit of the two available slots.
382+
assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss1);
383+
assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
384+
}
307385
}

lib/cretonne/src/regalloc/coloring.rs

+36-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use ir::{Ebb, Inst, Value, Function, ValueLoc, SigRef};
4848
use ir::{InstBuilder, ArgumentType, ArgumentLoc};
4949
use isa::{RegUnit, RegClass, RegInfo, regs_overlap};
5050
use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind};
51+
use packed_option::PackedOption;
5152
use regalloc::RegDiversions;
5253
use regalloc::affinity::Affinity;
5354
use regalloc::allocatable_set::AllocatableSet;
@@ -733,14 +734,47 @@ impl<'a> Context<'a> {
733734
fn shuffle_inputs(&mut self, regs: &mut AllocatableSet) {
734735
use regalloc::solver::Move::*;
735736

736-
self.solver.schedule_moves(regs);
737+
let spills = self.solver.schedule_moves(regs);
738+
739+
// The move operations returned by `schedule_moves` refer to emergency spill slots by
740+
// consecutive indexes starting from 0. Map these to real stack slots.
741+
// It is very unlikely (impossible?) that we would need more than one spill per top-level
742+
// register class, so avoid allocation by using a fixed array here.
743+
let mut slot = [PackedOption::default(); 8];
744+
assert!(spills <= slot.len(), "Too many spills ({})", spills);
745+
737746
for m in self.solver.moves() {
738747
match *m {
739748
Reg { value, from, to, .. } => {
740749
self.divert.regmove(value, from, to);
741750
self.cur.ins().regmove(value, from, to);
742751
}
743-
Spill { .. } | Fill { .. } => unimplemented!(),
752+
Spill {
753+
value,
754+
from,
755+
to_slot,
756+
..
757+
} => {
758+
debug_assert_eq!(slot[to_slot].expand(), None, "Overwriting slot in use");
759+
let ss = self.cur.func.stack_slots.get_emergency_slot(
760+
self.cur.func.dfg.value_type(value),
761+
&slot[0..spills],
762+
);
763+
slot[to_slot] = ss.into();
764+
self.divert.regspill(value, from, ss);
765+
self.cur.ins().regspill(value, from, ss);
766+
}
767+
Fill {
768+
value,
769+
from_slot,
770+
to,
771+
..
772+
} => {
773+
// These slots are single use, so mark `ss` as available again.
774+
let ss = slot[from_slot].take().expect("Using unallocated slot");
775+
self.divert.regfill(value, ss, to);
776+
self.cur.ins().regfill(value, ss, to);
777+
}
744778
}
745779
}
746780
}

lib/cretonne/src/regalloc/solver.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,13 @@ impl Solver {
523523
/// In either case, `to` will not be available for variables on the input side of the
524524
/// instruction.
525525
pub fn reassign_in(&mut self, value: Value, rc: RegClass, from: RegUnit, to: RegUnit) {
526-
dbg!("reassign_in({}:{}, %{} -> %{})", value, rc, from, to);
526+
dbg!(
527+
"reassign_in({}:{}, {} -> {})",
528+
value,
529+
rc,
530+
rc.info.display_regunit(from),
531+
rc.info.display_regunit(to)
532+
);
527533
debug_assert!(!self.inputs_done);
528534
if self.regs_in.is_avail(rc, from) {
529535
// It looks like `value` was already removed from the register set. It must have been
@@ -826,7 +832,9 @@ impl Solver {
826832
Move::with_assignment,
827833
));
828834

829-
dbg!("collect_moves: {}", DisplayList(&self.moves));
835+
if !(self.moves.is_empty()) {
836+
dbg!("collect_moves: {}", DisplayList(&self.moves));
837+
}
830838
}
831839

832840
/// Try to schedule a sequence of `regmove` instructions that will shuffle registers into

lib/cretonne/src/stack_layout.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ pub fn layout_stack(frame: &mut StackSlots, alignment: StackSize) -> Result<Stac
5656
)?;
5757
outgoing_max = max(outgoing_max, offset);
5858
}
59-
StackSlotKind::SpillSlot | StackSlotKind::Local => {
59+
StackSlotKind::SpillSlot |
60+
StackSlotKind::Local |
61+
StackSlotKind::EmergencySlot => {
6062
// Determine the smallest alignment of any local or spill slot.
6163
min_align = slot.alignment(min_align);
6264
}

misc/vim/syntax/cton.vim

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ endif
1414
syn spell notoplevel
1515

1616
syn keyword ctonHeader test isa set
17-
syn keyword ctonDecl function jump_table incoming_arg outgoing_arg spill_slot local
17+
syn keyword ctonDecl function jump_table incoming_arg outgoing_arg spill_slot local emergency_slot
1818
syn keyword ctonFilecheck check sameln nextln unordered not regex contained
1919

2020
syn match ctonType /\<[bif]\d\+\(x\d\+\)\?\>/

0 commit comments

Comments
 (0)