1 //! Evaluating const expressions. 2 3 use crate::prelude::*; 4 use crate::runtime::vm; 5 use crate::store::{AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter}; 6 #[cfg(feature = "gc")] 7 use crate::{ 8 AnyRef, ArrayRef, ArrayRefPre, ArrayType, ExternRef, I31, StructRef, StructRefPre, StructType, 9 }; 10 use crate::{OpaqueRootScope, Val}; 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 pub struct ConstExprEvaluator { 20 stack: Vec<Val>, 21 simple: Val, 22 } 23 24 impl Default for ConstExprEvaluator { 25 fn default() -> ConstExprEvaluator { 26 ConstExprEvaluator { 27 stack: Vec::new(), 28 simple: Val::I32(0), 29 } 30 } 31 } 32 33 /// The context within which a particular const expression is evaluated. 34 pub struct ConstEvalContext { 35 pub(crate) instance: InstanceId, 36 } 37 38 impl ConstEvalContext { 39 /// Create a new context. 40 pub fn new(instance: InstanceId) -> Self { 41 Self { instance } 42 } 43 44 fn global_get(&mut self, store: &mut StoreOpaque, index: GlobalIndex) -> Result<Val> { 45 let id = store.id(); 46 Ok(store 47 .instance_mut(self.instance) 48 .get_exported_global(id, index) 49 ._get(&mut AutoAssertNoGc::new(store))) 50 } 51 52 fn ref_func(&mut self, store: &mut StoreOpaque, index: FuncIndex) -> Result<Val> { 53 let id = store.id(); 54 let (instance, registry) = store.instance_and_module_registry_mut(self.instance); 55 // SAFETY: `id` is the correct store-owner of the function being looked 56 // up 57 let func = unsafe { instance.get_exported_func(registry, id, index) }; 58 Ok(func.into()) 59 } 60 61 #[cfg(feature = "gc")] 62 fn struct_fields_len(&self, store: &mut StoreOpaque, shared_ty: VMSharedTypeIndex) -> usize { 63 let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty); 64 let fields = struct_ty.fields(); 65 fields.len() 66 } 67 68 #[cfg(feature = "gc")] 69 async fn struct_new( 70 &mut self, 71 store: &mut StoreOpaque, 72 limiter: Option<&mut StoreResourceLimiter<'_>>, 73 shared_ty: VMSharedTypeIndex, 74 fields: &[Val], 75 ) -> Result<Val> { 76 let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty); 77 let allocator = StructRefPre::_new(store, struct_ty); 78 let struct_ref = StructRef::_new_async(store, limiter, &allocator, &fields).await?; 79 Ok(Val::AnyRef(Some(struct_ref.into()))) 80 } 81 82 #[cfg(feature = "gc")] 83 async fn struct_new_default( 84 &mut self, 85 store: &mut StoreOpaque, 86 limiter: Option<&mut StoreResourceLimiter<'_>>, 87 shared_ty: VMSharedTypeIndex, 88 ) -> Result<Val> { 89 let module = store 90 .instance(self.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 Val::I32(0) 117 } 118 wasmtime_environ::WasmStorageType::Val(v) => match v { 119 wasmtime_environ::WasmValType::I32 => Val::I32(0), 120 wasmtime_environ::WasmValType::I64 => Val::I64(0), 121 wasmtime_environ::WasmValType::F32 => Val::F32(0.0f32.to_bits()), 122 wasmtime_environ::WasmValType::F64 => Val::F64(0.0f64.to_bits()), 123 wasmtime_environ::WasmValType::V128 => Val::V128(0u128.into()), 124 wasmtime_environ::WasmValType::Ref(r) => { 125 assert!(r.nullable); 126 Val::null_top(r.heap_type.top()) 127 } 128 }, 129 }) 130 .collect::<smallvec::SmallVec<[_; 8]>>(); 131 132 self.struct_new(store, limiter, shared_ty, &fields).await 133 } 134 } 135 136 impl ConstExprEvaluator { 137 /// Same as [`Self::eval`] except that this is specifically intended for 138 /// integral constant expression. 139 /// 140 /// # Panics 141 /// 142 /// Panics if `ConstExpr` contains GC ops (e.g. it's not for an integral 143 /// type). 144 pub fn eval_int( 145 &mut self, 146 store: &mut StoreOpaque, 147 context: &mut ConstEvalContext, 148 expr: &ConstExpr, 149 ) -> Result<&Val> { 150 // Try to evaluate a simple expression first before doing the more 151 // complicated eval loop below. 152 if self.try_simple(expr).is_some() { 153 return Ok(&self.simple); 154 } 155 156 // Note that `assert_ready` here should be valid as production of an 157 // integer cannot involve GC meaning that async operations aren't used. 158 let mut scope = OpaqueRootScope::new(store); 159 vm::assert_ready(self.eval_loop(&mut scope, None, context, expr)) 160 } 161 162 /// Attempts to peek into `expr` to see if it's trivial to evaluate, e.g. 163 /// for `i32.const N`. 164 #[inline] 165 pub fn try_simple(&mut self, expr: &ConstExpr) -> Option<&Val> { 166 match expr.ops() { 167 [ConstOp::I32Const(i)] => Some(self.return_one(Val::I32(*i))), 168 [ConstOp::I64Const(i)] => Some(self.return_one(Val::I64(*i))), 169 [ConstOp::F32Const(f)] => Some(self.return_one(Val::F32(*f))), 170 [ConstOp::F64Const(f)] => Some(self.return_one(Val::F64(*f))), 171 _ => None, 172 } 173 } 174 175 /// Evaluate the given const expression in the given context. 176 /// 177 /// Note that the `store` argument is an `OpaqueRootScope` which is used to 178 /// require that a GC rooting scope external to evaluation of this constant 179 /// is required. Constant expression evaluation may perform GC allocations 180 /// and itself trigger a GC meaning that all references must be rooted, 181 /// hence the external requirement of a rooting scope. 182 /// 183 /// # Panics 184 /// 185 /// This function will panic if `expr` is an invalid constant expression. 186 pub async fn eval( 187 &mut self, 188 store: &mut OpaqueRootScope<&mut StoreOpaque>, 189 limiter: Option<&mut StoreResourceLimiter<'_>>, 190 context: &mut ConstEvalContext, 191 expr: &ConstExpr, 192 ) -> Result<&Val> { 193 // Same structure as `eval_int` above, except using `.await` and with a 194 // slightly different type signature here for this function. 195 if self.try_simple(expr).is_some() { 196 return Ok(&self.simple); 197 } 198 self.eval_loop(store, limiter, context, expr).await 199 } 200 201 #[inline] 202 fn return_one(&mut self, val: Val) -> &Val { 203 self.simple = val; 204 &self.simple 205 // self.stack.clear(); 206 // self.stack.push(val); 207 // &self.stack[0] 208 } 209 210 #[cold] 211 async fn eval_loop( 212 &mut self, 213 store: &mut OpaqueRootScope<&mut StoreOpaque>, 214 mut limiter: Option<&mut StoreResourceLimiter<'_>>, 215 context: &mut ConstEvalContext, 216 expr: &ConstExpr, 217 ) -> Result<&Val> { 218 log::trace!("evaluating const expr: {expr:?}"); 219 220 self.stack.clear(); 221 222 // On GC-less builds ensure that this is always considered used an 223 // needed-mutable. 224 let _ = &mut limiter; 225 226 for op in expr.ops() { 227 log::trace!("const-evaluating op: {op:?}"); 228 match op { 229 ConstOp::I32Const(i) => self.stack.push(Val::I32(*i)), 230 ConstOp::I64Const(i) => self.stack.push(Val::I64(*i)), 231 ConstOp::F32Const(f) => self.stack.push(Val::F32(*f)), 232 ConstOp::F64Const(f) => self.stack.push(Val::F64(*f)), 233 ConstOp::V128Const(v) => self.stack.push(Val::V128((*v).into())), 234 ConstOp::GlobalGet(g) => self.stack.push(context.global_get(store, *g)?), 235 ConstOp::RefNull(ty) => self.stack.push(Val::null_top(*ty)), 236 ConstOp::RefFunc(f) => self.stack.push(context.ref_func(store, *f)?), 237 #[cfg(feature = "gc")] 238 ConstOp::RefI31 => { 239 let i = self.pop()?.unwrap_i32(); 240 let i31 = I31::wrapping_i32(i); 241 let r = AnyRef::_from_i31(&mut AutoAssertNoGc::new(store), i31); 242 self.stack.push(Val::AnyRef(Some(r))); 243 } 244 #[cfg(not(feature = "gc"))] 245 ConstOp::RefI31 => panic!("should not have validated"), 246 ConstOp::I32Add => { 247 let b = self.pop()?.unwrap_i32(); 248 let a = self.pop()?.unwrap_i32(); 249 self.stack.push(Val::I32(a.wrapping_add(b))); 250 } 251 ConstOp::I32Sub => { 252 let b = self.pop()?.unwrap_i32(); 253 let a = self.pop()?.unwrap_i32(); 254 self.stack.push(Val::I32(a.wrapping_sub(b))); 255 } 256 ConstOp::I32Mul => { 257 let b = self.pop()?.unwrap_i32(); 258 let a = self.pop()?.unwrap_i32(); 259 self.stack.push(Val::I32(a.wrapping_mul(b))); 260 } 261 ConstOp::I64Add => { 262 let b = self.pop()?.unwrap_i64(); 263 let a = self.pop()?.unwrap_i64(); 264 self.stack.push(Val::I64(a.wrapping_add(b))); 265 } 266 ConstOp::I64Sub => { 267 let b = self.pop()?.unwrap_i64(); 268 let a = self.pop()?.unwrap_i64(); 269 self.stack.push(Val::I64(a.wrapping_sub(b))); 270 } 271 ConstOp::I64Mul => { 272 let b = self.pop()?.unwrap_i64(); 273 let a = self.pop()?.unwrap_i64(); 274 self.stack.push(Val::I64(a.wrapping_mul(b))); 275 } 276 277 #[cfg(not(feature = "gc"))] 278 ConstOp::StructNew { .. } 279 | ConstOp::StructNewDefault { .. } 280 | ConstOp::ArrayNew { .. } 281 | ConstOp::ArrayNewDefault { .. } 282 | ConstOp::ArrayNewFixed { .. } 283 | ConstOp::ExternConvertAny 284 | ConstOp::AnyConvertExtern => { 285 bail!( 286 "const expr evaluation error: struct operations are not \ 287 supported without the `gc` feature" 288 ) 289 } 290 291 #[cfg(feature = "gc")] 292 ConstOp::StructNew { struct_type_index } => { 293 let interned_type_index = store.instance(context.instance).env_module().types 294 [*struct_type_index] 295 .unwrap_engine_type_index(); 296 let len = context.struct_fields_len(store, interned_type_index); 297 298 if self.stack.len() < len { 299 bail!( 300 "const expr evaluation error: expected at least {len} values on the stack, found {}", 301 self.stack.len() 302 ) 303 } 304 305 let start = self.stack.len() - len; 306 let s = context 307 .struct_new( 308 store, 309 limiter.as_deref_mut(), 310 interned_type_index, 311 &self.stack[start..], 312 ) 313 .await?; 314 self.stack.truncate(start); 315 self.stack.push(s); 316 } 317 318 #[cfg(feature = "gc")] 319 ConstOp::StructNewDefault { struct_type_index } => { 320 let ty = store.instance(context.instance).env_module().types 321 [*struct_type_index] 322 .unwrap_engine_type_index(); 323 self.stack.push( 324 context 325 .struct_new_default(store, limiter.as_deref_mut(), ty) 326 .await?, 327 ); 328 } 329 330 #[cfg(feature = "gc")] 331 ConstOp::ArrayNew { array_type_index } => { 332 let ty = store.instance(context.instance).env_module().types[*array_type_index] 333 .unwrap_engine_type_index(); 334 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 335 336 let len = self.pop()?.unwrap_i32().cast_unsigned(); 337 338 let elem = self.pop()?; 339 340 let pre = ArrayRefPre::_new(store, ty); 341 let array = 342 ArrayRef::_new_async(store, limiter.as_deref_mut(), &pre, &elem, len) 343 .await?; 344 345 self.stack.push(Val::AnyRef(Some(array.into()))); 346 } 347 348 #[cfg(feature = "gc")] 349 ConstOp::ArrayNewDefault { array_type_index } => { 350 let ty = store.instance(context.instance).env_module().types[*array_type_index] 351 .unwrap_engine_type_index(); 352 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 353 354 let len = self.pop()?.unwrap_i32().cast_unsigned(); 355 356 let elem = Val::default_for_ty(ty.element_type().unpack()) 357 .expect("type should have a default value"); 358 359 let pre = ArrayRefPre::_new(store, ty); 360 let array = 361 ArrayRef::_new_async(store, limiter.as_deref_mut(), &pre, &elem, len) 362 .await?; 363 364 self.stack.push(Val::AnyRef(Some(array.into()))); 365 } 366 367 #[cfg(feature = "gc")] 368 ConstOp::ArrayNewFixed { 369 array_type_index, 370 array_size, 371 } => { 372 let ty = store.instance(context.instance).env_module().types[*array_type_index] 373 .unwrap_engine_type_index(); 374 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 375 376 let array_size = usize::try_from(*array_size).unwrap(); 377 if self.stack.len() < array_size { 378 bail!( 379 "const expr evaluation error: expected at least {array_size} values on the stack, found {}", 380 self.stack.len() 381 ) 382 } 383 384 let start = self.stack.len() - array_size; 385 386 let elems = self 387 .stack 388 .drain(start..) 389 .collect::<smallvec::SmallVec<[_; 8]>>(); 390 391 let pre = ArrayRefPre::_new(store, ty); 392 let array = 393 ArrayRef::_new_fixed_async(store, limiter.as_deref_mut(), &pre, &elems) 394 .await?; 395 396 self.stack.push(Val::AnyRef(Some(array.into()))); 397 } 398 399 #[cfg(feature = "gc")] 400 ConstOp::ExternConvertAny => { 401 let mut store = AutoAssertNoGc::new(store); 402 let result = match self.pop()?.unwrap_anyref() { 403 Some(anyref) => Some(ExternRef::_convert_any(&mut store, *anyref)?), 404 None => None, 405 }; 406 self.stack.push(Val::ExternRef(result)); 407 } 408 409 #[cfg(feature = "gc")] 410 ConstOp::AnyConvertExtern => { 411 let mut store = AutoAssertNoGc::new(store); 412 let result = match self.pop()?.unwrap_externref() { 413 Some(externref) => Some(AnyRef::_convert_extern(&mut store, *externref)?), 414 None => None, 415 }; 416 self.stack.push(result.into()); 417 } 418 } 419 } 420 421 if self.stack.len() == 1 { 422 log::trace!("const expr evaluated to {:?}", self.stack[0]); 423 Ok(&self.stack[0]) 424 } else { 425 bail!( 426 "const expr evaluation error: expected 1 resulting value, found {}", 427 self.stack.len() 428 ) 429 } 430 } 431 432 fn pop(&mut self) -> Result<Val> { 433 self.stack.pop().ok_or_else(|| { 434 format_err!( 435 "const expr evaluation error: attempted to pop from an empty \ 436 evaluation stack" 437 ) 438 }) 439 } 440 } 441