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