1 //! Evaluating const expressions.
2 
3 use crate::prelude::*;
4 use crate::store::{AutoAssertNoGc, InstanceId, StoreOpaque};
5 #[cfg(feature = "gc")]
6 use crate::{
7     AnyRef, ArrayRef, ArrayRefPre, ArrayType, ExternRef, I31, StructRef, StructRefPre, StructType,
8 };
9 use crate::{OpaqueRootScope, Val};
10 use wasmtime_environ::{ConstExpr, ConstOp, FuncIndex, GlobalIndex};
11 #[cfg(feature = "gc")]
12 use wasmtime_environ::{VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType};
13 
14 /// An interpreter for const expressions.
15 ///
16 /// This can be reused across many const expression evaluations to reuse
17 /// allocated resources, if any.
18 #[derive(Default)]
19 pub struct ConstExprEvaluator {
20     stack: Vec<Val>,
21 }
22 
23 /// The context within which a particular const expression is evaluated.
24 pub struct ConstEvalContext {
25     pub(crate) instance: InstanceId,
26 }
27 
28 impl ConstEvalContext {
29     /// Create a new context.
30     pub fn new(instance: InstanceId) -> Self {
31         Self { instance }
32     }
33 
34     fn global_get(&mut self, store: &mut StoreOpaque, index: GlobalIndex) -> Result<Val> {
35         let id = store.id();
36         Ok(store
37             .instance_mut(self.instance)
38             .get_exported_global(id, index)
39             ._get(&mut AutoAssertNoGc::new(store)))
40     }
41 
42     fn ref_func(&mut self, store: &mut StoreOpaque, index: FuncIndex) -> Result<Val> {
43         let id = store.id();
44         // SAFETY: `id` is the correct store-owner of the function being looked
45         // up
46         let func = unsafe {
47             store
48                 .instance_mut(self.instance)
49                 .get_exported_func(id, index)
50         };
51         Ok(func.into())
52     }
53 
54     #[cfg(feature = "gc")]
55     fn struct_fields_len(&self, store: &mut StoreOpaque, shared_ty: VMSharedTypeIndex) -> usize {
56         let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
57         let fields = struct_ty.fields();
58         fields.len()
59     }
60 
61     /// Safety: field values must be of the correct types.
62     #[cfg(feature = "gc")]
63     unsafe fn struct_new(
64         &mut self,
65         store: &mut StoreOpaque,
66         shared_ty: VMSharedTypeIndex,
67         fields: &[Val],
68     ) -> Result<Val> {
69         let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
70         let allocator = StructRefPre::_new(store, struct_ty);
71         let struct_ref = unsafe { StructRef::new_maybe_async(store, &allocator, &fields)? };
72         Ok(Val::AnyRef(Some(struct_ref.into())))
73     }
74 
75     #[cfg(feature = "gc")]
76     fn struct_new_default(
77         &mut self,
78         store: &mut StoreOpaque,
79         shared_ty: VMSharedTypeIndex,
80     ) -> Result<Val> {
81         let module = store
82             .instance(self.instance)
83             .runtime_module()
84             .expect("should never be allocating a struct type defined in a dummy module");
85 
86         let borrowed = module
87             .engine()
88             .signatures()
89             .borrow(shared_ty)
90             .expect("should have a registered type for struct");
91         let WasmSubType {
92             composite_type:
93                 WasmCompositeType {
94                     shared: false,
95                     inner: WasmCompositeInnerType::Struct(struct_ty),
96                 },
97             ..
98         } = &*borrowed
99         else {
100             unreachable!("registered type should be a struct");
101         };
102 
103         let fields = struct_ty
104             .fields
105             .iter()
106             .map(|ty| match &ty.element_type {
107                 wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
108                     Val::I32(0)
109                 }
110                 wasmtime_environ::WasmStorageType::Val(v) => match v {
111                     wasmtime_environ::WasmValType::I32 => Val::I32(0),
112                     wasmtime_environ::WasmValType::I64 => Val::I64(0),
113                     wasmtime_environ::WasmValType::F32 => Val::F32(0.0f32.to_bits()),
114                     wasmtime_environ::WasmValType::F64 => Val::F64(0.0f64.to_bits()),
115                     wasmtime_environ::WasmValType::V128 => Val::V128(0u128.into()),
116                     wasmtime_environ::WasmValType::Ref(r) => {
117                         assert!(r.nullable);
118                         Val::null_top(r.heap_type.top())
119                     }
120                 },
121             })
122             .collect::<smallvec::SmallVec<[_; 8]>>();
123 
124         unsafe { self.struct_new(store, shared_ty, &fields) }
125     }
126 }
127 
128 impl ConstExprEvaluator {
129     /// Evaluate the given const expression in the given context.
130     ///
131     ///
132     /// Note that the `store` argument is an `OpaqueRootScope` which is used to
133     /// require that a GC rooting scope external to evaluation of this constant
134     /// is required. Constant expression evaluation may perform GC allocations
135     /// and itself trigger a GC meaning that all references must be rooted,
136     /// hence the external requirement of a rooting scope.
137     ///
138     /// # Unsafety
139     ///
140     /// When async is enabled, this may only be executed on a fiber stack.
141     ///
142     /// The given const expression must be valid within the given context,
143     /// e.g. the const expression must be well-typed and the context must return
144     /// global values of the expected types. This evaluator operates directly on
145     /// untyped `ValRaw`s and does not and cannot check that its operands are of
146     /// the correct type.
147     ///
148     /// If given async store, then this must be called from on an async fiber
149     /// stack.
150     pub unsafe fn eval(
151         &mut self,
152         store: &mut OpaqueRootScope<&mut StoreOpaque>,
153         context: &mut ConstEvalContext,
154         expr: &ConstExpr,
155     ) -> Result<&Val> {
156         match expr.ops() {
157             // Skip the interpreter loop for some known constant patterns that
158             // are easy to calculate the result of.
159             [ConstOp::I32Const(i)] => self.return_one(Val::I32(*i)),
160             [ConstOp::I64Const(i)] => self.return_one(Val::I64(*i)),
161             [ConstOp::F32Const(f)] => self.return_one(Val::F32(*f)),
162             [ConstOp::F64Const(f)] => self.return_one(Val::F64(*f)),
163 
164             // Fall back to the interpreter loop for all other expressions.
165             //
166             // SAFETY: this function has the same contract as `eval_loop`.
167             other => unsafe { self.eval_loop(store, context, other) },
168         }
169     }
170 
171     fn return_one(&mut self, val: Val) -> Result<&Val> {
172         self.stack.clear();
173         self.stack.push(val);
174         Ok(&self.stack[0])
175     }
176 
177     /// # Safety
178     ///
179     /// See [`Self::eval`].
180     unsafe fn eval_loop(
181         &mut self,
182         store: &mut OpaqueRootScope<&mut StoreOpaque>,
183         context: &mut ConstEvalContext,
184         ops: &[ConstOp],
185     ) -> Result<&Val> {
186         log::trace!("evaluating const expr: {ops:?}");
187 
188         self.stack.clear();
189 
190         for op in ops {
191             log::trace!("const-evaluating op: {op:?}");
192             match op {
193                 ConstOp::I32Const(i) => self.stack.push(Val::I32(*i)),
194                 ConstOp::I64Const(i) => self.stack.push(Val::I64(*i)),
195                 ConstOp::F32Const(f) => self.stack.push(Val::F32(*f)),
196                 ConstOp::F64Const(f) => self.stack.push(Val::F64(*f)),
197                 ConstOp::V128Const(v) => self.stack.push(Val::V128((*v).into())),
198                 ConstOp::GlobalGet(g) => self.stack.push(context.global_get(store, *g)?),
199                 ConstOp::RefNull(ty) => self.stack.push(Val::null_top(*ty)),
200                 ConstOp::RefFunc(f) => self.stack.push(context.ref_func(store, *f)?),
201                 #[cfg(feature = "gc")]
202                 ConstOp::RefI31 => {
203                     let i = self.pop()?.unwrap_i32();
204                     let i31 = I31::wrapping_i32(i);
205                     let r = AnyRef::_from_i31(&mut AutoAssertNoGc::new(store), i31);
206                     self.stack.push(Val::AnyRef(Some(r)));
207                 }
208                 #[cfg(not(feature = "gc"))]
209                 ConstOp::RefI31 => panic!("should not have validated"),
210                 ConstOp::I32Add => {
211                     let b = self.pop()?.unwrap_i32();
212                     let a = self.pop()?.unwrap_i32();
213                     self.stack.push(Val::I32(a.wrapping_add(b)));
214                 }
215                 ConstOp::I32Sub => {
216                     let b = self.pop()?.unwrap_i32();
217                     let a = self.pop()?.unwrap_i32();
218                     self.stack.push(Val::I32(a.wrapping_sub(b)));
219                 }
220                 ConstOp::I32Mul => {
221                     let b = self.pop()?.unwrap_i32();
222                     let a = self.pop()?.unwrap_i32();
223                     self.stack.push(Val::I32(a.wrapping_mul(b)));
224                 }
225                 ConstOp::I64Add => {
226                     let b = self.pop()?.unwrap_i64();
227                     let a = self.pop()?.unwrap_i64();
228                     self.stack.push(Val::I64(a.wrapping_add(b)));
229                 }
230                 ConstOp::I64Sub => {
231                     let b = self.pop()?.unwrap_i64();
232                     let a = self.pop()?.unwrap_i64();
233                     self.stack.push(Val::I64(a.wrapping_sub(b)));
234                 }
235                 ConstOp::I64Mul => {
236                     let b = self.pop()?.unwrap_i64();
237                     let a = self.pop()?.unwrap_i64();
238                     self.stack.push(Val::I64(a.wrapping_mul(b)));
239                 }
240 
241                 #[cfg(not(feature = "gc"))]
242                 ConstOp::StructNew { .. }
243                 | ConstOp::StructNewDefault { .. }
244                 | ConstOp::ArrayNew { .. }
245                 | ConstOp::ArrayNewDefault { .. }
246                 | ConstOp::ArrayNewFixed { .. }
247                 | ConstOp::ExternConvertAny
248                 | ConstOp::AnyConvertExtern => {
249                     bail!(
250                         "const expr evaluation error: struct operations are not \
251                          supported without the `gc` feature"
252                     )
253                 }
254 
255                 #[cfg(feature = "gc")]
256                 ConstOp::StructNew { struct_type_index } => {
257                     let interned_type_index = store.instance(context.instance).env_module().types
258                         [*struct_type_index]
259                         .unwrap_engine_type_index();
260                     let len = context.struct_fields_len(store, interned_type_index);
261 
262                     if self.stack.len() < len {
263                         bail!(
264                             "const expr evaluation error: expected at least {len} values on the stack, found {}",
265                             self.stack.len()
266                         )
267                     }
268 
269                     let start = self.stack.len() - len;
270                     let s = unsafe {
271                         context.struct_new(store, interned_type_index, &self.stack[start..])?
272                     };
273                     self.stack.truncate(start);
274                     self.stack.push(s);
275                 }
276 
277                 #[cfg(feature = "gc")]
278                 ConstOp::StructNewDefault { struct_type_index } => {
279                     let ty = store.instance(context.instance).env_module().types
280                         [*struct_type_index]
281                         .unwrap_engine_type_index();
282                     self.stack.push(context.struct_new_default(store, ty)?);
283                 }
284 
285                 #[cfg(feature = "gc")]
286                 ConstOp::ArrayNew { array_type_index } => {
287                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
288                         .unwrap_engine_type_index();
289                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
290 
291                     let len = self.pop()?.unwrap_i32().cast_unsigned();
292 
293                     let elem = self.pop()?;
294 
295                     let pre = ArrayRefPre::_new(store, ty);
296                     let array = unsafe { ArrayRef::new_maybe_async(store, &pre, &elem, len)? };
297 
298                     self.stack.push(Val::AnyRef(Some(array.into())));
299                 }
300 
301                 #[cfg(feature = "gc")]
302                 ConstOp::ArrayNewDefault { array_type_index } => {
303                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
304                         .unwrap_engine_type_index();
305                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
306 
307                     let len = self.pop()?.unwrap_i32().cast_unsigned();
308 
309                     let elem = Val::default_for_ty(ty.element_type().unpack())
310                         .expect("type should have a default value");
311 
312                     let pre = ArrayRefPre::_new(store, ty);
313                     let array = unsafe { ArrayRef::new_maybe_async(store, &pre, &elem, len)? };
314 
315                     self.stack.push(Val::AnyRef(Some(array.into())));
316                 }
317 
318                 #[cfg(feature = "gc")]
319                 ConstOp::ArrayNewFixed {
320                     array_type_index,
321                     array_size,
322                 } => {
323                     let ty = store.instance(context.instance).env_module().types[*array_type_index]
324                         .unwrap_engine_type_index();
325                     let ty = ArrayType::from_shared_type_index(store.engine(), ty);
326 
327                     let array_size = usize::try_from(*array_size).unwrap();
328                     if self.stack.len() < array_size {
329                         bail!(
330                             "const expr evaluation error: expected at least {array_size} values on the stack, found {}",
331                             self.stack.len()
332                         )
333                     }
334 
335                     let start = self.stack.len() - array_size;
336 
337                     let elems = self
338                         .stack
339                         .drain(start..)
340                         .collect::<smallvec::SmallVec<[_; 8]>>();
341 
342                     let pre = ArrayRefPre::_new(store, ty);
343                     let array = unsafe { ArrayRef::new_fixed_maybe_async(store, &pre, &elems)? };
344 
345                     self.stack.push(Val::AnyRef(Some(array.into())));
346                 }
347 
348                 #[cfg(feature = "gc")]
349                 ConstOp::ExternConvertAny => {
350                     let mut store = AutoAssertNoGc::new(store);
351                     let result = match self.pop()?.unwrap_anyref() {
352                         Some(anyref) => Some(ExternRef::_convert_any(&mut store, *anyref)?),
353                         None => None,
354                     };
355                     self.stack.push(Val::ExternRef(result));
356                 }
357 
358                 #[cfg(feature = "gc")]
359                 ConstOp::AnyConvertExtern => {
360                     let mut store = AutoAssertNoGc::new(store);
361                     let result = match self.pop()?.unwrap_externref() {
362                         Some(externref) => Some(AnyRef::_convert_extern(&mut store, *externref)?),
363                         None => None,
364                     };
365                     self.stack.push(result.into());
366                 }
367             }
368         }
369 
370         if self.stack.len() == 1 {
371             log::trace!("const expr evaluated to {:?}", self.stack[0]);
372             Ok(&self.stack[0])
373         } else {
374             bail!(
375                 "const expr evaluation error: expected 1 resulting value, found {}",
376                 self.stack.len()
377             )
378         }
379     }
380 
381     fn pop(&mut self) -> Result<Val> {
382         self.stack.pop().ok_or_else(|| {
383             anyhow!(
384                 "const expr evaluation error: attempted to pop from an empty \
385                  evaluation stack"
386             )
387         })
388     }
389 }
390