wasmtime/runtime/vm/
const_expr.rs

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