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