wasm/validation/
validation_stack.rs

1use core::iter;
2
3use super::Result;
4use alloc::vec;
5use alloc::vec::Vec;
6
7use crate::{
8    core::reader::types::{FuncType, ResultType},
9    Error, NumType, RefType, ValType,
10};
11
12#[derive(Debug, PartialEq, Eq)]
13pub struct ValidationStack {
14    stack: Vec<ValidationStackEntry>,
15    // TODO hide implementation
16    pub ctrl_stack: Vec<CtrlStackEntry>,
17}
18
19impl ValidationStack {
20    /// Initialize a new ValidationStack to validate a block of type [] -> []
21    pub fn new() -> Self {
22        Self {
23            stack: Vec::new(),
24            ctrl_stack: vec![CtrlStackEntry {
25                label_info: LabelInfo::Untyped,
26                block_ty: FuncType {
27                    params: ResultType {
28                        valtypes: Vec::new(),
29                    },
30                    returns: ResultType {
31                        valtypes: Vec::new(),
32                    },
33                },
34                height: 0,
35                unreachable: false,
36            }],
37        }
38    }
39
40    /// Initialize a new ValidationStack to validate a block of type `block_ty`
41    pub(super) fn new_for_func(block_ty: FuncType) -> Self {
42        Self {
43            stack: Vec::new(),
44            ctrl_stack: vec![CtrlStackEntry {
45                label_info: LabelInfo::Func {
46                    stps_to_backpatch: Vec::new(),
47                },
48                block_ty,
49                height: 0,
50                unreachable: false,
51            }],
52        }
53    }
54
55    pub fn len(&self) -> usize {
56        self.stack.len()
57    }
58
59    pub fn push_valtype(&mut self, valtype: ValType) {
60        self.stack.push(ValidationStackEntry::Val(valtype));
61    }
62
63    /// Similar to [`ValidationStack::pop_valtype`], because it pops a value from the stack,
64    /// but more public and doesn't actually return the popped value.
65    pub(super) fn drop_val(&mut self) -> Result<()> {
66        self.pop_valtype().map_err(|_| Error::ExpectedAnOperand)?;
67        Ok(())
68    }
69
70    /// Mark the current control block as unreachable, removing all of the types pushed to the stack since the current control block was entered.
71    /// pop operations from the stack will yield `Ok(ValidationStackEntry::Bottom)` if the stack height is the same as the height when this control
72    /// block was entered.
73    ///
74    /// Returns `Ok(())` if called during validation of a control block. `Returns Err(Error::ValidationCtrlStackEmpty)` if no control block context is found
75    /// in the control block stack.
76    pub(super) fn make_unspecified(&mut self) -> Result<()> {
77        let last_ctrl_stack_entry = self
78            .ctrl_stack
79            .last_mut()
80            .ok_or(Error::ValidationCtrlStackEmpty)?;
81        last_ctrl_stack_entry.unreachable = true;
82        self.stack.truncate(last_ctrl_stack_entry.height);
83        Ok(())
84    }
85
86    /// Pop a [`ValidationStackEntry`] from the [`ValidationStack`]
87    ///
88    /// # Returns
89    ///
90    /// - Returns `Ok(_)` with the former top-most [`ValidationStackEntry`] inside, if the stack had
91    ///   at least one element pushed after the current control block is entered. May also return `Ok(ValidationStackEntry::Bottom)`
92    ///   if `make_unspecified` is called within the current control block.
93    /// - Returns `Err(_)` otherwise.
94    fn pop_valtype(&mut self) -> Result<ValidationStackEntry> {
95        // TODO unwrapping might not be the best option
96        // TODO ugly
97        // TODO return type should be Result<()> maybe?
98        let last_ctrl_stack_entry = self.ctrl_stack.last().unwrap();
99        assert!(self.stack.len() >= last_ctrl_stack_entry.height);
100        if last_ctrl_stack_entry.height == self.stack.len() {
101            if last_ctrl_stack_entry.unreachable {
102                Ok(ValidationStackEntry::Bottom)
103            } else {
104                Err(Error::EndInvalidValueStack)
105            }
106        } else {
107            //empty stack is covered with above check
108            self.stack.pop().ok_or(Error::EndInvalidValueStack)
109        }
110    }
111
112    /// Attempt popping `Valtype::RefType(expected_ty)` from type stack.
113    ///
114    /// # Returns
115    ///
116    /// - Returns `Ok(())` if `Valtype::RefType(expected_ty)` unifies to the item returned by `pop_valtype` operation and `Err(_)` otherwise.
117    pub fn assert_pop_ref_type(&mut self, expected_ty: Option<RefType>) -> Result<()> {
118        match self.pop_valtype()? {
119            ValidationStackEntry::Val(ValType::RefType(ref_type)) => {
120                expected_ty.map_or(Ok(()), |ty| {
121                    (ty == ref_type)
122                        .then_some(())
123                        .ok_or(Error::DifferentRefTypes(ref_type, ty))
124                })
125            }
126            ValidationStackEntry::Val(v) => Err(Error::ExpectedARefType(v)),
127            ValidationStackEntry::Bottom => Ok(()),
128        }
129    }
130
131    /// Attempt popping expected_ty from type stack.
132    ///
133    /// # Returns
134    ///
135    /// - Returns `Ok(())` if expected_ty unifies to the item returned by `pop_valtype` operation and `Err(_)` otherwise.
136    pub fn assert_pop_val_type(&mut self, expected_ty: ValType) -> Result<()> {
137        match self.pop_valtype()? {
138            ValidationStackEntry::Val(ty) => (ty == expected_ty)
139                .then_some(())
140                .ok_or(Error::InvalidValidationStackValType(Some(ty))),
141            ValidationStackEntry::Bottom => Ok(()),
142        }
143    }
144
145    // private fns to shut the borrow checker up when calling methods with mutable ref to self with immutable ref to self arguments
146    // TODO ugly but I can't come up with anything else better
147    fn assert_val_types_on_top_with_custom_stacks(
148        stack: &mut Vec<ValidationStackEntry>,
149        ctrl_stack: &[CtrlStackEntry],
150        expected_val_types: &[ValType],
151        unify_to_expected_types: bool,
152    ) -> Result<()> {
153        let last_ctrl_stack_entry = ctrl_stack.last().ok_or(Error::ValidationCtrlStackEmpty)?;
154        let stack_len = stack.len();
155
156        let rev_iterator = expected_val_types.iter().rev().enumerate();
157        for (i, expected_ty) in rev_iterator {
158            if stack_len - last_ctrl_stack_entry.height <= i {
159                if last_ctrl_stack_entry.unreachable {
160                    if unify_to_expected_types {
161                        // Unify(t2*,expected_val_types) := [t2* expected_val_types]
162                        stack.splice(
163                            stack_len - i..stack_len - i,
164                            expected_val_types[..expected_val_types.len() - i]
165                                .iter()
166                                .map(|ty| ValidationStackEntry::Val(*ty)),
167                        );
168                    } else {
169                        stack.splice(
170                            stack_len - i..stack_len - i,
171                            iter::repeat(ValidationStackEntry::Bottom)
172                                .take(expected_val_types.len() - i),
173                        );
174                    }
175                    return Ok(());
176                } else {
177                    return Err(Error::EndInvalidValueStack);
178                }
179            }
180
181            // the above height check ensures this access is valid
182            let actual_ty = &mut stack[stack_len - i - 1];
183
184            match actual_ty {
185                ValidationStackEntry::Val(actual_val_ty) => {
186                    if *actual_val_ty != *expected_ty {
187                        return Err(Error::EndInvalidValueStack);
188                    }
189                }
190                ValidationStackEntry::Bottom => {
191                    // Bottom will always unify to the expected ty
192                    if unify_to_expected_types {
193                        *actual_ty = ValidationStackEntry::Val(*expected_ty);
194                    }
195                }
196            }
197        }
198
199        Ok(())
200    }
201
202    fn assert_val_types_with_custom_stacks(
203        stack: &mut Vec<ValidationStackEntry>,
204        ctrl_stack: &[CtrlStackEntry],
205        expected_val_types: &[ValType],
206        unify_to_expected_types: bool,
207    ) -> Result<()> {
208        ValidationStack::assert_val_types_on_top_with_custom_stacks(
209            stack,
210            ctrl_stack,
211            expected_val_types,
212            unify_to_expected_types,
213        )?;
214        //if we can assert types in the above there is a last ctrl stack entry, this access is valid.
215        let last_ctrl_stack_entry = &ctrl_stack[ctrl_stack.len() - 1];
216        if stack.len() == last_ctrl_stack_entry.height + expected_val_types.len() {
217            Ok(())
218        } else {
219            Err(Error::EndInvalidValueStack)
220        }
221    }
222
223    /// Assert that the types retrieved from the type stack by `pop_valtype` unify to `expected_val_types`, and
224    /// after this operation the type stack would be the same as the first time the current control block is entered.
225    /// This method will unify the types on the stack to the expected valtypes if `unify_to_expected_types` is set.
226    /// Any occurence of an error may leave the stack in an invalid state.
227    ///
228    /// # Returns
229    ///
230    /// - `Ok(_)`, the tail of the stack unifies to the `expected_val_types`
231    /// - `Err(_)` otherwise
232    ///
233    pub(super) fn assert_val_types_on_top(
234        &mut self,
235        expected_val_types: &[ValType],
236        unify_to_expected_types: bool,
237    ) -> Result<()> {
238        ValidationStack::assert_val_types_on_top_with_custom_stacks(
239            &mut self.stack,
240            &self.ctrl_stack,
241            expected_val_types,
242            unify_to_expected_types,
243        )
244    }
245
246    /// Assert that the types retrieved from the type stack by `pop_valtype` unify to `expected_val_types`.
247    /// This method will unify the types on the stack to the expected valtypes if `unify_to_expected_types` is set.
248    /// Any occurence of an error may leave the stack in an invalid state.
249    ///
250    /// # Returns
251    ///
252    /// - `Ok(_)`, the tail of the stack unifies to the `expected_val_types`
253    /// - `Err(_)` otherwise
254    ///
255    pub fn assert_val_types(
256        &mut self,
257        expected_val_types: &[ValType],
258        unify_to_expected_types: bool,
259    ) -> Result<()> {
260        ValidationStack::assert_val_types_with_custom_stacks(
261            &mut self.stack,
262            &self.ctrl_stack,
263            expected_val_types,
264            unify_to_expected_types,
265        )
266    }
267
268    /// Call `assert_val_types_on_top` for the label signature of the `label_idx`th outer control block (0 corresponds to the current control block).
269    /// Label signature of all controi blocks are the output signature of the control blocks except for the Loop block. For Loop blocks, it is the input signature.
270    /// This method will unify the types on the stack to the expected valtypes if `unify_to_expected_types` is set.
271    ///
272    /// # Returns
273    ///
274    /// - `Ok(_)`, the tail of the stack unifies to the label signature of the  `label_idx`th outer control block
275    /// - `Err(_)` otherwise
276    ///
277    pub fn assert_val_types_of_label_jump_types_on_top(
278        &mut self,
279        label_idx: usize,
280        unify_to_expected_types: bool,
281    ) -> Result<()> {
282        let label_types = self
283            .ctrl_stack
284            .get(self.ctrl_stack.len() - label_idx - 1)
285            .ok_or(Error::InvalidLabelIdx(label_idx))?
286            .label_types();
287        ValidationStack::assert_val_types_on_top_with_custom_stacks(
288            &mut self.stack,
289            &self.ctrl_stack,
290            label_types,
291            unify_to_expected_types,
292        )
293    }
294
295    /// Signal to this struct that a new control block is entered, and calls `assert_val_types_on_top` with the input signature of the new control block.
296    /// This method will unify the types on the stack to the expected valtypes if `unify_to_expected_types` is set.
297    ///
298    /// # Returns
299    ///
300    /// - `Ok(_)`, the tail of the stack unifies to the input signature of the  new control block
301    /// - `Err(_)` otherwise
302    ///
303    pub fn assert_push_ctrl(
304        &mut self,
305        label_info: LabelInfo,
306        block_ty: FuncType,
307        unify_to_expected_types: bool,
308    ) -> Result<()> {
309        self.assert_val_types_on_top(&block_ty.params.valtypes, unify_to_expected_types)?;
310        let height = self.stack.len() - block_ty.params.valtypes.len();
311        self.ctrl_stack.push(CtrlStackEntry {
312            label_info,
313            block_ty,
314            height,
315            unreachable: false,
316        });
317        Ok(())
318    }
319
320    /// Signal to this struct that the current control block is exited, and calls `assert_val_types_on_top` with the output signature of the new control block.
321    /// This method will unify the types on the stack to the expected valtypes if `unify_to_expected_types` is set.
322    ///
323    /// # Returns
324    ///
325    /// - `Ok(_)`, the tail of the stack unifies to the output signature of the current control block
326    /// - `Err(_)` otherwise
327    ///
328    pub fn assert_pop_ctrl(
329        &mut self,
330        unify_to_expected_types: bool,
331    ) -> Result<(LabelInfo, FuncType)> {
332        let return_types = &self
333            .ctrl_stack
334            .last()
335            .ok_or(Error::ValidationCtrlStackEmpty)?
336            .block_ty
337            .returns
338            .valtypes;
339        ValidationStack::assert_val_types_with_custom_stacks(
340            &mut self.stack,
341            &self.ctrl_stack,
342            return_types,
343            unify_to_expected_types,
344        )?;
345
346        //if we can assert types in the above there is a last ctrl stack entry, this access is valid.
347        let last_ctrl_stack_entry = self.ctrl_stack.pop().unwrap();
348        Ok((
349            last_ctrl_stack_entry.label_info,
350            last_ctrl_stack_entry.block_ty,
351        ))
352    }
353
354    /// Validate the `SELECT` instruction within the current control block. Returns OK(()) on success, Err(_) otherwise.
355    pub fn validate_polymorphic_select(&mut self) -> Result<()> {
356        //SELECT instruction has the type signature
357        //[t t i32] -> [t] where t unifies to a NumType(_) or VecType
358
359        self.assert_pop_val_type(ValType::NumType(crate::NumType::I32))?;
360
361        let first_arg = self.pop_valtype()?;
362        let second_arg = self.pop_valtype()?;
363
364        let unified_type = second_arg
365            .unify(&first_arg)
366            .ok_or(Error::InvalidValidationStackValType(None))?;
367
368        // t must unify to a NumType(_) or VecType
369        if !(unified_type.unifies_to(&ValidationStackEntry::Val(ValType::NumType(NumType::I32)))
370            || unified_type.unifies_to(&ValidationStackEntry::Val(ValType::NumType(NumType::F32)))
371            || unified_type.unifies_to(&ValidationStackEntry::Val(ValType::NumType(NumType::I64)))
372            || unified_type.unifies_to(&ValidationStackEntry::Val(ValType::NumType(NumType::F64)))
373            || unified_type.unifies_to(&ValidationStackEntry::Val(ValType::VecType)))
374        {
375            return Err(Error::InvalidValidationStackValType(None));
376        }
377
378        self.stack.push(unified_type);
379        Ok(())
380    }
381}
382
383/// corresponds to `opdtype` <https://webassembly.github.io/spec/core/valid/instructions.html#instructions>
384#[derive(Clone, Debug, PartialEq, Eq)]
385pub enum ValidationStackEntry {
386    Val(ValType),
387    Bottom,
388}
389
390impl ValidationStackEntry {
391    /// corresponds to whether `(self, other)` is a member of "matches" (<=) relation defined in <https://webassembly.github.io/spec/core/valid/instructions.html#instructions>
392    fn unifies_to(&self, other: &ValidationStackEntry) -> bool {
393        match self {
394            ValidationStackEntry::Bottom => true,
395            ValidationStackEntry::Val(_) => self == other,
396        }
397    }
398
399    /// convenience method that returns `Some(other)` if `self.unifies_to(other)` is true and `None` otherwise
400    fn unify(&self, other: &ValidationStackEntry) -> Option<Self> {
401        self.unifies_to(other).then(|| other.clone())
402    }
403}
404
405// TODO hide implementation
406#[derive(Clone, Debug, PartialEq, Eq)]
407pub struct CtrlStackEntry {
408    pub label_info: LabelInfo,
409    pub block_ty: FuncType,
410    pub height: usize,
411    pub unreachable: bool,
412}
413
414impl CtrlStackEntry {
415    pub fn label_types(&self) -> &[ValType] {
416        if matches!(self.label_info, LabelInfo::Loop { .. }) {
417            &self.block_ty.params.valtypes
418        } else {
419            &self.block_ty.returns.valtypes
420        }
421    }
422}
423
424// TODO replace LabelInfo with this
425// TODO hide implementation
426// TODO implementation coupled to Sidetable
427#[derive(Clone, Debug, PartialEq, Eq)]
428pub enum LabelInfo {
429    Block {
430        stps_to_backpatch: Vec<usize>,
431    },
432    Loop {
433        ip: usize,
434        stp: usize,
435    },
436    If {
437        stps_to_backpatch: Vec<usize>,
438        stp: usize,
439    },
440    Func {
441        stps_to_backpatch: Vec<usize>,
442    },
443    Untyped,
444}
445
446#[cfg(test)]
447mod tests {
448    use crate::{NumType, RefType, ValType};
449
450    use super::{CtrlStackEntry, FuncType, LabelInfo, ResultType, ValidationStack, Vec};
451
452    fn push_dummy_untyped_label(validation_stack: &mut ValidationStack) {
453        validation_stack.ctrl_stack.push(CtrlStackEntry {
454            label_info: LabelInfo::Untyped,
455            block_ty: FuncType {
456                params: ResultType {
457                    valtypes: Vec::new(),
458                },
459                returns: ResultType {
460                    valtypes: Vec::new(),
461                },
462            },
463            height: validation_stack.len(),
464            unreachable: false,
465        })
466    }
467
468    #[test]
469    fn push_then_pop() {
470        let mut stack = ValidationStack::new();
471
472        stack.push_valtype(ValType::NumType(NumType::F64));
473        stack.push_valtype(ValType::NumType(NumType::I32));
474        stack.push_valtype(ValType::VecType);
475        stack.push_valtype(ValType::RefType(RefType::ExternRef));
476
477        stack
478            .assert_pop_val_type(ValType::RefType(RefType::ExternRef))
479            .unwrap();
480        stack.assert_pop_val_type(ValType::VecType).unwrap();
481        stack
482            .assert_pop_val_type(ValType::NumType(NumType::I32))
483            .unwrap();
484        stack
485            .assert_pop_val_type(ValType::NumType(NumType::F64))
486            .unwrap();
487    }
488
489    // TODO rewrite these
490    // #[test]
491    // fn labels() {
492    //     let mut stack = ValidationStack::new();
493
494    //     stack.push_valtype(ValType::NumType(NumType::I64));
495    //     push_dummy_func_label(&mut stack);
496
497    //     push_dummy_block_label(&mut stack);
498
499    //     stack.push_valtype(ValType::VecType);
500
501    //     // This removes the `ValType::VecType` and the `LabelKind::Loop` label
502    //     let popped_label = stack.pop_label_and_above().unwrap();
503    //     assert_eq!(
504    //         popped_label,
505    //         LabelInfo {
506    //             kind: LabelKind::Loop,
507    //         }
508    //     );
509
510    //     let popped_label = stack.pop_label_and_above().unwrap();
511    //     assert_eq!(
512    //         popped_label,
513    //         LabelInfo {
514    //             kind: LabelKind::Block,
515    //         }
516    //     );
517
518    //     // The first valtype should still be there
519    //     stack.assert_pop_val_type(ValType::NumType(NumType::I64));
520    // }
521
522    #[test]
523    fn assert_valtypes() {
524        let mut stack = ValidationStack::new();
525
526        stack.push_valtype(ValType::NumType(NumType::F64));
527        stack.push_valtype(ValType::NumType(NumType::I32));
528        stack.push_valtype(ValType::NumType(NumType::F32));
529
530        stack
531            .assert_val_types(
532                &[
533                    ValType::NumType(NumType::F64),
534                    ValType::NumType(NumType::I32),
535                    ValType::NumType(NumType::F32),
536                ],
537                true,
538            )
539            .unwrap();
540
541        push_dummy_untyped_label(&mut stack);
542
543        stack.push_valtype(ValType::NumType(NumType::I32));
544
545        stack
546            .assert_val_types(&[ValType::NumType(NumType::I32)], true)
547            .unwrap();
548    }
549
550    #[test]
551    fn assert_emtpy_valtypes() {
552        let mut stack = ValidationStack::new();
553
554        stack.assert_val_types(&[], true).unwrap();
555
556        stack.push_valtype(ValType::NumType(NumType::I32));
557        push_dummy_untyped_label(&mut stack);
558
559        // Valtypes separated by a label should also not be detected
560        stack.assert_val_types(&[], true).unwrap();
561    }
562
563    #[test]
564    fn assert_valtypes_on_top() {
565        let mut stack = ValidationStack::new();
566
567        stack.assert_val_types_on_top(&[], true).unwrap();
568
569        stack.push_valtype(ValType::NumType(NumType::I32));
570        stack.push_valtype(ValType::NumType(NumType::F32));
571        stack.push_valtype(ValType::NumType(NumType::I64));
572
573        // There are always zero valtypes on top of the stack
574        stack.assert_val_types_on_top(&[], true).unwrap();
575
576        stack
577            .assert_val_types_on_top(&[ValType::NumType(NumType::I64)], true)
578            .unwrap();
579
580        stack
581            .assert_val_types_on_top(
582                &[
583                    ValType::NumType(NumType::F32),
584                    ValType::NumType(NumType::I64),
585                ],
586                true,
587            )
588            .unwrap();
589
590        stack
591            .assert_val_types_on_top(
592                &[
593                    ValType::NumType(NumType::I32),
594                    ValType::NumType(NumType::F32),
595                    ValType::NumType(NumType::I64),
596                ],
597                true,
598            )
599            .unwrap();
600    }
601
602    #[test]
603    fn unspecified() {
604        let mut stack = ValidationStack::new();
605        push_dummy_untyped_label(&mut stack);
606
607        stack.make_unspecified().unwrap();
608
609        // Now we can pop as many valtypes from the stack as we want
610        stack
611            .assert_pop_val_type(ValType::NumType(NumType::I32))
612            .unwrap();
613
614        stack
615            .assert_pop_val_type(ValType::RefType(RefType::ExternRef))
616            .unwrap();
617
618        // Let's remove the unspecified entry and the first label
619
620        // TODO hide implementation
621        stack.ctrl_stack.pop();
622
623        // Now there are no values left on the stack
624        assert_eq!(stack.assert_val_types(&[], true), Ok(()));
625    }
626
627    #[test]
628    fn unspecified2() {
629        let mut stack = ValidationStack::new();
630        push_dummy_untyped_label(&mut stack);
631
632        stack.make_unspecified().unwrap();
633
634        // Stack needs to keep track of unified types, I64 and F32 and I32 will appear.
635        stack
636            .assert_val_types(
637                &[
638                    ValType::NumType(NumType::I64),
639                    ValType::NumType(NumType::F32),
640                    ValType::NumType(NumType::I32),
641                ],
642                true,
643            )
644            .unwrap();
645
646        stack.ctrl_stack.pop();
647
648        assert_eq!(
649            stack.assert_pop_val_type(ValType::NumType(NumType::I32)),
650            Ok(())
651        );
652        assert_eq!(
653            stack.assert_pop_val_type(ValType::NumType(NumType::F32)),
654            Ok(())
655        );
656        assert_eq!(
657            stack.assert_pop_val_type(ValType::NumType(NumType::I64)),
658            Ok(())
659        );
660    }
661
662    #[test]
663    fn unspecified3() {
664        let mut stack = ValidationStack::new();
665        push_dummy_untyped_label(&mut stack);
666
667        stack.make_unspecified().unwrap();
668
669        stack.push_valtype(ValType::NumType(NumType::I32));
670
671        // Stack needs to keep track of unified types, I64 and F32 will appear under I32.
672        // Stack needs to keep track of unified types, I64 and F32 and I32 will appear.
673        stack
674            .assert_val_types(
675                &[
676                    ValType::NumType(NumType::I64),
677                    ValType::NumType(NumType::F32),
678                    ValType::NumType(NumType::I32),
679                ],
680                true,
681            )
682            .unwrap();
683
684        stack.ctrl_stack.pop();
685
686        assert_eq!(
687            stack.assert_pop_val_type(ValType::NumType(NumType::I32)),
688            Ok(())
689        );
690        assert_eq!(
691            stack.assert_pop_val_type(ValType::NumType(NumType::F32)),
692            Ok(())
693        );
694        assert_eq!(
695            stack.assert_pop_val_type(ValType::NumType(NumType::I64)),
696            Ok(())
697        );
698    }
699
700    #[test]
701    fn unspecified4() {
702        let mut stack = ValidationStack::new();
703
704        stack.push_valtype(ValType::VecType);
705        stack.push_valtype(ValType::NumType(NumType::I32));
706
707        push_dummy_untyped_label(&mut stack);
708
709        stack.make_unspecified().unwrap();
710
711        stack.push_valtype(ValType::VecType);
712        stack.push_valtype(ValType::RefType(RefType::FuncRef));
713
714        // Stack needs to keep track of unified types, I64 and F32 will appear below VecType and RefType
715        // and above I32 and VecType
716        stack
717            .assert_val_types(
718                &[
719                    ValType::NumType(NumType::I64),
720                    ValType::NumType(NumType::F32),
721                    ValType::VecType,
722                    ValType::RefType(RefType::FuncRef),
723                ],
724                true,
725            )
726            .unwrap();
727
728        stack.ctrl_stack.pop();
729
730        assert_eq!(
731            stack.assert_pop_val_type(ValType::RefType(RefType::FuncRef)),
732            Ok(())
733        );
734        assert_eq!(stack.assert_pop_val_type(ValType::VecType), Ok(()));
735        assert_eq!(
736            stack.assert_pop_val_type(ValType::NumType(NumType::F32)),
737            Ok(())
738        );
739        assert_eq!(
740            stack.assert_pop_val_type(ValType::NumType(NumType::I64)),
741            Ok(())
742        );
743        assert_eq!(
744            stack.assert_pop_val_type(ValType::NumType(NumType::I32)),
745            Ok(())
746        );
747        assert_eq!(stack.assert_pop_val_type(ValType::VecType), Ok(()));
748    }
749}