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