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