wasmtime/runtime/vm/
const_expr.rs

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