xref: /wasmtime-44.0.1/winch/codegen/src/stack.rs (revision 18aff9aa)
1 use crate::{codegen::CodeGenError, isa::reg::Reg, masm::StackSlot};
2 use anyhow::{Result, anyhow};
3 use smallvec::SmallVec;
4 use wasmparser::{Ieee32, Ieee64};
5 use wasmtime_environ::WasmValType;
6 
7 /// A typed register value used to track register values in the value
8 /// stack.
9 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
10 pub struct TypedReg {
11     /// The physical register.
12     pub reg: Reg,
13     /// The type associated to the physical register.
14     pub ty: WasmValType,
15 }
16 
17 impl TypedReg {
18     /// Create a new [`TypedReg`].
19     pub fn new(ty: WasmValType, reg: Reg) -> Self {
20         Self { ty, reg }
21     }
22 
23     /// Create an i64 [`TypedReg`].
24     pub fn i64(reg: Reg) -> Self {
25         Self {
26             ty: WasmValType::I64,
27             reg,
28         }
29     }
30 
31     /// Create an i32 [`TypedReg`].
32     pub fn i32(reg: Reg) -> Self {
33         Self {
34             ty: WasmValType::I32,
35             reg,
36         }
37     }
38 
39     /// Create an f64 [`TypedReg`].
40     pub fn f64(reg: Reg) -> Self {
41         Self {
42             ty: WasmValType::F64,
43             reg,
44         }
45     }
46 
47     /// Create an f32 [`TypedReg`].
48     pub fn f32(reg: Reg) -> Self {
49         Self {
50             ty: WasmValType::F32,
51             reg,
52         }
53     }
54 
55     /// Create a v128 [`TypedReg`].
56     pub fn v128(reg: Reg) -> Self {
57         Self {
58             ty: WasmValType::V128,
59             reg,
60         }
61     }
62 }
63 
64 impl From<TypedReg> for Reg {
65     fn from(tr: TypedReg) -> Self {
66         tr.reg
67     }
68 }
69 
70 /// A local value.
71 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
72 pub struct Local {
73     /// The index of the local.
74     pub index: u32,
75     /// The type of the local.
76     pub ty: WasmValType,
77 }
78 
79 /// A memory value.
80 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
81 pub struct Memory {
82     /// The type associated with the memory offset.
83     pub ty: WasmValType,
84     /// The stack slot corresponding to the memory value.
85     pub slot: StackSlot,
86 }
87 
88 /// Value definition to be used within the shadow stack.
89 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
90 pub(crate) enum Val {
91     /// I32 Constant.
92     I32(i32),
93     /// I64 Constant.
94     I64(i64),
95     /// F32 Constant.
96     F32(Ieee32),
97     /// F64 Constant.
98     F64(Ieee64),
99     /// V128 Constant.
100     V128(i128),
101     /// A register value.
102     Reg(TypedReg),
103     /// A local slot.
104     Local(Local),
105     /// Offset to a memory location.
106     Memory(Memory),
107 }
108 
109 impl From<TypedReg> for Val {
110     fn from(tr: TypedReg) -> Self {
111         Val::Reg(tr)
112     }
113 }
114 
115 impl From<Local> for Val {
116     fn from(local: Local) -> Self {
117         Val::Local(local)
118     }
119 }
120 
121 impl From<Memory> for Val {
122     fn from(mem: Memory) -> Self {
123         Val::Memory(mem)
124     }
125 }
126 
127 impl TryFrom<u32> for Val {
128     type Error = anyhow::Error;
129     fn try_from(value: u32) -> Result<Self, Self::Error> {
130         i32::try_from(value).map(Val::i32).map_err(Into::into)
131     }
132 }
133 
134 impl Val {
135     /// Create a new I32 constant value.
136     pub fn i32(v: i32) -> Self {
137         Self::I32(v)
138     }
139 
140     /// Create a new I64 constant value.
141     pub fn i64(v: i64) -> Self {
142         Self::I64(v)
143     }
144 
145     /// Create a new F32 constant value.
146     pub fn f32(v: Ieee32) -> Self {
147         Self::F32(v)
148     }
149 
150     pub fn f64(v: Ieee64) -> Self {
151         Self::F64(v)
152     }
153 
154     /// Create a new V128 constant value.
155     pub fn v128(v: i128) -> Self {
156         Self::V128(v)
157     }
158 
159     /// Create a new Reg value.
160     pub fn reg(reg: Reg, ty: WasmValType) -> Self {
161         Self::Reg(TypedReg { reg, ty })
162     }
163 
164     /// Create a new Local value.
165     pub fn local(index: u32, ty: WasmValType) -> Self {
166         Self::Local(Local { index, ty })
167     }
168 
169     /// Create a Memory value.
170     pub fn mem(ty: WasmValType, slot: StackSlot) -> Self {
171         Self::Memory(Memory { ty, slot })
172     }
173 
174     /// Check whether the value is a register.
175     pub fn is_reg(&self) -> bool {
176         match *self {
177             Self::Reg(_) => true,
178             _ => false,
179         }
180     }
181 
182     /// Check whether the value is a memory offset.
183     pub fn is_mem(&self) -> bool {
184         match *self {
185             Self::Memory(_) => true,
186             _ => false,
187         }
188     }
189 
190     /// Check whether the value is a constant.
191     pub fn is_const(&self) -> bool {
192         match *self {
193             Val::I32(_) | Val::I64(_) | Val::F32(_) | Val::F64(_) | Val::V128(_) => true,
194             _ => false,
195         }
196     }
197 
198     /// Check whether the value is local with a particular index.
199     pub fn is_local_at_index(&self, index: u32) -> bool {
200         match *self {
201             Self::Local(Local { index: i, .. }) if i == index => true,
202             _ => false,
203         }
204     }
205 
206     /// Get the register representation of the value.
207     ///
208     /// # Panics
209     /// This method will panic if the value is not a register.
210     pub fn unwrap_reg(&self) -> TypedReg {
211         match self {
212             Self::Reg(tr) => *tr,
213             v => panic!("expected value {v:?} to be a register"),
214         }
215     }
216 
217     /// Get the integer representation of the value.
218     ///
219     /// # Panics
220     /// This method will panic if the value is not an i32.
221     pub fn unwrap_i32(&self) -> i32 {
222         match self {
223             Self::I32(v) => *v,
224             v => panic!("expected value {v:?} to be i32"),
225         }
226     }
227 
228     /// Get the integer representation of the value.
229     ///
230     /// # Panics
231     /// This method will panic if the value is not an i64.
232     pub fn unwrap_i64(&self) -> i64 {
233         match self {
234             Self::I64(v) => *v,
235             v => panic!("expected value {v:?} to be i64"),
236         }
237     }
238 
239     /// Get the float representation of the value.
240     ///
241     /// # Panics
242     /// This method will panic if the value is not an f32.
243     pub fn unwrap_f32(&self) -> Ieee32 {
244         match self {
245             Self::F32(v) => *v,
246             v => panic!("expected value {v:?} to be f32"),
247         }
248     }
249 
250     /// Get the float representation of the value.
251     ///
252     /// # Panics
253     /// This method will panic if the value is not an f64.
254     pub fn unwrap_f64(&self) -> Ieee64 {
255         match self {
256             Self::F64(v) => *v,
257             v => panic!("expected value {v:?} to be f64"),
258         }
259     }
260 
261     /// Returns the underlying memory value if it is one, panics otherwise.
262     pub fn unwrap_mem(&self) -> Memory {
263         match self {
264             Self::Memory(m) => *m,
265             v => panic!("expected value {v:?} to be a Memory"),
266         }
267     }
268 
269     /// Check whether the value is an i32 constant.
270     pub fn is_i32_const(&self) -> bool {
271         match *self {
272             Self::I32(_) => true,
273             _ => false,
274         }
275     }
276 
277     /// Check whether the value is an i64 constant.
278     pub fn is_i64_const(&self) -> bool {
279         match *self {
280             Self::I64(_) => true,
281             _ => false,
282         }
283     }
284 
285     /// Check whether the value is an f32 constant.
286     pub fn is_f32_const(&self) -> bool {
287         match *self {
288             Self::F32(_) => true,
289             _ => false,
290         }
291     }
292 
293     /// Check whether the value is an f64 constant.
294     pub fn is_f64_const(&self) -> bool {
295         match *self {
296             Self::F64(_) => true,
297             _ => false,
298         }
299     }
300 
301     /// Get the type of the value.
302     pub fn ty(&self) -> WasmValType {
303         match self {
304             Val::I32(_) => WasmValType::I32,
305             Val::I64(_) => WasmValType::I64,
306             Val::F32(_) => WasmValType::F32,
307             Val::F64(_) => WasmValType::F64,
308             Val::V128(_) => WasmValType::V128,
309             Val::Reg(r) => r.ty,
310             Val::Memory(m) => m.ty,
311             Val::Local(l) => l.ty,
312         }
313     }
314 }
315 
316 /// The shadow stack used for compilation.
317 #[derive(Default, Debug)]
318 pub(crate) struct Stack {
319     // NB: The 64 is chosen arbitrarily. We can adjust as we see fit.
320     inner: SmallVec<[Val; 64]>,
321 }
322 
323 impl Stack {
324     /// Allocate a new stack.
325     pub fn new() -> Self {
326         Self {
327             inner: Default::default(),
328         }
329     }
330 
331     /// Ensures that there are at least `n` elements in the value stack,
332     /// and returns the index calculated by: stack length minus `n`.
333     pub fn ensure_index_at(&self, n: usize) -> Result<usize> {
334         if self.len() >= n {
335             Ok(self.len() - n)
336         } else {
337             Err(anyhow!(CodeGenError::missing_values_in_stack()))
338         }
339     }
340 
341     /// Returns true if the stack contains a local with the provided index
342     /// except if the only time the local appears is the top element.
343     pub fn contains_latent_local(&self, index: u32) -> bool {
344         self.inner
345             .iter()
346             // Iterate top-to-bottom so we can skip the top element and stop
347             // when we see a memory element.
348             .rev()
349             // The local is not latent if it's the top element because the top
350             // element will be popped next which materializes the local.
351             .skip(1)
352             // Stop when we see a memory element because that marks where we
353             // spilled up to so there will not be any locals past this point.
354             .take_while(|v| !v.is_mem())
355             .any(|v| v.is_local_at_index(index))
356     }
357 
358     /// Extend the stack with the given elements.
359     pub fn extend(&mut self, values: impl IntoIterator<Item = Val>) {
360         self.inner.extend(values);
361     }
362 
363     /// Inserts many values at the given index.
364     pub fn insert_many(&mut self, at: usize, values: &[Val]) {
365         debug_assert!(at <= self.len());
366 
367         if at == self.len() {
368             self.inner.extend_from_slice(values);
369         } else {
370             self.inner.insert_from_slice(at, values);
371         }
372     }
373 
374     /// Get the length of the stack.
375     pub fn len(&self) -> usize {
376         self.inner.len()
377     }
378 
379     /// Push a value to the stack.
380     pub fn push(&mut self, val: Val) {
381         self.inner.push(val);
382     }
383 
384     /// Peek into the top in the stack.
385     pub fn peek(&self) -> Option<&Val> {
386         self.inner.last()
387     }
388 
389     /// Returns an iterator referencing the last n items of the stack,
390     /// in bottom-most to top-most order.
391     pub fn peekn(&self, n: usize) -> impl Iterator<Item = &Val> + '_ {
392         let len = self.len();
393         assert!(n <= len);
394 
395         let partition = len - n;
396         self.inner[partition..].into_iter()
397     }
398 
399     /// Pops the top element of the stack, if any.
400     pub fn pop(&mut self) -> Option<Val> {
401         self.inner.pop()
402     }
403 
404     /// Pops the element at the top of the stack if it is an i32 const;
405     /// returns `None` otherwise.
406     pub fn pop_i32_const(&mut self) -> Option<i32> {
407         match self.peek() {
408             Some(v) => v.is_i32_const().then(|| self.pop().unwrap().unwrap_i32()),
409             _ => None,
410         }
411     }
412 
413     /// Pops the element at the top of the stack if it is an i64 const;
414     /// returns `None` otherwise.
415     pub fn pop_i64_const(&mut self) -> Option<i64> {
416         match self.peek() {
417             Some(v) => v.is_i64_const().then(|| self.pop().unwrap().unwrap_i64()),
418             _ => None,
419         }
420     }
421 
422     /// Pops the element at the top of the stack if it is an f32 const;
423     /// returns `None` otherwise.
424     pub fn pop_f32_const(&mut self) -> Option<Ieee32> {
425         match self.peek() {
426             Some(v) => v.is_f32_const().then(|| self.pop().unwrap().unwrap_f32()),
427             _ => None,
428         }
429     }
430 
431     /// Pops the element at the top of the stack if it is an f64 const;
432     /// returns `None` otherwise.
433     pub fn pop_f64_const(&mut self) -> Option<Ieee64> {
434         match self.peek() {
435             Some(v) => v.is_f64_const().then(|| self.pop().unwrap().unwrap_f64()),
436             _ => None,
437         }
438     }
439 
440     /// Pops the element at the top of the stack if it is a register;
441     /// returns `None` otherwise.
442     pub fn pop_reg(&mut self) -> Option<TypedReg> {
443         match self.peek() {
444             Some(v) => v.is_reg().then(|| self.pop().unwrap().unwrap_reg()),
445             _ => None,
446         }
447     }
448 
449     /// Pops the given register if it is at the top of the stack;
450     /// returns `None` otherwise.
451     pub fn pop_named_reg(&mut self, reg: Reg) -> Option<TypedReg> {
452         match self.peek() {
453             Some(v) => {
454                 (v.is_reg() && v.unwrap_reg().reg == reg).then(|| self.pop().unwrap().unwrap_reg())
455             }
456             _ => None,
457         }
458     }
459 
460     /// Get a mutable reference to the inner stack representation.
461     pub fn inner_mut(&mut self) -> &mut SmallVec<[Val; 64]> {
462         &mut self.inner
463     }
464 
465     /// Get a reference to the inner stack representation.
466     pub fn inner(&self) -> &SmallVec<[Val; 64]> {
467         &self.inner
468     }
469 
470     /// Calculates the size of, in bytes, of the top n [Memory] entries
471     /// in the value stack.
472     pub fn sizeof(&self, top: usize) -> u32 {
473         self.peekn(top).fold(0, |acc, v| {
474             if v.is_mem() {
475                 acc + v.unwrap_mem().slot.size
476             } else {
477                 acc
478             }
479         })
480     }
481 }
482 
483 #[cfg(test)]
484 mod tests {
485     use super::{Stack, Val};
486     use crate::isa::reg::Reg;
487     use wasmtime_environ::WasmValType;
488 
489     #[test]
490     fn test_pop_i32_const() {
491         let mut stack = Stack::new();
492         stack.push(Val::i32(33i32));
493         assert_eq!(33, stack.pop_i32_const().unwrap());
494 
495         stack.push(Val::local(10, WasmValType::I32));
496         assert!(stack.pop_i32_const().is_none());
497     }
498 
499     #[test]
500     fn test_pop_reg() {
501         let mut stack = Stack::new();
502         let reg = Reg::int(2usize);
503         stack.push(Val::reg(reg, WasmValType::I32));
504         stack.push(Val::i32(4));
505 
506         assert_eq!(None, stack.pop_reg());
507         let _ = stack.pop().unwrap();
508         assert_eq!(reg, stack.pop_reg().unwrap().reg);
509     }
510 
511     #[test]
512     fn test_pop_named_reg() {
513         let mut stack = Stack::new();
514         let reg = Reg::int(2usize);
515         stack.push(Val::reg(reg, WasmValType::I32));
516         stack.push(Val::reg(Reg::int(4), WasmValType::I32));
517 
518         assert_eq!(None, stack.pop_named_reg(reg));
519         let _ = stack.pop().unwrap();
520         assert_eq!(reg, stack.pop_named_reg(reg).unwrap().reg);
521     }
522 }
523