wasmtime/runtime/vm/
const_expr.rs

1//! Evaluating const expressions.
2
3use crate::prelude::*;
4use crate::runtime::vm;
5use crate::store::{Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter};
6#[cfg(feature = "gc")]
7use crate::{
8    AnyRef, ArrayRef, ArrayRefPre, ArrayType, ExternRef, I31, StructRef, StructRefPre, StructType,
9};
10use crate::{OpaqueRootScope, Val};
11use wasmtime_environ::{ConstExpr, ConstOp, FuncIndex, GlobalIndex};
12#[cfg(feature = "gc")]
13use wasmtime_environ::{VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType};
14
15/// An interpreter for const expressions.
16///
17/// This can be reused across many const expression evaluations to reuse
18/// allocated resources, if any.
19pub struct ConstExprEvaluator {
20    stack: Vec<Val>,
21    simple: Val,
22}
23
24impl Default for ConstExprEvaluator {
25    fn default() -> ConstExprEvaluator {
26        ConstExprEvaluator {
27            stack: Vec::new(),
28            simple: Val::I32(0),
29        }
30    }
31}
32
33/// The context within which a particular const expression is evaluated.
34pub struct ConstEvalContext {
35    pub(crate) instance: InstanceId,
36    pub(crate) asyncness: Asyncness,
37}
38
39impl ConstEvalContext {
40    /// Create a new context.
41    pub fn new(instance: InstanceId, asyncness: Asyncness) -> Self {
42        Self {
43            instance,
44            asyncness,
45        }
46    }
47
48    fn global_get(&mut self, store: &mut StoreOpaque, index: GlobalIndex) -> Result<Val> {
49        let id = store.id();
50        Ok(store
51            .instance_mut(self.instance)
52            .get_exported_global(id, index)
53            ._get(&mut AutoAssertNoGc::new(store)))
54    }
55
56    fn ref_func(&mut self, store: &mut StoreOpaque, index: FuncIndex) -> Result<Val> {
57        let id = store.id();
58        let (instance, registry) = store.instance_and_module_registry_mut(self.instance);
59        // SAFETY: `id` is the correct store-owner of the function being looked
60        // up
61        let func = unsafe { instance.get_exported_func(registry, id, index) };
62        Ok(func.into())
63    }
64
65    #[cfg(feature = "gc")]
66    fn struct_fields_len(&self, store: &mut StoreOpaque, shared_ty: VMSharedTypeIndex) -> usize {
67        let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
68        let fields = struct_ty.fields();
69        fields.len()
70    }
71
72    #[cfg(feature = "gc")]
73    async fn struct_new(
74        &mut self,
75        store: &mut StoreOpaque,
76        limiter: Option<&mut StoreResourceLimiter<'_>>,
77        shared_ty: VMSharedTypeIndex,
78        fields: &[Val],
79    ) -> Result<Val> {
80        let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
81        let allocator = StructRefPre::_new(store, struct_ty);
82        let struct_ref =
83            StructRef::_new_async(store, limiter, &allocator, &fields, self.asyncness).await?;
84        Ok(Val::AnyRef(Some(struct_ref.into())))
85    }
86
87    #[cfg(feature = "gc")]
88    async fn struct_new_default(
89        &mut self,
90        store: &mut StoreOpaque,
91        limiter: Option<&mut StoreResourceLimiter<'_>>,
92        shared_ty: VMSharedTypeIndex,
93    ) -> Result<Val> {
94        let module = store
95            .instance(self.instance)
96            .runtime_module()
97            .expect("should never be allocating a struct type defined in a dummy module");
98
99        let borrowed = module
100            .engine()
101            .signatures()
102            .borrow(shared_ty)
103            .expect("should have a registered type for struct");
104        let WasmSubType {
105            composite_type:
106                WasmCompositeType {
107                    shared: false,
108                    inner: WasmCompositeInnerType::Struct(struct_ty),
109                },
110            ..
111        } = &*borrowed
112        else {
113            unreachable!("registered type should be a struct");
114        };
115
116        let fields = struct_ty
117            .fields
118            .iter()
119            .map(|ty| match &ty.element_type {
120                wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
121                    Val::I32(0)
122                }
123                wasmtime_environ::WasmStorageType::Val(v) => match v {
124                    wasmtime_environ::WasmValType::I32 => Val::I32(0),
125                    wasmtime_environ::WasmValType::I64 => Val::I64(0),
126                    wasmtime_environ::WasmValType::F32 => Val::F32(0.0f32.to_bits()),
127                    wasmtime_environ::WasmValType::F64 => Val::F64(0.0f64.to_bits()),
128                    wasmtime_environ::WasmValType::V128 => Val::V128(0u128.into()),
129                    wasmtime_environ::WasmValType::Ref(r) => {
130                        assert!(r.nullable);
131                        Val::null_top(r.heap_type.top())
132                    }
133                },
134            })
135            .collect::<smallvec::SmallVec<[_; 8]>>();
136
137        self.struct_new(store, limiter, shared_ty, &fields).await
138    }
139}
140
141impl ConstExprEvaluator {
142    /// Same as [`Self::eval`] except that this is specifically intended for
143    /// integral constant expression.
144    ///
145    /// # Panics
146    ///
147    /// Panics if `ConstExpr` contains GC ops (e.g. it's not for an integral
148    /// type).
149    pub fn eval_int(
150        &mut self,
151        store: &mut StoreOpaque,
152        context: &mut ConstEvalContext,
153        expr: &ConstExpr,
154    ) -> Result<&Val> {
155        // Try to evaluate a simple expression first before doing the more
156        // complicated eval loop below.
157        if self.try_simple(expr).is_some() {
158            return Ok(&self.simple);
159        }
160
161        // Note that `assert_ready` here should be valid as production of an
162        // integer cannot involve GC meaning that async operations aren't used.
163        let mut scope = OpaqueRootScope::new(store);
164        vm::assert_ready(self.eval_loop(&mut scope, None, context, expr))
165    }
166
167    /// Attempts to peek into `expr` to see if it's trivial to evaluate, e.g.
168    /// for `i32.const N`.
169    #[inline]
170    pub fn try_simple(&mut self, expr: &ConstExpr) -> Option<&Val> {
171        match expr.ops() {
172            [ConstOp::I32Const(i)] => Some(self.return_one(Val::I32(*i))),
173            [ConstOp::I64Const(i)] => Some(self.return_one(Val::I64(*i))),
174            [ConstOp::F32Const(f)] => Some(self.return_one(Val::F32(*f))),
175            [ConstOp::F64Const(f)] => Some(self.return_one(Val::F64(*f))),
176            _ => None,
177        }
178    }
179
180    /// Evaluate the given const expression in the given context.
181    ///
182    /// Note that the `store` argument is an `OpaqueRootScope` which is used to
183    /// require that a GC rooting scope external to evaluation of this constant
184    /// is required. Constant expression evaluation may perform GC allocations
185    /// and itself trigger a GC meaning that all references must be rooted,
186    /// hence the external requirement of a rooting scope.
187    ///
188    /// # Panics
189    ///
190    /// This function will panic if `expr` is an invalid constant expression.
191    pub async fn eval(
192        &mut self,
193        store: &mut OpaqueRootScope<&mut StoreOpaque>,
194        limiter: Option<&mut StoreResourceLimiter<'_>>,
195        context: &mut ConstEvalContext,
196        expr: &ConstExpr,
197    ) -> Result<&Val> {
198        // Same structure as `eval_int` above, except using `.await` and with a
199        // slightly different type signature here for this function.
200        if self.try_simple(expr).is_some() {
201            return Ok(&self.simple);
202        }
203        self.eval_loop(store, limiter, context, expr).await
204    }
205
206    #[inline]
207    fn return_one(&mut self, val: Val) -> &Val {
208        self.simple = val;
209        &self.simple
210    }
211
212    #[cold]
213    async fn eval_loop(
214        &mut self,
215        store: &mut OpaqueRootScope<&mut StoreOpaque>,
216        mut limiter: Option<&mut StoreResourceLimiter<'_>>,
217        context: &mut ConstEvalContext,
218        expr: &ConstExpr,
219    ) -> Result<&Val> {
220        log::trace!("evaluating const expr: {expr:?}");
221
222        self.stack.clear();
223
224        // On GC-less builds ensure that this is always considered used an
225        // needed-mutable.
226        let _ = &mut limiter;
227
228        for op in expr.ops() {
229            log::trace!("const-evaluating op: {op:?}");
230            match op {
231                ConstOp::I32Const(i) => self.stack.push(Val::I32(*i)),
232                ConstOp::I64Const(i) => self.stack.push(Val::I64(*i)),
233                ConstOp::F32Const(f) => self.stack.push(Val::F32(*f)),
234                ConstOp::F64Const(f) => self.stack.push(Val::F64(*f)),
235                ConstOp::V128Const(v) => self.stack.push(Val::V128((*v).into())),
236                ConstOp::GlobalGet(g) => self.stack.push(context.global_get(store, *g)?),
237                ConstOp::RefNull(ty) => self.stack.push(Val::null_top(*ty)),
238                ConstOp::RefFunc(f) => self.stack.push(context.ref_func(store, *f)?),
239                #[cfg(feature = "gc")]
240                ConstOp::RefI31 => {
241                    let i = self.pop()?.unwrap_i32();
242                    let i31 = I31::wrapping_i32(i);
243                    let r = AnyRef::_from_i31(&mut AutoAssertNoGc::new(store), i31);
244                    self.stack.push(Val::AnyRef(Some(r)));
245                }
246                #[cfg(not(feature = "gc"))]
247                ConstOp::RefI31 => panic!("should not have validated"),
248                ConstOp::I32Add => {
249                    let b = self.pop()?.unwrap_i32();
250                    let a = self.pop()?.unwrap_i32();
251                    self.stack.push(Val::I32(a.wrapping_add(b)));
252                }
253                ConstOp::I32Sub => {
254                    let b = self.pop()?.unwrap_i32();
255                    let a = self.pop()?.unwrap_i32();
256                    self.stack.push(Val::I32(a.wrapping_sub(b)));
257                }
258                ConstOp::I32Mul => {
259                    let b = self.pop()?.unwrap_i32();
260                    let a = self.pop()?.unwrap_i32();
261                    self.stack.push(Val::I32(a.wrapping_mul(b)));
262                }
263                ConstOp::I64Add => {
264                    let b = self.pop()?.unwrap_i64();
265                    let a = self.pop()?.unwrap_i64();
266                    self.stack.push(Val::I64(a.wrapping_add(b)));
267                }
268                ConstOp::I64Sub => {
269                    let b = self.pop()?.unwrap_i64();
270                    let a = self.pop()?.unwrap_i64();
271                    self.stack.push(Val::I64(a.wrapping_sub(b)));
272                }
273                ConstOp::I64Mul => {
274                    let b = self.pop()?.unwrap_i64();
275                    let a = self.pop()?.unwrap_i64();
276                    self.stack.push(Val::I64(a.wrapping_mul(b)));
277                }
278
279                #[cfg(not(feature = "gc"))]
280                ConstOp::StructNew { .. }
281                | ConstOp::StructNewDefault { .. }
282                | ConstOp::ArrayNew { .. }
283                | ConstOp::ArrayNewDefault { .. }
284                | ConstOp::ArrayNewFixed { .. }
285                | ConstOp::ExternConvertAny
286                | ConstOp::AnyConvertExtern => {
287                    bail!(
288                        "const expr evaluation error: struct operations are not \
289                         supported without the `gc` feature"
290                    )
291                }
292
293                #[cfg(feature = "gc")]
294                ConstOp::StructNew { struct_type_index } => {
295                    let interned_type_index = store.instance(context.instance).env_module().types
296                        [*struct_type_index]
297                        .unwrap_engine_type_index();
298                    let len = context.struct_fields_len(store, interned_type_index);
299
300                    if self.stack.len() < len {
301                        bail!(
302                            "const expr evaluation error: expected at least {len} values on the stack, found {}",
303                            self.stack.len()
304                        )
305                    }
306
307                    let start = self.stack.len() - len;
308                    let s = context
309                        .struct_new(
310                            store,
311                            limiter.as_deref_mut(),
312                            interned_type_index,
313                            &self.stack[start..],
314                        )
315                        .await?;
316                    self.stack.truncate(start);
317                    self.stack.push(s);
318                }
319
320                #[cfg(feature = "gc")]
321                ConstOp::StructNewDefault { struct_type_index } => {
322                    let ty = store.instance(context.instance).env_module().types
323                        [*struct_type_index]
324                        .unwrap_engine_type_index();
325                    self.stack.push(
326                        context
327                            .struct_new_default(store, limiter.as_deref_mut(), ty)
328                            .await?,
329                    );
330                }
331
332                #[cfg(feature = "gc")]
333                ConstOp::ArrayNew { array_type_index } => {
334                    let ty = store.instance(context.instance).env_module().types[*array_type_index]
335                        .unwrap_engine_type_index();
336                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
337
338                    let len = self.pop()?.unwrap_i32().cast_unsigned();
339
340                    let elem = self.pop()?;
341
342                    let pre = ArrayRefPre::_new(store, ty);
343                    let array = ArrayRef::_new_async(
344                        store,
345                        limiter.as_deref_mut(),
346                        &pre,
347                        &elem,
348                        len,
349                        context.asyncness,
350                    )
351                    .await?;
352
353                    self.stack.push(Val::AnyRef(Some(array.into())));
354                }
355
356                #[cfg(feature = "gc")]
357                ConstOp::ArrayNewDefault { array_type_index } => {
358                    let ty = store.instance(context.instance).env_module().types[*array_type_index]
359                        .unwrap_engine_type_index();
360                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
361
362                    let len = self.pop()?.unwrap_i32().cast_unsigned();
363
364                    let elem = Val::default_for_ty(ty.element_type().unpack())
365                        .expect("type should have a default value");
366
367                    let pre = ArrayRefPre::_new(store, ty);
368                    let array = ArrayRef::_new_async(
369                        store,
370                        limiter.as_deref_mut(),
371                        &pre,
372                        &elem,
373                        len,
374                        context.asyncness,
375                    )
376                    .await?;
377
378                    self.stack.push(Val::AnyRef(Some(array.into())));
379                }
380
381                #[cfg(feature = "gc")]
382                ConstOp::ArrayNewFixed {
383                    array_type_index,
384                    array_size,
385                } => {
386                    let ty = store.instance(context.instance).env_module().types[*array_type_index]
387                        .unwrap_engine_type_index();
388                    let ty = ArrayType::from_shared_type_index(store.engine(), ty);
389
390                    let array_size = usize::try_from(*array_size).unwrap();
391                    if self.stack.len() < array_size {
392                        bail!(
393                            "const expr evaluation error: expected at least {array_size} values on the stack, found {}",
394                            self.stack.len()
395                        )
396                    }
397
398                    let start = self.stack.len() - array_size;
399
400                    let elems = self
401                        .stack
402                        .drain(start..)
403                        .collect::<smallvec::SmallVec<[_; 8]>>();
404
405                    let pre = ArrayRefPre::_new(store, ty);
406                    let array = ArrayRef::_new_fixed_async(
407                        store,
408                        limiter.as_deref_mut(),
409                        &pre,
410                        &elems,
411                        context.asyncness,
412                    )
413                    .await?;
414
415                    self.stack.push(Val::AnyRef(Some(array.into())));
416                }
417
418                #[cfg(feature = "gc")]
419                ConstOp::ExternConvertAny => {
420                    let mut store = AutoAssertNoGc::new(store);
421                    let result = match self.pop()?.unwrap_anyref() {
422                        Some(anyref) => Some(ExternRef::_convert_any(&mut store, *anyref)?),
423                        None => None,
424                    };
425                    self.stack.push(Val::ExternRef(result));
426                }
427
428                #[cfg(feature = "gc")]
429                ConstOp::AnyConvertExtern => {
430                    let mut store = AutoAssertNoGc::new(store);
431                    let result = match self.pop()?.unwrap_externref() {
432                        Some(externref) => Some(AnyRef::_convert_extern(&mut store, *externref)?),
433                        None => None,
434                    };
435                    self.stack.push(result.into());
436                }
437            }
438        }
439
440        if self.stack.len() == 1 {
441            log::trace!("const expr evaluated to {:?}", self.stack[0]);
442            Ok(&self.stack[0])
443        } else {
444            bail!(
445                "const expr evaluation error: expected 1 resulting value, found {}",
446                self.stack.len()
447            )
448        }
449    }
450
451    fn pop(&mut self) -> Result<Val> {
452        self.stack.pop().ok_or_else(|| {
453            format_err!(
454                "const expr evaluation error: attempted to pop from an empty \
455                 evaluation stack"
456            )
457        })
458    }
459}