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.instance.defined_or_imported_global_ptr(index).as_ref();
37             global.to_val_raw(store, self.instance.env_module().globals[index].wasm_ty)
38         }
39     }
40 
41     fn ref_func(&mut self, index: FuncIndex) -> Result<ValRaw> {
42         Ok(ValRaw::funcref(
43             self.instance.get_func_ref(index).unwrap().as_ptr().cast(),
44         ))
45     }
46 
47     #[cfg(feature = "gc")]
48     fn struct_fields_len(&self, struct_type_index: ModuleInternedTypeIndex) -> usize {
49         let module = self
50             .instance
51             .runtime_module()
52             .expect("should never be allocating a struct type defined in a dummy module");
53 
54         let struct_ty = match &module.types()[struct_type_index].composite_type.inner {
55             WasmCompositeInnerType::Struct(s) => s,
56             _ => unreachable!(),
57         };
58 
59         struct_ty.fields.len()
60     }
61 
62     /// Safety: field values must be of the correct types.
63     #[cfg(feature = "gc")]
64     unsafe fn struct_new(
65         &mut self,
66         store: &mut AutoAssertNoGc<'_>,
67         struct_type_index: ModuleInternedTypeIndex,
68         fields: &[ValRaw],
69     ) -> Result<ValRaw> {
70         let module = self
71             .instance
72             .runtime_module()
73             .expect("should never be allocating a struct type defined in a dummy module");
74         let shared_ty = module
75             .signatures()
76             .shared_type(struct_type_index)
77             .expect("should have an engine type for module type");
78 
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 = StructRef::_new(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         struct_type_index: ModuleInternedTypeIndex,
100     ) -> Result<ValRaw> {
101         let module = self
102             .instance
103             .runtime_module()
104             .expect("should never be allocating a struct type defined in a dummy module");
105 
106         let shared_ty = module
107             .signatures()
108             .shared_type(struct_type_index)
109             .expect("should have an engine type for module type");
110 
111         let borrowed = module
112             .engine()
113             .signatures()
114             .borrow(shared_ty)
115             .expect("should have a registered type for struct");
116         let WasmSubType {
117             composite_type:
118                 WasmCompositeType {
119                     shared: false,
120                     inner: WasmCompositeInnerType::Struct(struct_ty),
121                 },
122             ..
123         } = &*borrowed
124         else {
125             unreachable!("registered type should be a struct");
126         };
127 
128         let fields = struct_ty
129             .fields
130             .iter()
131             .map(|ty| match &ty.element_type {
132                 wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
133                     ValRaw::i32(0)
134                 }
135                 wasmtime_environ::WasmStorageType::Val(v) => match v {
136                     wasmtime_environ::WasmValType::I32 => ValRaw::i32(0),
137                     wasmtime_environ::WasmValType::I64 => ValRaw::i64(0),
138                     wasmtime_environ::WasmValType::F32 => ValRaw::f32(0.0f32.to_bits()),
139                     wasmtime_environ::WasmValType::F64 => ValRaw::f64(0.0f64.to_bits()),
140                     wasmtime_environ::WasmValType::V128 => ValRaw::v128(0),
141                     wasmtime_environ::WasmValType::Ref(r) => {
142                         assert!(r.nullable);
143                         ValRaw::null()
144                     }
145                 },
146             })
147             .collect::<SmallVec<[_; 8]>>();
148 
149         unsafe { self.struct_new(store, struct_type_index, &fields) }
150     }
151 }
152 
153 impl ConstExprEvaluator {
154     /// Evaluate the given const expression in the given context.
155     ///
156     /// # Unsafety
157     ///
158     /// The given const expression must be valid within the given context,
159     /// e.g. the const expression must be well-typed and the context must return
160     /// global values of the expected types. This evaluator operates directly on
161     /// untyped `ValRaw`s and does not and cannot check that its operands are of
162     /// the correct type.
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             match op {
190                 ConstOp::I32Const(i) => self.stack.push(ValRaw::i32(*i)),
191                 ConstOp::I64Const(i) => self.stack.push(ValRaw::i64(*i)),
192                 ConstOp::F32Const(f) => self.stack.push(ValRaw::f32(*f)),
193                 ConstOp::F64Const(f) => self.stack.push(ValRaw::f64(*f)),
194                 ConstOp::V128Const(v) => self.stack.push(ValRaw::v128(*v)),
195                 ConstOp::GlobalGet(g) => self.stack.push(context.global_get(&mut store, *g)?),
196                 ConstOp::RefNull => self.stack.push(ValRaw::null()),
197                 ConstOp::RefFunc(f) => self.stack.push(context.ref_func(*f)?),
198                 ConstOp::RefI31 => {
199                     let i = self.pop()?.get_i32();
200                     let i31 = I31::wrapping_i32(i);
201                     let raw = VMGcRef::from_i31(i31).as_raw_u32();
202                     self.stack.push(ValRaw::anyref(raw));
203                 }
204                 ConstOp::I32Add => {
205                     let b = self.pop()?.get_i32();
206                     let a = self.pop()?.get_i32();
207                     self.stack.push(ValRaw::i32(a.wrapping_add(b)));
208                 }
209                 ConstOp::I32Sub => {
210                     let b = self.pop()?.get_i32();
211                     let a = self.pop()?.get_i32();
212                     self.stack.push(ValRaw::i32(a.wrapping_sub(b)));
213                 }
214                 ConstOp::I32Mul => {
215                     let b = self.pop()?.get_i32();
216                     let a = self.pop()?.get_i32();
217                     self.stack.push(ValRaw::i32(a.wrapping_mul(b)));
218                 }
219                 ConstOp::I64Add => {
220                     let b = self.pop()?.get_i64();
221                     let a = self.pop()?.get_i64();
222                     self.stack.push(ValRaw::i64(a.wrapping_add(b)));
223                 }
224                 ConstOp::I64Sub => {
225                     let b = self.pop()?.get_i64();
226                     let a = self.pop()?.get_i64();
227                     self.stack.push(ValRaw::i64(a.wrapping_sub(b)));
228                 }
229                 ConstOp::I64Mul => {
230                     let b = self.pop()?.get_i64();
231                     let a = self.pop()?.get_i64();
232                     self.stack.push(ValRaw::i64(a.wrapping_mul(b)));
233                 }
234 
235                 #[cfg(not(feature = "gc"))]
236                 ConstOp::StructNew { .. }
237                 | ConstOp::StructNewDefault { .. }
238                 | ConstOp::ArrayNew { .. }
239                 | ConstOp::ArrayNewDefault { .. }
240                 | ConstOp::ArrayNewFixed { .. } => {
241                     bail!(
242                         "const expr evaluation error: struct operations are not \
243                          supported without the `gc` feature"
244                     )
245                 }
246 
247                 #[cfg(feature = "gc")]
248                 ConstOp::StructNew { struct_type_index } => {
249                     let interned_type_index =
250                         context.instance.env_module().types[*struct_type_index];
251                     let len = context.struct_fields_len(interned_type_index);
252 
253                     if self.stack.len() < len {
254                         bail!(
255                             "const expr evaluation error: expected at least {len} values on the stack, found {}",
256                             self.stack.len()
257                         )
258                     }
259 
260                     let start = self.stack.len() - len;
261                     let s = context.struct_new(
262                         &mut store,
263                         interned_type_index,
264                         &self.stack[start..],
265                     )?;
266                     self.stack.truncate(start);
267                     self.stack.push(s);
268                 }
269 
270                 #[cfg(feature = "gc")]
271                 ConstOp::StructNewDefault { struct_type_index } => {
272                     let interned_type_index =
273                         context.instance.env_module().types[*struct_type_index];
274                     self.stack
275                         .push(context.struct_new_default(&mut store, interned_type_index)?);
276                 }
277 
278                 #[cfg(feature = "gc")]
279                 ConstOp::ArrayNew { array_type_index } => {
280                     let interned_type_index =
281                         context.instance.env_module().types[*array_type_index];
282                     let module = context.instance.runtime_module().expect(
283                         "should never be allocating a struct type defined in a dummy module",
284                     );
285                     let shared_ty = module
286                         .signatures()
287                         .shared_type(interned_type_index)
288                         .expect("should have an engine type for module type");
289                     let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
290 
291                     #[allow(clippy::cast_sign_loss)]
292                     let len = self.pop()?.get_i32() as u32;
293 
294                     let elem = Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack());
295 
296                     let pre = ArrayRefPre::_new(&mut store, ty);
297                     let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
298 
299                     self.stack
300                         .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
301                 }
302 
303                 #[cfg(feature = "gc")]
304                 ConstOp::ArrayNewDefault { array_type_index } => {
305                     let interned_type_index =
306                         context.instance.env_module().types[*array_type_index];
307                     let module = context.instance.runtime_module().expect(
308                         "should never be allocating a struct type defined in a dummy module",
309                     );
310                     let shared_ty = module
311                         .signatures()
312                         .shared_type(interned_type_index)
313                         .expect("should have an engine type for module type");
314                     let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
315 
316                     #[allow(clippy::cast_sign_loss)]
317                     let len = self.pop()?.get_i32() as u32;
318 
319                     let elem = Val::default_for_ty(ty.element_type().unpack())
320                         .expect("type should have a default value");
321 
322                     let pre = ArrayRefPre::_new(&mut store, ty);
323                     let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
324 
325                     self.stack
326                         .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
327                 }
328 
329                 #[cfg(feature = "gc")]
330                 ConstOp::ArrayNewFixed {
331                     array_type_index,
332                     array_size,
333                 } => {
334                     let interned_type_index =
335                         context.instance.env_module().types[*array_type_index];
336                     let module = context.instance.runtime_module().expect(
337                         "should never be allocating a struct type defined in a dummy module",
338                     );
339                     let shared_ty = module
340                         .signatures()
341                         .shared_type(interned_type_index)
342                         .expect("should have an engine type for module type");
343                     let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
344 
345                     let array_size = usize::try_from(*array_size).unwrap();
346                     if self.stack.len() < array_size {
347                         bail!(
348                             "const expr evaluation error: expected at least {array_size} values on the stack, found {}",
349                             self.stack.len()
350                         )
351                     }
352 
353                     let start = self.stack.len() - array_size;
354 
355                     let elem_ty = ty.element_type();
356                     let elem_ty = elem_ty.unpack();
357 
358                     let elems = self
359                         .stack
360                         .drain(start..)
361                         .map(|raw| Val::_from_raw(&mut store, raw, elem_ty))
362                         .collect::<SmallVec<[_; 8]>>();
363 
364                     let pre = ArrayRefPre::_new(&mut store, ty);
365                     let array = ArrayRef::_new_fixed(&mut store, &pre, &elems)?;
366 
367                     self.stack
368                         .push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
369                 }
370             }
371         }
372 
373         if self.stack.len() == 1 {
374             log::trace!("const expr evaluated to {:?}", self.stack[0]);
375             Ok(self.stack[0])
376         } else {
377             bail!(
378                 "const expr evaluation error: expected 1 resulting value, found {}",
379                 self.stack.len()
380             )
381         }
382     }
383 
384     fn pop(&mut self) -> Result<ValRaw> {
385         self.stack.pop().ok_or_else(|| {
386             anyhow!(
387                 "const expr evaluation error: attempted to pop from an empty \
388                  evaluation stack"
389             )
390         })
391     }
392 }
393