1 //! Stack slots.
2 //!
3 //! The `StackSlotData` struct keeps track of a single stack slot in a function.
4 //!
5
6 use crate::entity::{Iter, IterMut, Keys, PrimaryMap};
7 use crate::ir::{StackSlot, Type};
8 use crate::packed_option::PackedOption;
9 use alloc::vec::Vec;
10 use core::cmp;
11 use core::fmt;
12 use core::ops::{Index, IndexMut};
13 use core::slice;
14 use core::str::FromStr;
15
16 #[cfg(feature = "enable-serde")]
17 use serde::{Deserialize, Serialize};
18
19 /// The size of an object on the stack, or the size of a stack frame.
20 ///
21 /// We don't use `usize` to represent object sizes on the target platform because Cranelift supports
22 /// cross-compilation, and `usize` is a type that depends on the host platform, not the target
23 /// platform.
24 pub type StackSize = u32;
25
26 /// A stack offset.
27 ///
28 /// The location of a stack offset relative to a stack pointer or frame pointer.
29 pub type StackOffset = i32;
30
31 /// The minimum size of a spill slot in bytes.
32 ///
33 /// ISA implementations are allowed to assume that small types like `b1` and `i8` get a full 4-byte
34 /// spill slot.
35 const MIN_SPILL_SLOT_SIZE: StackSize = 4;
36
37 /// Get the spill slot size to use for `ty`.
spill_size(ty: Type) -> StackSize38 fn spill_size(ty: Type) -> StackSize {
39 cmp::max(MIN_SPILL_SLOT_SIZE, ty.bytes())
40 }
41
42 /// The kind of a stack slot.
43 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
44 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
45 pub enum StackSlotKind {
46 /// A spill slot. This is a stack slot created by the register allocator.
47 SpillSlot,
48
49 /// An explicit stack slot. This is a chunk of stack memory for use by the `stack_load`
50 /// and `stack_store` instructions.
51 ExplicitSlot,
52
53 /// An incoming function argument.
54 ///
55 /// If the current function has more arguments than fits in registers, the remaining arguments
56 /// are passed on the stack by the caller. These incoming arguments are represented as SSA
57 /// values assigned to incoming stack slots.
58 IncomingArg,
59
60 /// An outgoing function argument.
61 ///
62 /// When preparing to call a function whose arguments don't fit in registers, outgoing argument
63 /// stack slots are used to represent individual arguments in the outgoing call frame. These
64 /// stack slots are only valid while setting up a call.
65 OutgoingArg,
66
67 /// Space allocated in the caller's frame for the callee's return values
68 /// that are passed out via return pointer.
69 ///
70 /// If there are more return values than registers available for the callee's calling
71 /// convention, or the return value is larger than the available registers' space, then we
72 /// allocate stack space in this frame and pass a pointer to the callee, which then writes its
73 /// return values into this space.
74 StructReturnSlot,
75
76 /// An emergency spill slot.
77 ///
78 /// Emergency slots are allocated late when the register's constraint solver needs extra space
79 /// to shuffle registers around. They are only used briefly, and can be reused.
80 EmergencySlot,
81 }
82
83 impl FromStr for StackSlotKind {
84 type Err = ();
85
from_str(s: &str) -> Result<Self, ()>86 fn from_str(s: &str) -> Result<Self, ()> {
87 use self::StackSlotKind::*;
88 match s {
89 "explicit_slot" => Ok(ExplicitSlot),
90 "spill_slot" => Ok(SpillSlot),
91 "incoming_arg" => Ok(IncomingArg),
92 "outgoing_arg" => Ok(OutgoingArg),
93 "sret_slot" => Ok(StructReturnSlot),
94 "emergency_slot" => Ok(EmergencySlot),
95 _ => Err(()),
96 }
97 }
98 }
99
100 impl fmt::Display for StackSlotKind {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 use self::StackSlotKind::*;
103 f.write_str(match *self {
104 ExplicitSlot => "explicit_slot",
105 SpillSlot => "spill_slot",
106 IncomingArg => "incoming_arg",
107 OutgoingArg => "outgoing_arg",
108 StructReturnSlot => "sret_slot",
109 EmergencySlot => "emergency_slot",
110 })
111 }
112 }
113
114 /// Contents of a stack slot.
115 #[derive(Clone, Debug, PartialEq, Eq)]
116 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
117 pub struct StackSlotData {
118 /// The kind of stack slot.
119 pub kind: StackSlotKind,
120
121 /// Size of stack slot in bytes.
122 pub size: StackSize,
123
124 /// Offset of stack slot relative to the stack pointer in the caller.
125 ///
126 /// On x86, the base address is the stack pointer *before* the return address was pushed. On
127 /// RISC ISAs, the base address is the value of the stack pointer on entry to the function.
128 ///
129 /// For `OutgoingArg` stack slots, the offset is relative to the current function's stack
130 /// pointer immediately before the call.
131 pub offset: Option<StackOffset>,
132 }
133
134 impl StackSlotData {
135 /// Create a stack slot with the specified byte size.
new(kind: StackSlotKind, size: StackSize) -> Self136 pub fn new(kind: StackSlotKind, size: StackSize) -> Self {
137 Self {
138 kind,
139 size,
140 offset: None,
141 }
142 }
143
144 /// Get the alignment in bytes of this stack slot given the stack pointer alignment.
alignment(&self, max_align: StackSize) -> StackSize145 pub fn alignment(&self, max_align: StackSize) -> StackSize {
146 debug_assert!(max_align.is_power_of_two());
147 // We want to find the largest power of two that divides both `self.size` and `max_align`.
148 // That is the same as isolating the rightmost bit in `x`.
149 let x = self.size | max_align;
150 // C.f. Hacker's delight.
151 x & x.wrapping_neg()
152 }
153 }
154
155 impl fmt::Display for StackSlotData {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result156 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157 write!(f, "{} {}", self.kind, self.size)?;
158 if let Some(offset) = self.offset {
159 write!(f, ", offset {}", offset)?;
160 }
161 Ok(())
162 }
163 }
164
165 /// Stack frame layout information.
166 ///
167 /// This is computed by the `layout_stack()` method.
168 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
169 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
170 pub struct StackLayoutInfo {
171 /// The total size of the stack frame.
172 ///
173 /// This is the distance from the stack pointer in the current function to the stack pointer in
174 /// the calling function, so it includes a pushed return address as well as space for outgoing
175 /// call arguments.
176 pub frame_size: StackSize,
177
178 /// The total size of the stack frame for inbound arguments pushed by the caller.
179 pub inbound_args_size: StackSize,
180 }
181
182 /// Stack frame manager.
183 ///
184 /// Keep track of all the stack slots used by a function.
185 #[derive(Clone, Debug, PartialEq, Eq, Default)]
186 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
187 pub struct StackSlots {
188 /// All allocated stack slots.
189 slots: PrimaryMap<StackSlot, StackSlotData>,
190
191 /// All the outgoing stack slots, ordered by offset.
192 outgoing: Vec<StackSlot>,
193
194 /// All the emergency slots.
195 emergency: Vec<StackSlot>,
196
197 /// Layout information computed from `layout_stack`.
198 pub layout_info: Option<StackLayoutInfo>,
199 }
200
201 /// Stack slot manager functions that behave mostly like an entity map.
202 impl StackSlots {
203 /// Create an empty stack slot manager.
new() -> Self204 pub fn new() -> Self {
205 StackSlots::default()
206 }
207
208 /// Clear out everything.
clear(&mut self)209 pub fn clear(&mut self) {
210 self.slots.clear();
211 self.outgoing.clear();
212 self.emergency.clear();
213 self.layout_info = None;
214 }
215
216 /// Allocate a new stack slot.
217 ///
218 /// This function should be primarily used by the text format parser. There are more convenient
219 /// functions for creating specific kinds of stack slots below.
push(&mut self, data: StackSlotData) -> StackSlot220 pub fn push(&mut self, data: StackSlotData) -> StackSlot {
221 self.slots.push(data)
222 }
223
224 /// Check if `ss` is a valid stack slot reference.
is_valid(&self, ss: StackSlot) -> bool225 pub fn is_valid(&self, ss: StackSlot) -> bool {
226 self.slots.is_valid(ss)
227 }
228
229 /// Get an iterator over all the stack slot keys.
iter(&self) -> Iter<StackSlot, StackSlotData>230 pub fn iter(&self) -> Iter<StackSlot, StackSlotData> {
231 self.slots.iter()
232 }
233
234 /// Get an iterator over all the stack slot keys, mutable edition.
iter_mut(&mut self) -> IterMut<StackSlot, StackSlotData>235 pub fn iter_mut(&mut self) -> IterMut<StackSlot, StackSlotData> {
236 self.slots.iter_mut()
237 }
238
239 /// Get an iterator over all the stack slot records.
values(&self) -> slice::Iter<StackSlotData>240 pub fn values(&self) -> slice::Iter<StackSlotData> {
241 self.slots.values()
242 }
243
244 /// Get an iterator over all the stack slot records, mutable edition.
values_mut(&mut self) -> slice::IterMut<StackSlotData>245 pub fn values_mut(&mut self) -> slice::IterMut<StackSlotData> {
246 self.slots.values_mut()
247 }
248
249 /// Get an iterator over all the stack slot keys.
keys(&self) -> Keys<StackSlot>250 pub fn keys(&self) -> Keys<StackSlot> {
251 self.slots.keys()
252 }
253
254 /// Get a reference to the next stack slot that would be created by `push()`.
255 ///
256 /// This should just be used by the parser.
next_key(&self) -> StackSlot257 pub fn next_key(&self) -> StackSlot {
258 self.slots.next_key()
259 }
260 }
261
262 impl Index<StackSlot> for StackSlots {
263 type Output = StackSlotData;
264
index(&self, ss: StackSlot) -> &StackSlotData265 fn index(&self, ss: StackSlot) -> &StackSlotData {
266 &self.slots[ss]
267 }
268 }
269
270 impl IndexMut<StackSlot> for StackSlots {
index_mut(&mut self, ss: StackSlot) -> &mut StackSlotData271 fn index_mut(&mut self, ss: StackSlot) -> &mut StackSlotData {
272 &mut self.slots[ss]
273 }
274 }
275
276 /// Higher-level stack frame manipulation functions.
277 impl StackSlots {
278 /// Create a new spill slot for spilling values of type `ty`.
make_spill_slot(&mut self, ty: Type) -> StackSlot279 pub fn make_spill_slot(&mut self, ty: Type) -> StackSlot {
280 self.push(StackSlotData::new(StackSlotKind::SpillSlot, spill_size(ty)))
281 }
282
283 /// Create a stack slot representing an incoming function argument.
make_incoming_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot284 pub fn make_incoming_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot {
285 let mut data = StackSlotData::new(StackSlotKind::IncomingArg, size);
286 debug_assert!(offset <= StackOffset::max_value() - data.size as StackOffset);
287 data.offset = Some(offset);
288 self.push(data)
289 }
290
291 /// Get a stack slot representing an outgoing argument.
292 ///
293 /// This may create a new stack slot, or reuse an existing outgoing stack slot with the
294 /// requested offset and size.
295 ///
296 /// The requested offset is relative to this function's stack pointer immediately before making
297 /// the call.
get_outgoing_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot298 pub fn get_outgoing_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot {
299 // Look for an existing outgoing stack slot with the same offset and size.
300 let inspos = match self.outgoing.binary_search_by_key(&(offset, size), |&ss| {
301 (self[ss].offset.unwrap(), self[ss].size)
302 }) {
303 Ok(idx) => return self.outgoing[idx],
304 Err(idx) => idx,
305 };
306
307 // No existing slot found. Make one and insert it into `outgoing`.
308 let mut data = StackSlotData::new(StackSlotKind::OutgoingArg, size);
309 debug_assert!(offset <= StackOffset::max_value() - size as StackOffset);
310 data.offset = Some(offset);
311 let ss = self.slots.push(data);
312 self.outgoing.insert(inspos, ss);
313 ss
314 }
315
316 /// Get an emergency spill slot that can be used to store a `ty` value.
317 ///
318 /// This may allocate a new slot, or it may reuse an existing emergency spill slot, excluding
319 /// any slots in the `in_use` list.
get_emergency_slot( &mut self, ty: Type, in_use: &[PackedOption<StackSlot>], ) -> StackSlot320 pub fn get_emergency_slot(
321 &mut self,
322 ty: Type,
323 in_use: &[PackedOption<StackSlot>],
324 ) -> StackSlot {
325 let size = spill_size(ty);
326
327 // Find the smallest existing slot that can fit the type.
328 if let Some(&ss) = self
329 .emergency
330 .iter()
331 .filter(|&&ss| self[ss].size >= size && !in_use.contains(&ss.into()))
332 .min_by_key(|&&ss| self[ss].size)
333 {
334 return ss;
335 }
336
337 // Alternatively, use the largest available slot and make it larger.
338 if let Some(&ss) = self
339 .emergency
340 .iter()
341 .filter(|&&ss| !in_use.contains(&ss.into()))
342 .max_by_key(|&&ss| self[ss].size)
343 {
344 self.slots[ss].size = size;
345 return ss;
346 }
347
348 // No existing slot found. Make one and insert it into `emergency`.
349 let data = StackSlotData::new(StackSlotKind::EmergencySlot, size);
350 let ss = self.slots.push(data);
351 self.emergency.push(ss);
352 ss
353 }
354 }
355
356 #[cfg(test)]
357 mod tests {
358 use super::*;
359 use crate::ir::types;
360 use crate::ir::Function;
361 use alloc::string::ToString;
362
363 #[test]
stack_slot()364 fn stack_slot() {
365 let mut func = Function::new();
366
367 let ss0 = func.create_stack_slot(StackSlotData::new(StackSlotKind::IncomingArg, 4));
368 let ss1 = func.create_stack_slot(StackSlotData::new(StackSlotKind::SpillSlot, 8));
369 assert_eq!(ss0.to_string(), "ss0");
370 assert_eq!(ss1.to_string(), "ss1");
371
372 assert_eq!(func.stack_slots[ss0].size, 4);
373 assert_eq!(func.stack_slots[ss1].size, 8);
374
375 assert_eq!(func.stack_slots[ss0].to_string(), "incoming_arg 4");
376 assert_eq!(func.stack_slots[ss1].to_string(), "spill_slot 8");
377 }
378
379 #[test]
outgoing()380 fn outgoing() {
381 let mut sss = StackSlots::new();
382
383 let ss0 = sss.get_outgoing_arg(4, 8);
384 let ss1 = sss.get_outgoing_arg(4, 4);
385 let ss2 = sss.get_outgoing_arg(8, 8);
386
387 assert_eq!(sss[ss0].offset, Some(8));
388 assert_eq!(sss[ss0].size, 4);
389
390 assert_eq!(sss[ss1].offset, Some(4));
391 assert_eq!(sss[ss1].size, 4);
392
393 assert_eq!(sss[ss2].offset, Some(8));
394 assert_eq!(sss[ss2].size, 8);
395
396 assert_eq!(sss.get_outgoing_arg(4, 8), ss0);
397 assert_eq!(sss.get_outgoing_arg(4, 4), ss1);
398 assert_eq!(sss.get_outgoing_arg(8, 8), ss2);
399 }
400
401 #[test]
alignment()402 fn alignment() {
403 let slot = StackSlotData::new(StackSlotKind::SpillSlot, 8);
404
405 assert_eq!(slot.alignment(4), 4);
406 assert_eq!(slot.alignment(8), 8);
407 assert_eq!(slot.alignment(16), 8);
408
409 let slot2 = StackSlotData::new(StackSlotKind::ExplicitSlot, 24);
410
411 assert_eq!(slot2.alignment(4), 4);
412 assert_eq!(slot2.alignment(8), 8);
413 assert_eq!(slot2.alignment(16), 8);
414 assert_eq!(slot2.alignment(32), 8);
415 }
416
417 #[test]
emergency()418 fn emergency() {
419 let mut sss = StackSlots::new();
420
421 let ss0 = sss.get_emergency_slot(types::I32, &[]);
422 assert_eq!(sss[ss0].size, 4);
423
424 // When a smaller size is requested, we should simply get the same slot back.
425 assert_eq!(sss.get_emergency_slot(types::I8, &[]), ss0);
426 assert_eq!(sss[ss0].size, 4);
427 assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss0);
428 assert_eq!(sss[ss0].size, 4);
429
430 // Ask for a larger size and the slot should grow.
431 assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
432 assert_eq!(sss[ss0].size, 8);
433
434 // When one slot is in use, we should get a new one.
435 let ss1 = sss.get_emergency_slot(types::I32, &[None.into(), ss0.into()]);
436 assert_eq!(sss[ss0].size, 8);
437 assert_eq!(sss[ss1].size, 4);
438
439 // Now we should get the smallest fit of the two available slots.
440 assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss1);
441 assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
442 }
443 }
444