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