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