wasmtime/runtime/vm/
const_expr.rs

1//! Evaluating const expressions.
2
3use crate::prelude::*;
4use crate::runtime::vm::{Instance, VMGcRef, ValRaw, I31};
5use crate::store::{AutoAssertNoGc, StoreOpaque};
6#[cfg(feature = "gc")]
7use crate::{ArrayRef, ArrayRefPre, ArrayType, StructRef, StructRefPre, StructType, Val};
8use smallvec::SmallVec;
9use wasmtime_environ::{ConstExpr, ConstOp, FuncIndex, GlobalIndex};
10#[cfg(feature = "gc")]
11use wasmtime_environ::{VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType};
12
13/// An interpreter for const expressions.
14///
15/// This can be reused across many const expression evaluations to reuse
16/// allocated resources, if any.
17#[derive(Default)]
18pub struct ConstExprEvaluator {
19    stack: SmallVec<[ValRaw; 2]>,
20}
21
22/// The context within which a particular const expression is evaluated.
23pub struct ConstEvalContext<'a> {
24    pub(crate) instance: &'a mut Instance,
25}
26
27impl<'a> ConstEvalContext<'a> {
28    /// Create a new context.
29    pub fn new(instance: &'a mut Instance) -> Self {
30        Self { instance }
31    }
32
33    fn global_get(&mut self, store: &mut AutoAssertNoGc<'_>, index: GlobalIndex) -> Result<ValRaw> {
34        unsafe {
35            let global = self.instance.defined_or_imported_global_ptr(index).as_ref();
36            global.to_val_raw(store, self.instance.env_module().globals[index].wasm_ty)
37        }
38    }
39
40    fn ref_func(&mut self, index: FuncIndex) -> Result<ValRaw> {
41        Ok(ValRaw::funcref(
42            self.instance.get_func_ref(index).unwrap().as_ptr().cast(),
43        ))
44    }
45
46    #[cfg(feature = "gc")]
47    fn struct_fields_len(
48        &self,
49        store: &mut AutoAssertNoGc<'_>,
50        shared_ty: VMSharedTypeIndex,
51    ) -> usize {
52        let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
53        let fields = struct_ty.fields();
54        fields.len()
55    }
56
57    /// Safety: field values must be of the correct types.
58    #[cfg(feature = "gc")]
59    unsafe fn struct_new(
60        &mut self,
61        store: &mut AutoAssertNoGc<'_>,
62        shared_ty: VMSharedTypeIndex,
63        fields: &[ValRaw],
64    ) -> Result<ValRaw> {
65        let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
66        let fields = fields
67            .iter()
68            .zip(struct_ty.fields())
69            .map(|(raw, ty)| {
70                let ty = ty.element_type().unpack();
71                Val::_from_raw(store, *raw, ty)
72            })
73            .collect::<Vec<_>>();
74
75        let allocator = StructRefPre::_new(store, struct_ty);
76        let struct_ref = StructRef::_new(store, &allocator, &fields)?;
77        let raw = struct_ref.to_anyref()._to_raw(store)?;
78        Ok(ValRaw::anyref(raw))
79    }
80
81    #[cfg(feature = "gc")]
82    fn struct_new_default(
83        &mut self,
84        store: &mut AutoAssertNoGc<'_>,
85        shared_ty: VMSharedTypeIndex,
86    ) -> Result<ValRaw> {
87        let module = self
88            .instance
89            .runtime_module()
90            .expect("should never be allocating a struct type defined in a dummy module");
91
92        let borrowed = module
93            .engine()
94            .signatures()
95            .borrow(shared_ty)
96            .expect("should have a registered type for struct");
97        let WasmSubType {
98            composite_type:
99                WasmCompositeType {
100                    shared: false,
101                    inner: WasmCompositeInnerType::Struct(struct_ty),
102                },
103            ..
104        } = &*borrowed
105        else {
106            unreachable!("registered type should be a struct");
107        };
108
109        let fields = struct_ty
110            .fields
111            .iter()
112            .map(|ty| match &ty.element_type {
113                wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
114                    ValRaw::i32(0)
115                }
116                wasmtime_environ::WasmStorageType::Val(v) => match v {
117                    wasmtime_environ::WasmValType::I32 => ValRaw::i32(0),
118                    wasmtime_environ::WasmValType::I64 => ValRaw::i64(0),
119                    wasmtime_environ::WasmValType::F32 => ValRaw::f32(0.0f32.to_bits()),
120                    wasmtime_environ::WasmValType::F64 => ValRaw::f64(0.0f64.to_bits()),
121                    wasmtime_environ::WasmValType::V128 => ValRaw::v128(0),
122                    wasmtime_environ::WasmValType::Ref(r) => {
123                        assert!(r.nullable);
124                        ValRaw::null()
125                    }
126                },
127            })
128            .collect::<SmallVec<[_; 8]>>();
129
130        unsafe { self.struct_new(store, shared_ty, &fields) }
131    }
132}
133
134impl ConstExprEvaluator {
135    /// Evaluate the given const expression in the given context.
136    ///
137    /// # Unsafety
138    ///
139    /// The given const expression must be valid within the given context,
140    /// e.g. the const expression must be well-typed and the context must return
141    /// global values of the expected types. This evaluator operates directly on
142    /// untyped `ValRaw`s and does not and cannot check that its operands are of
143    /// the correct type.
144    pub unsafe fn eval(
145        &mut self,
146        store: &mut StoreOpaque,
147        context: &mut ConstEvalContext<'_>,
148        expr: &ConstExpr,
149    ) -> Result<ValRaw> {
150        log::trace!("evaluating const expr: {:?}", expr);
151
152        self.stack.clear();
153
154        // Ensure that we don't permanently root any GC references we allocate
155        // during const evaluation, keeping them alive for the duration of the
156        // store's lifetime.
157        #[cfg(feature = "gc")]
158        let mut store = crate::OpaqueRootScope::new(store);
159        #[cfg(not(feature = "gc"))]
160        let mut store = store;
161
162        // We cannot allow GC during const evaluation because the stack of
163        // `ValRaw`s are not rooted. If we had a GC reference on our stack, and
164        // then performed a collection, that on-stack reference's object could
165        // be reclaimed or relocated by the collector, and then when we use the
166        // reference again we would basically get a use-after-free bug.
167        let mut store = AutoAssertNoGc::new(&mut store);
168
169        for op in expr.ops() {
170            log::trace!("const-evaluating op: {op:?}");
171            match op {
172                ConstOp::I32Const(i) => self.stack.push(ValRaw::i32(*i)),
173                ConstOp::I64Const(i) => self.stack.push(ValRaw::i64(*i)),
174                ConstOp::F32Const(f) => self.stack.push(ValRaw::f32(*f)),
175                ConstOp::F64Const(f) => self.stack.push(ValRaw::f64(*f)),
176                ConstOp::V128Const(v) => self.stack.push(ValRaw::v128(*v)),
177                ConstOp::GlobalGet(g) => self.stack.push(context.global_get(&mut store, *g)?),
178                ConstOp::RefNull => self.stack.push(ValRaw::null()),
179                ConstOp::RefFunc(f) => self.stack.push(context.ref_func(*f)?),
180                ConstOp::RefI31 => {
181                    let i = self.pop()?.get_i32();
182                    let i31 = I31::wrapping_i32(i);
183                    let raw = VMGcRef::from_i31(i31).as_raw_u32();
184                    self.stack.push(ValRaw::anyref(raw));
185                }
186                ConstOp::I32Add => {
187                    let b = self.pop()?.get_i32();
188                    let a = self.pop()?.get_i32();
189                    self.stack.push(ValRaw::i32(a.wrapping_add(b)));
190                }
191                ConstOp::I32Sub => {
192                    let b = self.pop()?.get_i32();
193                    let a = self.pop()?.get_i32();
194                    self.stack.push(ValRaw::i32(a.wrapping_sub(b)));
195                }
196                ConstOp::I32Mul => {
197                    let b = self.pop()?.get_i32();
198                    let a = self.pop()?.get_i32();
199                    self.stack.push(ValRaw::i32(a.wrapping_mul(b)));
200                }
201                ConstOp::I64Add => {
202                    let b = self.pop()?.get_i64();
203                    let a = self.pop()?.get_i64();
204                    self.stack.push(ValRaw::i64(a.wrapping_add(b)));
205                }
206                ConstOp::I64Sub => {
207                    let b = self.pop()?.get_i64();
208                    let a = self.pop()?.get_i64();
209                    self.stack.push(ValRaw::i64(a.wrapping_sub(b)));
210                }
211                ConstOp::I64Mul => {
212                    let b = self.pop()?.get_i64();
213                    let a = self.pop()?.get_i64();
214                    self.stack.push(ValRaw::i64(a.wrapping_mul(b)));
215                }
216
217                #[cfg(not(feature = "gc"))]
218                ConstOp::StructNew { .. }
219                | ConstOp::StructNewDefault { .. }
220                | ConstOp::ArrayNew { .. }
221                | ConstOp::ArrayNewDefault { .. }
222                | ConstOp::ArrayNewFixed { .. } => {
223                    bail!(
224                        "const expr evaluation error: struct operations are not \
225                         supported without the `gc` feature"
226                    )
227                }
228
229                #[cfg(feature = "gc")]
230                ConstOp::StructNew { struct_type_index } => {
231                    let interned_type_index = context.instance.env_module().types
232                        [*struct_type_index]
233                        .unwrap_engine_type_index();
234                    let len = context.struct_fields_len(&mut store, interned_type_index);
235
236                    if self.stack.len() < len {
237                        bail!(
238                            "const expr evaluation error: expected at least {len} values on the stack, found {}",
239                            self.stack.len()
240                        )
241                    }
242
243                    let start = self.stack.len() - len;
244                    let s = context.struct_new(
245                        &mut store,
246                        interned_type_index,
247                        &self.stack[start..],
248                    )?;
249                    self.stack.truncate(start);
250                    self.stack.push(s);
251                }
252
253                #[cfg(feature = "gc")]
254                ConstOp::StructNewDefault { struct_type_index } => {
255                    let ty = context.instance.env_module().types[*struct_type_index]
256                        .unwrap_engine_type_index();
257                    self.stack.push(context.struct_new_default(&mut store, ty)?);
258                }
259
260                #[cfg(feature = "gc")]
261                ConstOp::ArrayNew { array_type_index } => {
262                    let ty = context.instance.env_module().types[*array_type_index]
263                        .unwrap_engine_type_index();
264                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
265
266                    #[allow(clippy::cast_sign_loss)]
267                    let len = self.pop()?.get_i32() as u32;
268
269                    let elem = Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack());
270
271                    let pre = ArrayRefPre::_new(&mut store, ty);
272                    let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
273
274                    self.stack
275                        .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
276                }
277
278                #[cfg(feature = "gc")]
279                ConstOp::ArrayNewDefault { array_type_index } => {
280                    let ty = context.instance.env_module().types[*array_type_index]
281                        .unwrap_engine_type_index();
282                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
283
284                    #[allow(clippy::cast_sign_loss)]
285                    let len = self.pop()?.get_i32() as u32;
286
287                    let elem = Val::default_for_ty(ty.element_type().unpack())
288                        .expect("type should have a default value");
289
290                    let pre = ArrayRefPre::_new(&mut store, ty);
291                    let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
292
293                    self.stack
294                        .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
295                }
296
297                #[cfg(feature = "gc")]
298                ConstOp::ArrayNewFixed {
299                    array_type_index,
300                    array_size,
301                } => {
302                    let ty = context.instance.env_module().types[*array_type_index]
303                        .unwrap_engine_type_index();
304                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
305
306                    let array_size = usize::try_from(*array_size).unwrap();
307                    if self.stack.len() < array_size {
308                        bail!(
309                            "const expr evaluation error: expected at least {array_size} values on the stack, found {}",
310                            self.stack.len()
311                        )
312                    }
313
314                    let start = self.stack.len() - array_size;
315
316                    let elem_ty = ty.element_type();
317                    let elem_ty = elem_ty.unpack();
318
319                    let elems = self
320                        .stack
321                        .drain(start..)
322                        .map(|raw| Val::_from_raw(&mut store, raw, elem_ty))
323                        .collect::<SmallVec<[_; 8]>>();
324
325                    let pre = ArrayRefPre::_new(&mut store, ty);
326                    let array = ArrayRef::_new_fixed(&mut store, &pre, &elems)?;
327
328                    self.stack
329                        .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
330                }
331            }
332        }
333
334        if self.stack.len() == 1 {
335            log::trace!("const expr evaluated to {:?}", self.stack[0]);
336            Ok(self.stack[0])
337        } else {
338            bail!(
339                "const expr evaluation error: expected 1 resulting value, found {}",
340                self.stack.len()
341            )
342        }
343    }
344
345    fn pop(&mut self) -> Result<ValRaw> {
346        self.stack.pop().ok_or_else(|| {
347            anyhow!(
348                "const expr evaluation error: attempted to pop from an empty \
349                 evaluation stack"
350            )
351        })
352    }
353}