@@ -557,6 +557,40 @@ fn struct_descriptor(type_id: u32) -> u64 {
557557 . unwrap_or ( 0 )
558558}
559559
560+ /// Per-variant GC pointer descriptors for enum types, keyed by the enum's type
561+ /// id and then by the variant discriminant. Unlike a struct, an enum's pointer
562+ /// layout depends on the active variant: variant A may hold a `String` (a GC
563+ /// pointer) in a slot where variant B holds a `Float` (a scalar). A single
564+ /// per-type mask (the union) would trace the scalar's bits as a pointer, so the
565+ /// collector selects the mask by the value's discriminant instead. An enum's id
566+ /// appears only here, never in [`struct_descriptors`], so a present entry marks
567+ /// the value as an enum.
568+ fn enum_descriptors ( ) -> & ' static std:: sync:: RwLock < HashMap < u32 , HashMap < u32 , u64 > > > {
569+ static ENUM_DESCRIPTORS : std:: sync:: OnceLock <
570+ std:: sync:: RwLock < HashMap < u32 , HashMap < u32 , u64 > > > ,
571+ > = std:: sync:: OnceLock :: new ( ) ;
572+ ENUM_DESCRIPTORS . get_or_init ( || std:: sync:: RwLock :: new ( HashMap :: new ( ) ) )
573+ }
574+
575+ /// Register the GC pointer mask of one enum variant. `type_id` is the enum
576+ /// type's id (shared by every variant); `variant` is the discriminant; `mask`
577+ /// has bit `slot` set when that slot of this variant holds a GC pointer (slot 0
578+ /// is the discriminant and is never a pointer). The back end emits one call per
579+ /// constructed variant in the program entry point, before any value is built.
580+ ///
581+ /// # Safety
582+ ///
583+ /// Called only from the generated entry shim with back-end-computed arguments.
584+ #[ no_mangle]
585+ pub extern "C" fn raven_enum_register ( type_id : u32 , variant : u32 , mask : u64 ) {
586+ enum_descriptors ( )
587+ . write ( )
588+ . unwrap ( )
589+ . entry ( type_id)
590+ . or_default ( )
591+ . insert ( variant, mask) ;
592+ }
593+
560594/// Number of root slots currently registered. Test and diagnostic aid.
561595#[ cfg( test) ]
562596pub ( crate ) fn root_count ( ) -> usize {
@@ -861,9 +895,24 @@ unsafe fn trace_object(object: *mut ObjectHeader, work: &mut Vec<*mut ObjectHead
861895 // SAFETY: tag confirms the struct layout. `len` is the field
862896 // count and `cap` is the per-type descriptor id.
863897 let ( field_count, type_id) = unsafe { ( ( * object) . len , ( * object) . cap ) } ;
864- let mask = struct_descriptor ( type_id) ;
898+ let fields = ( object as * const u8 ) . wrapping_add ( STRUCT_FIELDS_OFFSET ) ;
899+ // An enum's pointer layout depends on its active variant, so select
900+ // the mask by the discriminant in slot 0; a plain struct uses its
901+ // single per-type mask. Without this, a scalar payload (a `Float` in
902+ // one variant) sharing a slot with a pointer payload (a `String` in
903+ // another) would be traced as a pointer and corrupt the collector.
904+ let mask = {
905+ let enums = enum_descriptors ( ) . read ( ) . unwrap ( ) ;
906+ if let Some ( variants) = enums. get ( & type_id) {
907+ // SAFETY: an enum always has slot 0 (the discriminant).
908+ let disc = unsafe { * ( fields as * const usize ) } as u32 ;
909+ variants. get ( & disc) . copied ( ) . unwrap_or ( 0 )
910+ } else {
911+ drop ( enums) ;
912+ struct_descriptor ( type_id)
913+ }
914+ } ;
865915 if mask != 0 {
866- let fields = ( object as * const u8 ) . wrapping_add ( STRUCT_FIELDS_OFFSET ) ;
867916 // The descriptor mask is 64 bits, so only the first 64 fields
868917 // can be tracked. Cap the loop so a struct with more than 64
869918 // fields does not shift `1u64 << i` past the word width (a panic
@@ -1610,6 +1659,68 @@ mod stress_tests {
16101659 } ) ;
16111660 }
16121661
1662+ #[ test]
1663+ fn enum_traces_by_active_variant_not_union ( ) {
1664+ isolated ( || {
1665+ // Enum type 41: variant 0 (Text) holds a String pointer at slot 1;
1666+ // variant 1 (Num) holds a scalar Float at slot 1. The union of the
1667+ // two masks marks slot 1 as a pointer, so tracing a `Num` value by
1668+ // the union would follow its raw Float bits. Per-variant tracing,
1669+ // selected by the discriminant in slot 0, must not.
1670+ raven_enum_register ( 41 , 0 , 0b10 ) ; // Text: slot 1 is a GC pointer
1671+ raven_enum_register ( 41 , 1 , 0b00 ) ; // Num: slot 1 is a scalar
1672+
1673+ // A `Num` value: discriminant 1, slot 1 = 1.0's raw f64 bits (which,
1674+ // as an address, would fault if dereferenced).
1675+ let num = raven_struct_new ( 2 , 41 ) ;
1676+ // SAFETY: two slots; write discriminant then payload.
1677+ unsafe {
1678+ let f = raven_struct_fields ( num) as * mut u64 ;
1679+ f. write ( 1 ) ;
1680+ f. add ( 1 ) . write ( 0x3ff0000000000000 ) ;
1681+ }
1682+
1683+ // A `Text` value: discriminant 0, slot 1 = a real String pointer.
1684+ let s = marked_string ( 6 , 0x30 ) ;
1685+ let text = raven_struct_new ( 2 , 41 ) ;
1686+ // SAFETY: two slots; write discriminant then the String pointer.
1687+ unsafe {
1688+ let f = raven_struct_fields ( text) as * mut u64 ;
1689+ f. write ( 0 ) ;
1690+ f. add ( 1 ) . write ( s as u64 ) ;
1691+ }
1692+
1693+ let mut slot_a: * mut u8 = num as * mut u8 ;
1694+ let mut slot_b: * mut u8 = text as * mut u8 ;
1695+ let mut roots: [ * mut * mut u8 ; 2 ] =
1696+ [ & mut slot_a as * mut * mut u8 , & mut slot_b as * mut * mut u8 ] ;
1697+ raven_gc_enter_frame ( roots. as_mut_ptr ( ) as * mut * mut u8 , 2 ) ;
1698+
1699+ // num + text + the String. The Num's float payload is not an object.
1700+ assert_eq ! ( raven_gc_live_objects( ) , 3 ) ;
1701+ // Every churn collection traces both enums. Before the fix the Num's
1702+ // float bits were followed as a pointer and the collector crashed.
1703+ churn ( 20_000 , 256 ) ;
1704+ assert_eq ! ( raven_gc_live_objects( ) , 3 ) ;
1705+ // SAFETY: the Num's scalar slot is untouched, the Text's String is
1706+ // intact and still marked.
1707+ unsafe {
1708+ let num_payload = ( raven_struct_fields ( num) as * const u64 ) . add ( 1 ) . read ( ) ;
1709+ assert_eq ! (
1710+ num_payload, 0x3ff0000000000000 ,
1711+ "Num float payload corrupted"
1712+ ) ;
1713+ let s_again = ( raven_struct_fields ( text) as * const u64 ) . add ( 1 ) . read ( )
1714+ as * mut crate :: object:: String ;
1715+ assert_eq ! ( s_again, s) ;
1716+ }
1717+ assert_marked ( s, 6 , 0x30 ) ;
1718+ raven_gc_leave_frame ( ) ;
1719+ raven_gc_collect ( ) ;
1720+ assert_eq ! ( raven_gc_live_objects( ) , 0 ) ;
1721+ } ) ;
1722+ }
1723+
16131724 #[ test]
16141725 fn list_of_structs_survives_churn ( ) {
16151726 isolated ( || {
0 commit comments