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