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