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