xref: /wasmtime-44.0.1/winch/codegen/src/stack.rs (revision 5bdb78b3)
1 use crate::{Result, codegen::CodeGenError, format_err, isa::reg::Reg, masm::StackSlot};
2 use smallvec::SmallVec;
3 use wasmparser::{Ieee32, Ieee64};
4 use 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)]
9 pub struct TypedReg {
10     /// The physical register.
11     pub reg: Reg,
12     /// The type associated to the physical register.
13     pub ty: WasmValType,
14 }
15 
16 impl 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 
63 impl 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)]
71 pub 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)]
80 pub 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)]
89 pub(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 
108 impl From<TypedReg> for Val {
109     fn from(tr: TypedReg) -> Self {
110         Val::Reg(tr)
111     }
112 }
113 
114 impl From<Local> for Val {
115     fn from(local: Local) -> Self {
116         Val::Local(local)
117     }
118 }
119 
120 impl From<Memory> for Val {
121     fn from(mem: Memory) -> Self {
122         Val::Memory(mem)
123     }
124 }
125 
126 impl 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 
133 impl 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)]
317 pub(crate) struct Stack {
318     // NB: The 64 is chosen arbitrarily. We can adjust as we see fit.
319     inner: SmallVec<[Val; 64]>,
320 }
321 
322 impl 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)]
483 mod 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 }
522