1 //! Evaluating const expressions.
2 
3 use crate::prelude::*;
4 use crate::runtime::vm;
5 use crate::store::{AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter};
6 #[cfg(feature = "gc")]
7 use crate::{
8     AnyRef, ArrayRef, ArrayRefPre, ArrayType, ExternRef, I31, StructRef, StructRefPre, StructType,
9 };
10 use crate::{OpaqueRootScope, Val};
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 pub struct ConstExprEvaluator {
20     stack: Vec<Val>,
21     simple: Val,
22 }
23 
24 impl Default for ConstExprEvaluator {
25     fn default() -> ConstExprEvaluator {
26         ConstExprEvaluator {
27             stack: Vec::new(),
28             simple: Val::I32(0),
29         }
30     }
31 }
32 
33 /// The context within which a particular const expression is evaluated.
34 pub struct ConstEvalContext {
35     pub(crate) instance: InstanceId,
36 }
37 
38 impl ConstEvalContext {
39     /// Create a new context.
40     pub fn new(instance: InstanceId) -> Self {
41         Self { instance }
42     }
43 
44     fn global_get(&mut self, store: &mut StoreOpaque, index: GlobalIndex) -> Result<Val> {
45         let id = store.id();
46         Ok(store
47             .instance_mut(self.instance)
48             .get_exported_global(id, index)
49             ._get(&mut AutoAssertNoGc::new(store)))
50     }
51 
52     fn ref_func(&mut self, store: &mut StoreOpaque, index: FuncIndex) -> Result<Val> {
53         let id = store.id();
54         let (instance, registry) = store.instance_and_module_registry_mut(self.instance);
55         // SAFETY: `id` is the correct store-owner of the function being looked
56         // up
57         let func = unsafe { instance.get_exported_func(registry, id, index) };
58         Ok(func.into())
59     }
60 
61     #[cfg(feature = "gc")]
62     fn struct_fields_len(&self, store: &mut StoreOpaque, shared_ty: VMSharedTypeIndex) -> usize {
63         let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
64         let fields = struct_ty.fields();
65         fields.len()
66     }
67 
68     #[cfg(feature = "gc")]
69     async fn struct_new(
70         &mut self,
71         store: &mut StoreOpaque,
72         limiter: Option<&mut StoreResourceLimiter<'_>>,
73         shared_ty: VMSharedTypeIndex,
74         fields: &[Val],
75     ) -> Result<Val> {
76         let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
77         let allocator = StructRefPre::_new(store, struct_ty);
78         let struct_ref = StructRef::_new_async(store, limiter, &allocator, &fields).await?;
79         Ok(Val::AnyRef(Some(struct_ref.into())))
80     }
81 
82     #[cfg(feature = "gc")]
83     async fn struct_new_default(
84         &mut self,
85         store: &mut StoreOpaque,
86         limiter: Option<&mut StoreResourceLimiter<'_>>,
87         shared_ty: VMSharedTypeIndex,
88     ) -> Result<Val> {
89         let module = store
90             .instance(self.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                     Val::I32(0)
117                 }
118                 wasmtime_environ::WasmStorageType::Val(v) => match v {
119                     wasmtime_environ::WasmValType::I32 => Val::I32(0),
120                     wasmtime_environ::WasmValType::I64 => Val::I64(0),
121                     wasmtime_environ::WasmValType::F32 => Val::F32(0.0f32.to_bits()),
122                     wasmtime_environ::WasmValType::F64 => Val::F64(0.0f64.to_bits()),
123                     wasmtime_environ::WasmValType::V128 => Val::V128(0u128.into()),
124                     wasmtime_environ::WasmValType::Ref(r) => {
125                         assert!(r.nullable);
126                         Val::null_top(r.heap_type.top())
127                     }
128                 },
129             })
130             .collect::<smallvec::SmallVec<[_; 8]>>();
131 
132         self.struct_new(store, limiter, shared_ty, &fields).await
133     }
134 }
135 
136 impl ConstExprEvaluator {
137     /// Same as [`Self::eval`] except that this is specifically intended for
138     /// integral constant expression.
139     ///
140     /// # Panics
141     ///
142     /// Panics if `ConstExpr` contains GC ops (e.g. it's not for an integral
143     /// type).
144     pub fn eval_int(
145         &mut self,
146         store: &mut StoreOpaque,
147         context: &mut ConstEvalContext,
148         expr: &ConstExpr,
149     ) -> Result<&Val> {
150         // Try to evaluate a simple expression first before doing the more
151         // complicated eval loop below.
152         if self.try_simple(expr).is_some() {
153             return Ok(&self.simple);
154         }
155 
156         // Note that `assert_ready` here should be valid as production of an
157         // integer cannot involve GC meaning that async operations aren't used.
158         let mut scope = OpaqueRootScope::new(store);
159         vm::assert_ready(self.eval_loop(&mut scope, None, context, expr))
160     }
161 
162     /// Attempts to peek into `expr` to see if it's trivial to evaluate, e.g.
163     /// for `i32.const N`.
164     #[inline]
165     pub fn try_simple(&mut self, expr: &ConstExpr) -> Option<&Val> {
166         match expr.ops() {
167             [ConstOp::I32Const(i)] => Some(self.return_one(Val::I32(*i))),
168             [ConstOp::I64Const(i)] => Some(self.return_one(Val::I64(*i))),
169             [ConstOp::F32Const(f)] => Some(self.return_one(Val::F32(*f))),
170             [ConstOp::F64Const(f)] => Some(self.return_one(Val::F64(*f))),
171             _ => None,
172         }
173     }
174 
175     /// Evaluate the given const expression in the given context.
176     ///
177     /// Note that the `store` argument is an `OpaqueRootScope` which is used to
178     /// require that a GC rooting scope external to evaluation of this constant
179     /// is required. Constant expression evaluation may perform GC allocations
180     /// and itself trigger a GC meaning that all references must be rooted,
181     /// hence the external requirement of a rooting scope.
182     ///
183     /// # Panics
184     ///
185     /// This function will panic if `expr` is an invalid constant expression.
186     pub async fn eval(
187         &mut self,
188         store: &mut OpaqueRootScope<&mut StoreOpaque>,
189         limiter: Option<&mut StoreResourceLimiter<'_>>,
190         context: &mut ConstEvalContext,
191         expr: &ConstExpr,
192     ) -> Result<&Val> {
193         // Same structure as `eval_int` above, except using `.await` and with a
194         // slightly different type signature here for this function.
195         if self.try_simple(expr).is_some() {
196             return Ok(&self.simple);
197         }
198         self.eval_loop(store, limiter, context, expr).await
199     }
200 
201     #[inline]
202     fn return_one(&mut self, val: Val) -> &Val {
203         self.simple = val;
204         &self.simple
205         // self.stack.clear();
206         // self.stack.push(val);
207         // &self.stack[0]
208     }
209 
210     #[cold]
211     async fn eval_loop(
212         &mut self,
213         store: &mut OpaqueRootScope<&mut StoreOpaque>,
214         mut limiter: Option<&mut StoreResourceLimiter<'_>>,
215         context: &mut ConstEvalContext,
216         expr: &ConstExpr,
217     ) -> Result<&Val> {
218         log::trace!("evaluating const expr: {expr:?}");
219 
220         self.stack.clear();
221 
222         // On GC-less builds ensure that this is always considered used an
223         // needed-mutable.
224         let _ = &mut limiter;
225 
226         for op in expr.ops() {
227             log::trace!("const-evaluating op: {op:?}");
228             match op {
229                 ConstOp::I32Const(i) => self.stack.push(Val::I32(*i)),
230                 ConstOp::I64Const(i) => self.stack.push(Val::I64(*i)),
231                 ConstOp::F32Const(f) => self.stack.push(Val::F32(*f)),
232                 ConstOp::F64Const(f) => self.stack.push(Val::F64(*f)),
233                 ConstOp::V128Const(v) => self.stack.push(Val::V128((*v).into())),
234                 ConstOp::GlobalGet(g) => self.stack.push(context.global_get(store, *g)?),
235                 ConstOp::RefNull(ty) => self.stack.push(Val::null_top(*ty)),
236                 ConstOp::RefFunc(f) => self.stack.push(context.ref_func(store, *f)?),
237                 #[cfg(feature = "gc")]
238                 ConstOp::RefI31 => {
239                     let i = self.pop()?.unwrap_i32();
240                     let i31 = I31::wrapping_i32(i);
241                     let r = AnyRef::_from_i31(&mut AutoAssertNoGc::new(store), i31);
242                     self.stack.push(Val::AnyRef(Some(r)));
243                 }
244                 #[cfg(not(feature = "gc"))]
245                 ConstOp::RefI31 => panic!("should not have validated"),
246                 ConstOp::I32Add => {
247                     let b = self.pop()?.unwrap_i32();
248                     let a = self.pop()?.unwrap_i32();
249                     self.stack.push(Val::I32(a.wrapping_add(b)));
250                 }
251                 ConstOp::I32Sub => {
252                     let b = self.pop()?.unwrap_i32();
253                     let a = self.pop()?.unwrap_i32();
254                     self.stack.push(Val::I32(a.wrapping_sub(b)));
255                 }
256                 ConstOp::I32Mul => {
257                     let b = self.pop()?.unwrap_i32();
258                     let a = self.pop()?.unwrap_i32();
259                     self.stack.push(Val::I32(a.wrapping_mul(b)));
260                 }
261                 ConstOp::I64Add => {
262                     let b = self.pop()?.unwrap_i64();
263                     let a = self.pop()?.unwrap_i64();
264                     self.stack.push(Val::I64(a.wrapping_add(b)));
265                 }
266                 ConstOp::I64Sub => {
267                     let b = self.pop()?.unwrap_i64();
268                     let a = self.pop()?.unwrap_i64();
269                     self.stack.push(Val::I64(a.wrapping_sub(b)));
270                 }
271                 ConstOp::I64Mul => {
272                     let b = self.pop()?.unwrap_i64();
273                     let a = self.pop()?.unwrap_i64();
274                     self.stack.push(Val::I64(a.wrapping_mul(b)));
275                 }
276 
277                 #[cfg(not(feature = "gc"))]
278                 ConstOp::StructNew { .. }
279                 | ConstOp::StructNewDefault { .. }
280                 | ConstOp::ArrayNew { .. }
281                 | ConstOp::ArrayNewDefault { .. }
282                 | ConstOp::ArrayNewFixed { .. }
283                 | ConstOp::ExternConvertAny
284                 | ConstOp::AnyConvertExtern => {
285                     bail!(
286                         "const expr evaluation error: struct operations are not \
287                          supported without the `gc` feature"
288                     )
289                 }
290 
291                 #[cfg(feature = "gc")]
292                 ConstOp::StructNew { struct_type_index } => {
293                     let interned_type_index = store.instance(context.instance).env_module().types
294                         [*struct_type_index]
295                         .unwrap_engine_type_index();
296                     let len = context.struct_fields_len(store, interned_type_index);
297 
298                     if self.stack.len() < len {
299                         bail!(
300                             "const expr evaluation error: expected at least {len} values on the stack, found {}",
301                             self.stack.len()
302                         )
303                     }
304 
305                     let start = self.stack.len() - len;
306                     let s = context
307                         .struct_new(
308                             store,
309                             limiter.as_deref_mut(),
310                             interned_type_index,
311                             &self.stack[start..],
312                         )
313                         .await?;
314                     self.stack.truncate(start);
315                     self.stack.push(s);
316                 }
317 
318                 #[cfg(feature = "gc")]
319                 ConstOp::StructNewDefault { struct_type_index } => {
320                     let ty = store.instance(context.instance).env_module().types
321                         [*struct_type_index]
322                         .unwrap_engine_type_index();
323                     self.stack.push(
324                         context
325                             .struct_new_default(store, limiter.as_deref_mut(), ty)
326                             .await?,
327                     );
328                 }
329 
330                 #[cfg(feature = "gc")]
331                 ConstOp::ArrayNew { array_type_index } => {
332                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
333                         .unwrap_engine_type_index();
334                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
335 
336                     let len = self.pop()?.unwrap_i32().cast_unsigned();
337 
338                     let elem = self.pop()?;
339 
340                     let pre = ArrayRefPre::_new(store, ty);
341                     let array =
342                         ArrayRef::_new_async(store, limiter.as_deref_mut(), &pre, &elem, len)
343                             .await?;
344 
345                     self.stack.push(Val::AnyRef(Some(array.into())));
346                 }
347 
348                 #[cfg(feature = "gc")]
349                 ConstOp::ArrayNewDefault { array_type_index } => {
350                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
351                         .unwrap_engine_type_index();
352                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
353 
354                     let len = self.pop()?.unwrap_i32().cast_unsigned();
355 
356                     let elem = Val::default_for_ty(ty.element_type().unpack())
357                         .expect("type should have a default value");
358 
359                     let pre = ArrayRefPre::_new(store, ty);
360                     let array =
361                         ArrayRef::_new_async(store, limiter.as_deref_mut(), &pre, &elem, len)
362                             .await?;
363 
364                     self.stack.push(Val::AnyRef(Some(array.into())));
365                 }
366 
367                 #[cfg(feature = "gc")]
368                 ConstOp::ArrayNewFixed {
369                     array_type_index,
370                     array_size,
371                 } => {
372                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
373                         .unwrap_engine_type_index();
374                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
375 
376                     let array_size = usize::try_from(*array_size).unwrap();
377                     if self.stack.len() < array_size {
378                         bail!(
379                             "const expr evaluation error: expected at least {array_size} values on the stack, found {}",
380                             self.stack.len()
381                         )
382                     }
383 
384                     let start = self.stack.len() - array_size;
385 
386                     let elems = self
387                         .stack
388                         .drain(start..)
389                         .collect::<smallvec::SmallVec<[_; 8]>>();
390 
391                     let pre = ArrayRefPre::_new(store, ty);
392                     let array =
393                         ArrayRef::_new_fixed_async(store, limiter.as_deref_mut(), &pre, &elems)
394                             .await?;
395 
396                     self.stack.push(Val::AnyRef(Some(array.into())));
397                 }
398 
399                 #[cfg(feature = "gc")]
400                 ConstOp::ExternConvertAny => {
401                     let mut store = AutoAssertNoGc::new(store);
402                     let result = match self.pop()?.unwrap_anyref() {
403                         Some(anyref) => Some(ExternRef::_convert_any(&mut store, *anyref)?),
404                         None => None,
405                     };
406                     self.stack.push(Val::ExternRef(result));
407                 }
408 
409                 #[cfg(feature = "gc")]
410                 ConstOp::AnyConvertExtern => {
411                     let mut store = AutoAssertNoGc::new(store);
412                     let result = match self.pop()?.unwrap_externref() {
413                         Some(externref) => Some(AnyRef::_convert_extern(&mut store, *externref)?),
414                         None => None,
415                     };
416                     self.stack.push(result.into());
417                 }
418             }
419         }
420 
421         if self.stack.len() == 1 {
422             log::trace!("const expr evaluated to {:?}", self.stack[0]);
423             Ok(&self.stack[0])
424         } else {
425             bail!(
426                 "const expr evaluation error: expected 1 resulting value, found {}",
427                 self.stack.len()
428             )
429         }
430     }
431 
432     fn pop(&mut self) -> Result<Val> {
433         self.stack.pop().ok_or_else(|| {
434             format_err!(
435                 "const expr evaluation error: attempted to pop from an empty \
436                  evaluation stack"
437             )
438         })
439     }
440 }
441