1 //! Evaluating const expressions. 2 3 use crate::prelude::*; 4 use crate::store::{AutoAssertNoGc, InstanceId, StoreOpaque}; 5 #[cfg(feature = "gc")] 6 use crate::{ 7 AnyRef, ArrayRef, ArrayRefPre, ArrayType, ExternRef, I31, StructRef, StructRefPre, StructType, 8 }; 9 use crate::{OpaqueRootScope, Val}; 10 use wasmtime_environ::{ConstExpr, ConstOp, FuncIndex, GlobalIndex}; 11 #[cfg(feature = "gc")] 12 use wasmtime_environ::{VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType}; 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: Vec<Val>, 21 } 22 23 /// The context within which a particular const expression is evaluated. 24 pub struct ConstEvalContext { 25 pub(crate) instance: InstanceId, 26 } 27 28 impl ConstEvalContext { 29 /// Create a new context. 30 pub fn new(instance: InstanceId) -> Self { 31 Self { instance } 32 } 33 34 fn global_get(&mut self, store: &mut StoreOpaque, index: GlobalIndex) -> Result<Val> { 35 let id = store.id(); 36 Ok(store 37 .instance_mut(self.instance) 38 .get_exported_global(id, index) 39 ._get(&mut AutoAssertNoGc::new(store))) 40 } 41 42 fn ref_func(&mut self, store: &mut StoreOpaque, index: FuncIndex) -> Result<Val> { 43 let id = store.id(); 44 // SAFETY: `id` is the correct store-owner of the function being looked 45 // up 46 let func = unsafe { 47 store 48 .instance_mut(self.instance) 49 .get_exported_func(id, index) 50 }; 51 Ok(func.into()) 52 } 53 54 #[cfg(feature = "gc")] 55 fn struct_fields_len(&self, store: &mut StoreOpaque, shared_ty: VMSharedTypeIndex) -> usize { 56 let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty); 57 let fields = struct_ty.fields(); 58 fields.len() 59 } 60 61 /// Safety: field values must be of the correct types. 62 #[cfg(feature = "gc")] 63 unsafe fn struct_new( 64 &mut self, 65 store: &mut StoreOpaque, 66 shared_ty: VMSharedTypeIndex, 67 fields: &[Val], 68 ) -> Result<Val> { 69 let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty); 70 let allocator = StructRefPre::_new(store, struct_ty); 71 let struct_ref = unsafe { StructRef::new_maybe_async(store, &allocator, &fields)? }; 72 Ok(Val::AnyRef(Some(struct_ref.into()))) 73 } 74 75 #[cfg(feature = "gc")] 76 fn struct_new_default( 77 &mut self, 78 store: &mut StoreOpaque, 79 shared_ty: VMSharedTypeIndex, 80 ) -> Result<Val> { 81 let module = store 82 .instance(self.instance) 83 .runtime_module() 84 .expect("should never be allocating a struct type defined in a dummy module"); 85 86 let borrowed = module 87 .engine() 88 .signatures() 89 .borrow(shared_ty) 90 .expect("should have a registered type for struct"); 91 let WasmSubType { 92 composite_type: 93 WasmCompositeType { 94 shared: false, 95 inner: WasmCompositeInnerType::Struct(struct_ty), 96 }, 97 .. 98 } = &*borrowed 99 else { 100 unreachable!("registered type should be a struct"); 101 }; 102 103 let fields = struct_ty 104 .fields 105 .iter() 106 .map(|ty| match &ty.element_type { 107 wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => { 108 Val::I32(0) 109 } 110 wasmtime_environ::WasmStorageType::Val(v) => match v { 111 wasmtime_environ::WasmValType::I32 => Val::I32(0), 112 wasmtime_environ::WasmValType::I64 => Val::I64(0), 113 wasmtime_environ::WasmValType::F32 => Val::F32(0.0f32.to_bits()), 114 wasmtime_environ::WasmValType::F64 => Val::F64(0.0f64.to_bits()), 115 wasmtime_environ::WasmValType::V128 => Val::V128(0u128.into()), 116 wasmtime_environ::WasmValType::Ref(r) => { 117 assert!(r.nullable); 118 Val::null_top(r.heap_type.top()) 119 } 120 }, 121 }) 122 .collect::<smallvec::SmallVec<[_; 8]>>(); 123 124 unsafe { self.struct_new(store, shared_ty, &fields) } 125 } 126 } 127 128 impl ConstExprEvaluator { 129 /// Evaluate the given const expression in the given context. 130 /// 131 /// 132 /// Note that the `store` argument is an `OpaqueRootScope` which is used to 133 /// require that a GC rooting scope external to evaluation of this constant 134 /// is required. Constant expression evaluation may perform GC allocations 135 /// and itself trigger a GC meaning that all references must be rooted, 136 /// hence the external requirement of a rooting scope. 137 /// 138 /// # Unsafety 139 /// 140 /// When async is enabled, this may only be executed on a fiber stack. 141 /// 142 /// The given const expression must be valid within the given context, 143 /// e.g. the const expression must be well-typed and the context must return 144 /// global values of the expected types. This evaluator operates directly on 145 /// untyped `ValRaw`s and does not and cannot check that its operands are of 146 /// the correct type. 147 /// 148 /// If given async store, then this must be called from on an async fiber 149 /// stack. 150 pub unsafe fn eval( 151 &mut self, 152 store: &mut OpaqueRootScope<&mut StoreOpaque>, 153 context: &mut ConstEvalContext, 154 expr: &ConstExpr, 155 ) -> Result<&Val> { 156 match expr.ops() { 157 // Skip the interpreter loop for some known constant patterns that 158 // are easy to calculate the result of. 159 [ConstOp::I32Const(i)] => self.return_one(Val::I32(*i)), 160 [ConstOp::I64Const(i)] => self.return_one(Val::I64(*i)), 161 [ConstOp::F32Const(f)] => self.return_one(Val::F32(*f)), 162 [ConstOp::F64Const(f)] => self.return_one(Val::F64(*f)), 163 164 // Fall back to the interpreter loop for all other expressions. 165 // 166 // SAFETY: this function has the same contract as `eval_loop`. 167 other => unsafe { self.eval_loop(store, context, other) }, 168 } 169 } 170 171 fn return_one(&mut self, val: Val) -> Result<&Val> { 172 self.stack.clear(); 173 self.stack.push(val); 174 Ok(&self.stack[0]) 175 } 176 177 /// # Safety 178 /// 179 /// See [`Self::eval`]. 180 unsafe fn eval_loop( 181 &mut self, 182 store: &mut OpaqueRootScope<&mut StoreOpaque>, 183 context: &mut ConstEvalContext, 184 ops: &[ConstOp], 185 ) -> Result<&Val> { 186 log::trace!("evaluating const expr: {ops:?}"); 187 188 self.stack.clear(); 189 190 for op in ops { 191 log::trace!("const-evaluating op: {op:?}"); 192 match op { 193 ConstOp::I32Const(i) => self.stack.push(Val::I32(*i)), 194 ConstOp::I64Const(i) => self.stack.push(Val::I64(*i)), 195 ConstOp::F32Const(f) => self.stack.push(Val::F32(*f)), 196 ConstOp::F64Const(f) => self.stack.push(Val::F64(*f)), 197 ConstOp::V128Const(v) => self.stack.push(Val::V128((*v).into())), 198 ConstOp::GlobalGet(g) => self.stack.push(context.global_get(store, *g)?), 199 ConstOp::RefNull(ty) => self.stack.push(Val::null_top(*ty)), 200 ConstOp::RefFunc(f) => self.stack.push(context.ref_func(store, *f)?), 201 #[cfg(feature = "gc")] 202 ConstOp::RefI31 => { 203 let i = self.pop()?.unwrap_i32(); 204 let i31 = I31::wrapping_i32(i); 205 let r = AnyRef::_from_i31(&mut AutoAssertNoGc::new(store), i31); 206 self.stack.push(Val::AnyRef(Some(r))); 207 } 208 #[cfg(not(feature = "gc"))] 209 ConstOp::RefI31 => panic!("should not have validated"), 210 ConstOp::I32Add => { 211 let b = self.pop()?.unwrap_i32(); 212 let a = self.pop()?.unwrap_i32(); 213 self.stack.push(Val::I32(a.wrapping_add(b))); 214 } 215 ConstOp::I32Sub => { 216 let b = self.pop()?.unwrap_i32(); 217 let a = self.pop()?.unwrap_i32(); 218 self.stack.push(Val::I32(a.wrapping_sub(b))); 219 } 220 ConstOp::I32Mul => { 221 let b = self.pop()?.unwrap_i32(); 222 let a = self.pop()?.unwrap_i32(); 223 self.stack.push(Val::I32(a.wrapping_mul(b))); 224 } 225 ConstOp::I64Add => { 226 let b = self.pop()?.unwrap_i64(); 227 let a = self.pop()?.unwrap_i64(); 228 self.stack.push(Val::I64(a.wrapping_add(b))); 229 } 230 ConstOp::I64Sub => { 231 let b = self.pop()?.unwrap_i64(); 232 let a = self.pop()?.unwrap_i64(); 233 self.stack.push(Val::I64(a.wrapping_sub(b))); 234 } 235 ConstOp::I64Mul => { 236 let b = self.pop()?.unwrap_i64(); 237 let a = self.pop()?.unwrap_i64(); 238 self.stack.push(Val::I64(a.wrapping_mul(b))); 239 } 240 241 #[cfg(not(feature = "gc"))] 242 ConstOp::StructNew { .. } 243 | ConstOp::StructNewDefault { .. } 244 | ConstOp::ArrayNew { .. } 245 | ConstOp::ArrayNewDefault { .. } 246 | ConstOp::ArrayNewFixed { .. } 247 | ConstOp::ExternConvertAny 248 | ConstOp::AnyConvertExtern => { 249 bail!( 250 "const expr evaluation error: struct operations are not \ 251 supported without the `gc` feature" 252 ) 253 } 254 255 #[cfg(feature = "gc")] 256 ConstOp::StructNew { struct_type_index } => { 257 let interned_type_index = store.instance(context.instance).env_module().types 258 [*struct_type_index] 259 .unwrap_engine_type_index(); 260 let len = context.struct_fields_len(store, interned_type_index); 261 262 if self.stack.len() < len { 263 bail!( 264 "const expr evaluation error: expected at least {len} values on the stack, found {}", 265 self.stack.len() 266 ) 267 } 268 269 let start = self.stack.len() - len; 270 let s = unsafe { 271 context.struct_new(store, interned_type_index, &self.stack[start..])? 272 }; 273 self.stack.truncate(start); 274 self.stack.push(s); 275 } 276 277 #[cfg(feature = "gc")] 278 ConstOp::StructNewDefault { struct_type_index } => { 279 let ty = store.instance(context.instance).env_module().types 280 [*struct_type_index] 281 .unwrap_engine_type_index(); 282 self.stack.push(context.struct_new_default(store, ty)?); 283 } 284 285 #[cfg(feature = "gc")] 286 ConstOp::ArrayNew { array_type_index } => { 287 let ty = store.instance(context.instance).env_module().types[*array_type_index] 288 .unwrap_engine_type_index(); 289 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 290 291 let len = self.pop()?.unwrap_i32().cast_unsigned(); 292 293 let elem = self.pop()?; 294 295 let pre = ArrayRefPre::_new(store, ty); 296 let array = unsafe { ArrayRef::new_maybe_async(store, &pre, &elem, len)? }; 297 298 self.stack.push(Val::AnyRef(Some(array.into()))); 299 } 300 301 #[cfg(feature = "gc")] 302 ConstOp::ArrayNewDefault { array_type_index } => { 303 let ty = store.instance(context.instance).env_module().types[*array_type_index] 304 .unwrap_engine_type_index(); 305 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 306 307 let len = self.pop()?.unwrap_i32().cast_unsigned(); 308 309 let elem = Val::default_for_ty(ty.element_type().unpack()) 310 .expect("type should have a default value"); 311 312 let pre = ArrayRefPre::_new(store, ty); 313 let array = unsafe { ArrayRef::new_maybe_async(store, &pre, &elem, len)? }; 314 315 self.stack.push(Val::AnyRef(Some(array.into()))); 316 } 317 318 #[cfg(feature = "gc")] 319 ConstOp::ArrayNewFixed { 320 array_type_index, 321 array_size, 322 } => { 323 let ty = store.instance(context.instance).env_module().types[*array_type_index] 324 .unwrap_engine_type_index(); 325 let ty = ArrayType::from_shared_type_index(store.engine(), ty); 326 327 let array_size = usize::try_from(*array_size).unwrap(); 328 if self.stack.len() < array_size { 329 bail!( 330 "const expr evaluation error: expected at least {array_size} values on the stack, found {}", 331 self.stack.len() 332 ) 333 } 334 335 let start = self.stack.len() - array_size; 336 337 let elems = self 338 .stack 339 .drain(start..) 340 .collect::<smallvec::SmallVec<[_; 8]>>(); 341 342 let pre = ArrayRefPre::_new(store, ty); 343 let array = unsafe { ArrayRef::new_fixed_maybe_async(store, &pre, &elems)? }; 344 345 self.stack.push(Val::AnyRef(Some(array.into()))); 346 } 347 348 #[cfg(feature = "gc")] 349 ConstOp::ExternConvertAny => { 350 let mut store = AutoAssertNoGc::new(store); 351 let result = match self.pop()?.unwrap_anyref() { 352 Some(anyref) => Some(ExternRef::_convert_any(&mut store, *anyref)?), 353 None => None, 354 }; 355 self.stack.push(Val::ExternRef(result)); 356 } 357 358 #[cfg(feature = "gc")] 359 ConstOp::AnyConvertExtern => { 360 let mut store = AutoAssertNoGc::new(store); 361 let result = match self.pop()?.unwrap_externref() { 362 Some(externref) => Some(AnyRef::_convert_extern(&mut store, *externref)?), 363 None => None, 364 }; 365 self.stack.push(result.into()); 366 } 367 } 368 } 369 370 if self.stack.len() == 1 { 371 log::trace!("const expr evaluated to {:?}", self.stack[0]); 372 Ok(&self.stack[0]) 373 } else { 374 bail!( 375 "const expr evaluation error: expected 1 resulting value, found {}", 376 self.stack.len() 377 ) 378 } 379 } 380 381 fn pop(&mut self) -> Result<Val> { 382 self.stack.pop().ok_or_else(|| { 383 anyhow!( 384 "const expr evaluation error: attempted to pop from an empty \ 385 evaluation stack" 386 ) 387 }) 388 } 389 } 390