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