Skip to main content

baedeker_core/validate/
state.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Validation state and control-flow scaffolding.
5//!
6//! These types model the operand/control stacks used by the WebAssembly validation
7//! algorithm. The full spec algorithm will refine these structures over time.
8
9use alloc::{vec, vec::Vec};
10
11use crate::types::{BlockType, ValType};
12
13/// Operand-stack entry used during validation.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum OperandType {
16    Typed(ValType),
17    Bottom,
18}
19
20/// Reachability state of the current validation point.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Reachability {
23    Reachable,
24    Unreachable,
25}
26
27/// Kind of structured control frame.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ControlKind {
30    Function,
31    Block,
32    Loop,
33    If,
34}
35
36/// A structured control frame in the validator.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ControlFrame {
39    pub kind: ControlKind,
40    pub block_type: BlockType,
41    pub outer_height: usize,
42    pub stack_floor: usize,
43    pub start_types: Vec<ValType>,
44    pub end_types: Vec<ValType>,
45    pub local_inits: Vec<bool>,
46    pub has_else: bool,
47}
48
49/// Operand stack state.
50#[derive(Debug, Clone, PartialEq, Eq, Default)]
51pub struct TypeStack {
52    values: Vec<OperandType>,
53}
54
55impl TypeStack {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    pub fn len(&self) -> usize {
61        self.values.len()
62    }
63
64    pub fn is_empty(&self) -> bool {
65        self.values.is_empty()
66    }
67
68    pub fn truncate(&mut self, len: usize) {
69        self.values.truncate(len);
70    }
71
72    pub fn push(&mut self, value: ValType) {
73        self.values.push(OperandType::Typed(value));
74    }
75
76    pub fn push_bottom(&mut self) {
77        self.values.push(OperandType::Bottom);
78    }
79
80    pub fn pop(&mut self) -> Option<OperandType> {
81        self.values.pop()
82    }
83
84    pub fn as_slice(&self) -> &[OperandType] {
85        &self.values
86    }
87}
88
89/// Function-local validation state.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct ValidationState {
92    pub operands: TypeStack,
93    pub controls: Vec<ControlFrame>,
94    pub locals: Vec<ValType>,
95    pub local_inits: Vec<bool>,
96    pub reachability: Reachability,
97}
98
99impl ValidationState {
100    pub fn new(locals: Vec<ValType>, local_inits: Vec<bool>, result_types: Vec<ValType>) -> Self {
101        Self {
102            operands: TypeStack::new(),
103            controls: vec![ControlFrame {
104                kind: ControlKind::Function,
105                block_type: BlockType::Empty,
106                outer_height: 0,
107                stack_floor: 0,
108                start_types: Vec::new(),
109                end_types: result_types,
110                local_inits: local_inits.clone(),
111                has_else: false,
112            }],
113            locals,
114            local_inits,
115            reachability: Reachability::Reachable,
116        }
117    }
118
119    pub fn current_frame(&self) -> &ControlFrame {
120        self.controls
121            .last()
122            .expect("validation state must always contain a function frame")
123    }
124
125    pub fn current_frame_mut(&mut self) -> &mut ControlFrame {
126        self.controls
127            .last_mut()
128            .expect("validation state must always contain a function frame")
129    }
130
131    pub fn push_frame(
132        &mut self,
133        kind: ControlKind,
134        block_type: BlockType,
135        start_types: Vec<ValType>,
136        end_types: Vec<ValType>,
137    ) {
138        let outer_height = self.operands.len();
139        for ty in &start_types {
140            self.operands.push(*ty);
141        }
142        let stack_floor = self.operands.len();
143
144        self.controls.push(ControlFrame {
145            kind,
146            block_type,
147            outer_height,
148            stack_floor,
149            start_types,
150            end_types,
151            local_inits: self.local_inits.clone(),
152            has_else: false,
153        });
154    }
155
156    pub fn pop_frame(&mut self) -> Option<ControlFrame> {
157        if self.controls.len() > 1 {
158            self.controls.pop()
159        } else {
160            None
161        }
162    }
163
164    pub fn current_label_types(&self, depth: u32) -> Option<&[ValType]> {
165        let frame = self.controls.iter().rev().nth(depth as usize)?;
166        Some(match frame.kind {
167            ControlKind::Loop => frame.start_types.as_slice(),
168            _ => frame.end_types.as_slice(),
169        })
170    }
171
172    pub fn enter_unreachable(&mut self) {
173        let floor = self.current_frame().outer_height;
174        self.operands.truncate(floor);
175        self.reachability = Reachability::Unreachable;
176    }
177}