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