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