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