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