Skip to main content

winch_codegen/
stack.rs

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