1 //! Types for the `gc` operations. 2 3 use crate::generators::gc_ops::limits::GcOpsLimits; 4 use crate::generators::gc_ops::ops::GcOp; 5 use serde::{Deserialize, Serialize}; 6 use std::collections::btree_map::Entry; 7 use std::collections::{BTreeMap, BTreeSet}; 8 use wasmtime_environ::graphs::{Dfs, DfsEvent, Graph}; 9 10 /// Identifies a `(rec ...)` group. 11 #[derive( 12 Debug, Copy, Clone, Eq, PartialOrd, PartialEq, Ord, Hash, Default, Serialize, Deserialize, 13 )] 14 pub struct RecGroupId(pub(crate) u32); 15 16 /// Identifies a type within a rec group. 17 #[derive( 18 Debug, Copy, Clone, Eq, PartialOrd, PartialEq, Ord, Hash, Default, Serialize, Deserialize, 19 )] 20 pub struct TypeId(pub(crate) u32); 21 22 /// A struct type definition. 23 #[derive(Debug, Default, Serialize, Deserialize)] 24 pub struct StructType {} 25 26 /// A composite type: currently only structs. 27 #[derive(Debug, Serialize, Deserialize)] 28 pub enum CompositeType { 29 /// A struct composite type. 30 Struct(StructType), 31 } 32 33 /// A sub-type definition (the per-type payload). 34 #[derive(Debug, Serialize, Deserialize)] 35 pub struct SubType { 36 pub(crate) is_final: bool, 37 pub(crate) supertype: Option<TypeId>, 38 pub(crate) composite_type: CompositeType, 39 } 40 41 /// Supertype graph: edges go from a type to its supertype. 42 struct SupertypeGraph<'a> { 43 type_defs: &'a BTreeMap<TypeId, SubType>, 44 } 45 46 impl Graph<TypeId> for SupertypeGraph<'_> { 47 type NodesIter<'a> 48 = std::iter::Copied<std::collections::btree_map::Keys<'a, TypeId, SubType>> 49 where 50 Self: 'a; 51 nodes(&self) -> Self::NodesIter<'_>52 fn nodes(&self) -> Self::NodesIter<'_> { 53 self.type_defs.keys().copied() 54 } 55 56 type SuccessorsIter<'a> 57 = std::option::IntoIter<TypeId> 58 where 59 Self: 'a; 60 successors(&self, node: TypeId) -> Self::SuccessorsIter<'_>61 fn successors(&self, node: TypeId) -> Self::SuccessorsIter<'_> { 62 self.type_defs 63 .get(&node) 64 .and_then(|def| def.supertype) 65 .into_iter() 66 } 67 } 68 69 /// Rec-group dependency graph: group A depends on group B when a type 70 /// in A has a supertype in B. 71 struct RecGroupGraph<'a> { 72 type_defs: &'a BTreeMap<TypeId, SubType>, 73 rec_groups: &'a BTreeMap<RecGroupId, BTreeSet<TypeId>>, 74 type_to_group: &'a BTreeMap<TypeId, RecGroupId>, 75 } 76 77 impl Graph<RecGroupId> for RecGroupGraph<'_> { 78 type NodesIter<'a> 79 = std::iter::Copied<std::collections::btree_map::Keys<'a, RecGroupId, BTreeSet<TypeId>>> 80 where 81 Self: 'a; 82 nodes(&self) -> Self::NodesIter<'_>83 fn nodes(&self) -> Self::NodesIter<'_> { 84 self.rec_groups.keys().copied() 85 } 86 87 type SuccessorsIter<'a> 88 = std::vec::IntoIter<RecGroupId> 89 where 90 Self: 'a; 91 successors(&self, group: RecGroupId) -> Self::SuccessorsIter<'_>92 fn successors(&self, group: RecGroupId) -> Self::SuccessorsIter<'_> { 93 let mut deps = BTreeSet::new(); 94 95 if let Some(type_ids) = self.rec_groups.get(&group) { 96 for &ty in type_ids { 97 if let Some(super_ty) = self.type_defs.get(&ty).and_then(|d| d.supertype) { 98 if let Some(&super_group) = self.type_to_group.get(&super_ty) { 99 if super_group != group { 100 deps.insert(super_group); 101 } 102 } 103 } 104 } 105 } 106 107 deps.into_iter().collect::<Vec<_>>().into_iter() 108 } 109 } 110 111 /// All type and rec-group state. 112 /// 113 /// Rec groups own sets of [`TypeId`]s; moving a type between groups is 114 /// just a set remove + set insert with no cascading index fixups. 115 #[derive(Debug, Default, Serialize, Deserialize)] 116 pub struct Types { 117 /// Map from rec-group id to the set of types it contains. 118 pub(crate) rec_groups: BTreeMap<RecGroupId, BTreeSet<TypeId>>, 119 /// Map from type id to its definition. 120 pub(crate) type_defs: BTreeMap<TypeId, SubType>, 121 } 122 123 impl Types { 124 /// Create empty type state. new() -> Self125 pub fn new() -> Self { 126 Self::default() 127 } 128 129 /// Return a fresh [`RecGroupId`] not already in use. fresh_rec_group_id(&self, rng: &mut mutatis::Rng) -> RecGroupId130 pub fn fresh_rec_group_id(&self, rng: &mut mutatis::Rng) -> RecGroupId { 131 for _ in 0..1000 { 132 let id = RecGroupId(rng.gen_u32()); 133 if !self.rec_groups.contains_key(&id) { 134 return id; 135 } 136 } 137 panic!("failed to generate a new RecGroupId in 1000 iterations"); 138 } 139 140 /// Return a fresh [`TypeId`] not already in use. fresh_type_id(&self, rng: &mut mutatis::Rng) -> TypeId141 pub fn fresh_type_id(&self, rng: &mut mutatis::Rng) -> TypeId { 142 for _ in 0..1000 { 143 let id = TypeId(rng.gen_u32()); 144 if !self.type_defs.contains_key(&id) { 145 return id; 146 } 147 } 148 panic!("failed to generate a new TypeId in 1000 iterations"); 149 } 150 151 /// Insert an empty rec group. Returns `true` if it was newly inserted. insert_rec_group(&mut self, id: RecGroupId) -> bool152 pub fn insert_rec_group(&mut self, id: RecGroupId) -> bool { 153 match self.rec_groups.entry(id) { 154 Entry::Vacant(e) => { 155 e.insert(BTreeSet::new()); 156 true 157 } 158 Entry::Occupied(_) => false, 159 } 160 } 161 162 /// Insert an empty struct type into the given rec group. 163 /// 164 /// The rec group must already exist. insert_empty_struct( &mut self, id: TypeId, group: RecGroupId, is_final: bool, supertype: Option<TypeId>, )165 pub fn insert_empty_struct( 166 &mut self, 167 id: TypeId, 168 group: RecGroupId, 169 is_final: bool, 170 supertype: Option<TypeId>, 171 ) { 172 self.rec_groups 173 .get_mut(&group) 174 .expect("rec group must exist") 175 .insert(id); 176 self.type_defs.insert( 177 id, 178 SubType { 179 is_final, 180 supertype, 181 composite_type: CompositeType::Struct(StructType::default()), 182 }, 183 ); 184 } 185 186 /// Remove a type from its rec group and from `type_defs`. remove_type(&mut self, id: TypeId)187 pub fn remove_type(&mut self, id: TypeId) { 188 self.type_defs.remove(&id); 189 for members in self.rec_groups.values_mut() { 190 members.remove(&id); 191 } 192 } 193 194 /// Find which rec group a type belongs to, if any. rec_group_of(&self, id: TypeId) -> Option<RecGroupId>195 pub fn rec_group_of(&self, id: TypeId) -> Option<RecGroupId> { 196 self.rec_groups 197 .iter() 198 .find(|(_, members)| members.contains(&id)) 199 .map(|(gid, _)| *gid) 200 } 201 202 /// Topological sort of types by their supertype (supertype before subtype). sort_types_topo(&self, out: &mut Vec<TypeId>)203 pub fn sort_types_topo(&self, out: &mut Vec<TypeId>) { 204 let graph = SupertypeGraph { 205 type_defs: &self.type_defs, 206 }; 207 208 let mut dfs = Dfs::new(graph.nodes()); 209 let mut seen = BTreeSet::new(); 210 211 out.clear(); 212 out.reserve(self.type_defs.len()); 213 214 while let Some(event) = dfs.next(&graph, |id| seen.contains(&id)) { 215 match event { 216 DfsEvent::Pre(id) => { 217 seen.insert(id); 218 } 219 DfsEvent::Post(id) => { 220 out.push(id); 221 } 222 DfsEvent::AfterEdge(_, _) => {} 223 } 224 } 225 } 226 227 /// Topological sort of rec groups: if a type in group G has a 228 /// supertype in group H, then H appears before G in the output. sort_rec_groups_topo(&self, out: &mut Vec<RecGroupId>)229 pub fn sort_rec_groups_topo(&self, out: &mut Vec<RecGroupId>) { 230 let type_to_group = self.type_to_group_map(); 231 let graph = RecGroupGraph { 232 type_defs: &self.type_defs, 233 rec_groups: &self.rec_groups, 234 type_to_group: &type_to_group, 235 }; 236 237 let mut dfs = Dfs::new(graph.nodes()); 238 let mut seen = BTreeSet::new(); 239 240 out.clear(); 241 out.reserve(self.rec_groups.len()); 242 243 while let Some(event) = dfs.next(&graph, |id| seen.contains(&id)) { 244 match event { 245 DfsEvent::Pre(id) => { 246 seen.insert(id); 247 } 248 DfsEvent::Post(id) => { 249 out.push(id); 250 } 251 DfsEvent::AfterEdge(_, _) => {} 252 } 253 } 254 } 255 256 /// Break cycles in the [type -> supertype] graph by dropping some supertype edges. break_supertype_cycles(&mut self)257 pub fn break_supertype_cycles(&mut self) { 258 let graph = SupertypeGraph { 259 type_defs: &self.type_defs, 260 }; 261 262 let mut dfs = Dfs::new(graph.nodes()); 263 let mut seen = BTreeSet::new(); 264 let mut active = BTreeSet::new(); 265 let mut to_clear = BTreeSet::new(); 266 267 while let Some(event) = dfs.next(&graph, |id| seen.contains(&id)) { 268 match event { 269 DfsEvent::Pre(id) => { 270 seen.insert(id); 271 active.insert(id); 272 } 273 DfsEvent::Post(id) => { 274 active.remove(&id); 275 } 276 DfsEvent::AfterEdge(from, to) => { 277 if active.contains(&to) { 278 to_clear.insert(from); 279 } 280 } 281 } 282 } 283 284 for id in to_clear { 285 if let Some(def) = self.type_defs.get_mut(&id) { 286 def.supertype = None; 287 } 288 } 289 } 290 291 /// Build a reverse map from type id to its owning rec group. type_to_group_map(&self) -> BTreeMap<TypeId, RecGroupId>292 fn type_to_group_map(&self) -> BTreeMap<TypeId, RecGroupId> { 293 self.rec_groups 294 .iter() 295 .flat_map(|(&gid, members)| members.iter().map(move |&tid| (tid, gid))) 296 .collect() 297 } 298 299 /// Break cycles in the rec-group dependency graph by dropping cross-group 300 /// supertype edges that are DFS back edges. break_rec_group_cycles(&mut self)301 pub fn break_rec_group_cycles(&mut self) { 302 let type_to_group = self.type_to_group_map(); 303 let graph = RecGroupGraph { 304 type_defs: &self.type_defs, 305 rec_groups: &self.rec_groups, 306 type_to_group: &type_to_group, 307 }; 308 309 let mut seen = BTreeSet::new(); 310 let mut back_edges: BTreeSet<(RecGroupId, RecGroupId)> = BTreeSet::new(); 311 let mut dfs = Dfs::default(); 312 313 for &root in self.rec_groups.keys() { 314 if seen.contains(&root) { 315 continue; 316 } 317 dfs.add_root(root); 318 let mut active = BTreeSet::new(); 319 320 while let Some(event) = dfs.next(&graph, |id| seen.contains(&id)) { 321 match event { 322 DfsEvent::Pre(id) => { 323 seen.insert(id); 324 active.insert(id); 325 } 326 DfsEvent::Post(id) => { 327 active.remove(&id); 328 } 329 DfsEvent::AfterEdge(from, to) => { 330 if active.contains(&to) { 331 back_edges.insert((from, to)); 332 } 333 } 334 } 335 } 336 } 337 338 // Drop supertype edges that correspond to back edges. 339 if !back_edges.is_empty() { 340 for (&tid, def) in self.type_defs.iter_mut() { 341 if let Some(st) = def.supertype { 342 if let (Some(&sg), Some(&spg)) = 343 (type_to_group.get(&tid), type_to_group.get(&st)) 344 { 345 if back_edges.contains(&(sg, spg)) { 346 def.supertype = None; 347 } 348 } 349 } 350 } 351 } 352 } 353 354 /// Fix up the types to ensure they are within the limits. fixup(&mut self, limits: &GcOpsLimits)355 pub fn fixup(&mut self, limits: &GcOpsLimits) { 356 let max_rec_groups = 357 usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large"); 358 let max_types = usize::try_from(limits.max_types).expect("max_types is too large"); 359 360 // 1. Trim excess types. 361 while self.type_defs.len() > max_types { 362 if let Some((tid, _)) = self.type_defs.pop_last() { 363 for members in self.rec_groups.values_mut() { 364 members.remove(&tid); 365 } 366 } 367 } 368 369 // 2. Drop dangling references and deduplicate across groups. 370 let mut seen = BTreeSet::new(); 371 for members in self.rec_groups.values_mut() { 372 members.retain(|tid| self.type_defs.contains_key(tid) && seen.insert(*tid)); 373 } 374 375 // 3. Trim excess rec groups. 376 while self.rec_groups.len() > max_rec_groups { 377 self.rec_groups.pop_last(); 378 } 379 380 // 4. Find all orphans (from trimmed groups or never in any group). 381 let housed: BTreeSet<TypeId> = self 382 .rec_groups 383 .values() 384 .flat_map(|m| m.iter().copied()) 385 .collect(); 386 let orphans: Vec<TypeId> = self 387 .type_defs 388 .keys() 389 .filter(|tid| !housed.contains(tid)) 390 .copied() 391 .collect(); 392 393 // 5. Adopt orphans or drop them. 394 if let Some(first_members) = self.rec_groups.values_mut().next() { 395 first_members.extend(orphans); 396 } else { 397 for tid in &orphans { 398 self.type_defs.remove(tid); 399 } 400 } 401 402 // 6. Clear supertypes that reference removed types. 403 let valid_type_ids: BTreeSet<TypeId> = self.type_defs.keys().copied().collect(); 404 for def in self.type_defs.values_mut() { 405 if let Some(st) = def.supertype { 406 if !valid_type_ids.contains(&st) { 407 def.supertype = None; 408 } 409 } 410 } 411 412 // 7. A subtype cannot have a final supertype. 413 let final_type_ids: BTreeSet<TypeId> = self 414 .type_defs 415 .iter() 416 .filter(|(_, d)| d.is_final) 417 .map(|(id, _)| *id) 418 .collect(); 419 for def in self.type_defs.values_mut() { 420 if let Some(st) = def.supertype { 421 if final_type_ids.contains(&st) { 422 def.supertype = None; 423 } 424 } 425 } 426 427 // 8. Break supertype cycles and rec-group dependency cycles. 428 self.break_supertype_cycles(); 429 self.break_rec_group_cycles(); 430 431 debug_assert!(self.is_well_formed(limits)); 432 } 433 434 /// Check if the types are well-formed and within configured limits, i.e. 435 /// rec/type counts are within limits, 436 /// every type belongs to exactly one rec group, 437 /// and every rec group member must exist in type_defs. is_well_formed(&self, limits: &GcOpsLimits) -> bool438 fn is_well_formed(&self, limits: &GcOpsLimits) -> bool { 439 if self.rec_groups.len() 440 > usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large") 441 { 442 log::debug!("[-] Failed: rec_groups.len() > max_rec_groups"); 443 return false; 444 } 445 if self.type_defs.len() > usize::try_from(limits.max_types).expect("max_types is too large") 446 { 447 log::debug!("[-] Failed: type_defs.len() > max_types"); 448 return false; 449 } 450 let mut all = BTreeSet::new(); 451 for members in self.rec_groups.values() { 452 for tid in members { 453 if !self.type_defs.contains_key(tid) { 454 log::debug!("[-] Failed: type_defs.contains_key(tid) is false"); 455 return false; 456 } 457 if !all.insert(*tid) { 458 log::debug!("[-] Failed: all.insert(tid) is false"); 459 return false; 460 } 461 } 462 } 463 if !self.type_defs.keys().all(|tid| all.contains(tid)) { 464 log::debug!("[-] Failed: type_defs.keys().all(|tid| all.contains(tid)) is false"); 465 return false; 466 } 467 // Every supertype must exist and must not be final. 468 for (&tid, def) in &self.type_defs { 469 if let Some(st) = def.supertype { 470 match self.type_defs.get(&st) { 471 None => { 472 log::debug!("[-] Failed: supertype {st:?} missing for subtype {tid:?}"); 473 return false; 474 } 475 Some(super_def) if super_def.is_final => { 476 log::debug!("[-] Failed: subtype {tid:?} has final supertype {st:?}"); 477 return false; 478 } 479 _ => {} 480 } 481 } 482 } 483 true 484 } 485 } 486 487 /// Tracks the required operand type on the abstract value stack. 488 #[derive(Copy, Clone, Debug)] 489 pub enum StackType { 490 /// `externref`. 491 ExternRef, 492 /// `(ref $*)` — optionally with a concrete type index. 493 Struct(Option<u32>), 494 } 495 496 impl StackType { 497 /// Ensure the top of `stack` satisfies `req`, emitting fixup ops as needed. fixup( req: Option<StackType>, stack: &mut Vec<StackType>, out: &mut Vec<GcOp>, num_types: u32, )498 pub fn fixup( 499 req: Option<StackType>, 500 stack: &mut Vec<StackType>, 501 out: &mut Vec<GcOp>, 502 num_types: u32, 503 ) { 504 let mut result_types = Vec::new(); 505 match req { 506 None => { 507 if stack.is_empty() { 508 Self::emit(GcOp::NullExtern, stack, out, num_types, &mut result_types); 509 } 510 stack.pop(); 511 } 512 Some(Self::ExternRef) => match stack.last() { 513 Some(Self::ExternRef) => { 514 stack.pop(); 515 } 516 _ => { 517 Self::emit(GcOp::NullExtern, stack, out, num_types, &mut result_types); 518 stack.pop(); 519 } 520 }, 521 Some(Self::Struct(wanted)) => { 522 let ok = match (wanted, stack.last()) { 523 (Some(wanted), Some(Self::Struct(Some(s)))) => *s == wanted, 524 (None, Some(Self::Struct(_))) => true, 525 _ => false, 526 }; 527 528 if ok { 529 stack.pop(); 530 } else { 531 match wanted { 532 // When num_types == 0, GcOp::fixup() should have dropped the ops 533 // that require a concrete type. 534 // But it keeps the ops that work with abstract types. 535 // Since our mutator can legally remove all the types, 536 // StackType::fixup() should insert GcOp::NullStruct() 537 // to satisfy the undropped ops that work with abstract types. 538 None => { 539 Self::emit(GcOp::NullStruct, stack, out, num_types, &mut result_types); 540 stack.pop(); 541 } 542 Some(t) => { 543 debug_assert_ne!( 544 num_types, 0, 545 "typed struct requirement with num_types == 0; op should have been removed" 546 ); 547 let t = Self::clamp(t, num_types); 548 Self::emit( 549 GcOp::StructNew { type_index: t }, 550 stack, 551 out, 552 num_types, 553 &mut result_types, 554 ); 555 stack.pop(); 556 } 557 } 558 } 559 } 560 } 561 } 562 563 /// Emit an opcode and update the stack. emit( op: GcOp, stack: &mut Vec<Self>, out: &mut Vec<GcOp>, num_types: u32, result_types: &mut Vec<Self>, )564 pub(crate) fn emit( 565 op: GcOp, 566 stack: &mut Vec<Self>, 567 out: &mut Vec<GcOp>, 568 num_types: u32, 569 result_types: &mut Vec<Self>, 570 ) { 571 out.push(op); 572 result_types.clear(); 573 op.result_types(result_types); 574 for ty in result_types { 575 let clamped_ty = match ty { 576 Self::Struct(Some(t)) => Self::Struct(Some(Self::clamp(*t, num_types))), 577 other => *other, 578 }; 579 stack.push(clamped_ty); 580 } 581 } 582 583 /// Clamp a type index to the number of types. clamp(t: u32, n: u32) -> u32584 fn clamp(t: u32, n: u32) -> u32 { 585 if n == 0 { 0 } else { t % n } 586 } 587 } 588