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