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::{
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)]
22 pub struct ConstExprEvaluator {
23     stack: SmallVec<[ValRaw; 2]>,
24 }
25 
26 /// The context within which a particular const expression is evaluated.
27 pub struct ConstEvalContext {
28     pub(crate) instance: InstanceId,
29 }
30 
31 impl 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                 unsafe { 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 
148 impl 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 = unsafe {
266                         context.struct_new(&mut store, interned_type_index, &self.stack[start..])?
267                     };
268                     self.stack.truncate(start);
269                     self.stack.push(s);
270                 }
271 
272                 #[cfg(feature = "gc")]
273                 ConstOp::StructNewDefault { struct_type_index } => {
274                     let ty = store.instance(context.instance).env_module().types
275                         [*struct_type_index]
276                         .unwrap_engine_type_index();
277                     self.stack.push(context.struct_new_default(&mut store, ty)?);
278                 }
279 
280                 #[cfg(feature = "gc")]
281                 ConstOp::ArrayNew { array_type_index } => {
282                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
283                         .unwrap_engine_type_index();
284                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
285 
286                     let len = self.pop()?.get_i32().unsigned();
287 
288                     let elem = unsafe {
289                         Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack())
290                     };
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| unsafe { 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 }
397