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