@@ -405,6 +405,17 @@ pub enum ComponentValType {
405405 List ( Box < ComponentValType > ) ,
406406 FixedSizeList ( Box < ComponentValType > , u32 ) ,
407407 Record ( Vec < ( String , ComponentValType ) > ) ,
408+ /// Component-Model `flags<N>`. The vector stores the flag names (for
409+ /// diagnostics); only the count `N = names.len()` participates in the
410+ /// canonical-ABI layout calculations (`flat_count = ceil(N/32)`,
411+ /// `size = ceil(N/8)` padded, `align` from storage class).
412+ ///
413+ /// Prior to LS-A-20, flags<N> was modelled as `Record<N × Bool>`,
414+ /// which made the canonical-ABI layout calculations return N for
415+ /// flat count, N bytes for size, and 1 for align — a silent
416+ /// divergence from the spec that crossed the `total_flat_params > 16`
417+ /// params-ptr threshold for any flags<17+> argument.
418+ Flags ( Vec < String > ) ,
408419 Variant ( Vec < ( String , Option < ComponentValType > ) > ) ,
409420 Tuple ( Vec < ComponentValType > ) ,
410421 Option ( Box < ComponentValType > ) ,
@@ -1455,6 +1466,11 @@ impl ParsedComponent {
14551466 ComponentValType :: FixedSizeList ( elem, len) => {
14561467 self . flat_byte_size ( elem) . saturating_mul ( * len)
14571468 }
1469+ ComponentValType :: Flags ( names) => {
1470+ // flats[ceil(N/32)] i32 storage words = 4 bytes each.
1471+ let n = names. len ( ) as u32 ;
1472+ 4u32 . saturating_mul ( n. div_ceil ( 32 ) )
1473+ }
14581474 ComponentValType :: Own ( _) | ComponentValType :: Borrow ( _) => 4 ,
14591475 }
14601476 }
@@ -1792,6 +1808,22 @@ impl ParsedComponent {
17921808 is_pointer_pair : false ,
17931809 } ) ;
17941810 }
1811+ ComponentValType :: Flags ( names) => {
1812+ // flags<N>: ceil(N/8) bytes padded to its storage class.
1813+ let n = names. len ( ) as u32 ;
1814+ let size = if n <= 8 {
1815+ 1
1816+ } else if n <= 16 {
1817+ 2
1818+ } else {
1819+ 4u32 . saturating_mul ( n. div_ceil ( 32 ) )
1820+ } ;
1821+ out. push ( ReturnAreaSlot {
1822+ byte_offset : base,
1823+ byte_size : size,
1824+ is_pointer_pair : false ,
1825+ } ) ;
1826+ }
17951827 }
17961828 }
17971829
@@ -2569,6 +2601,11 @@ impl ParsedComponent {
25692601 ComponentValType :: FixedSizeList ( elem, len) => {
25702602 self . flat_count ( elem) . saturating_mul ( * len)
25712603 }
2604+ ComponentValType :: Flags ( names) => {
2605+ // flags<N> flattens to ceil(N/32) i32 storage words per
2606+ // the Component Model canonical ABI.
2607+ ( names. len ( ) as u32 ) . div_ceil ( 32 )
2608+ }
25722609 ComponentValType :: Own ( _) | ComponentValType :: Borrow ( _) => 1 ,
25732610 }
25742611 }
@@ -2630,6 +2667,21 @@ impl ParsedComponent {
26302667 }
26312668 4
26322669 }
2670+ ComponentValType :: Flags ( names) => {
2671+ // flags<N>: align to the smallest power-of-two storage
2672+ // class that holds N bits. Per the Component Model
2673+ // canonical ABI: N≤8 → 1, N≤16 → 2, else 4 (32-bit
2674+ // storage classes; flags<33+> uses an array of i32
2675+ // which still aligns to 4).
2676+ let n = names. len ( ) as u32 ;
2677+ if n <= 8 {
2678+ 1
2679+ } else if n <= 16 {
2680+ 2
2681+ } else {
2682+ 4
2683+ }
2684+ }
26332685 ComponentValType :: Own ( _) | ComponentValType :: Borrow ( _) => 4 ,
26342686 }
26352687 }
@@ -2717,6 +2769,25 @@ impl ParsedComponent {
27172769 . unwrap_or ( 0 ) ;
27182770 align_up ( ds, max_case_align) . saturating_add ( ok_s. max ( err_s) )
27192771 }
2772+ ComponentValType :: Flags ( names) => {
2773+ // flags<N>: ceil(N/8) bytes, packed in the smallest
2774+ // storage class. Per Component Model canonical ABI:
2775+ // N≤8: 1 byte
2776+ // N≤16: 2 bytes (1 u16)
2777+ // N≤32: 4 bytes (1 u32)
2778+ // N≤64: 8 bytes (2 u32s)
2779+ // etc.
2780+ // Storage scales as ceil(N/32) i32 words past N=32.
2781+ let n = names. len ( ) as u32 ;
2782+ if n <= 8 {
2783+ 1
2784+ } else if n <= 16 {
2785+ 2
2786+ } else {
2787+ // 4 bytes per i32 word; ceil(N/32) words.
2788+ 4u32 . saturating_mul ( n. div_ceil ( 32 ) )
2789+ }
2790+ }
27202791 ComponentValType :: Type ( idx) => {
27212792 if let Some ( ct) = self . get_type_definition ( * idx)
27222793 && let ComponentTypeKind :: Defined ( inner) = & ct. kind
@@ -3000,18 +3071,13 @@ fn convert_wp_defined_type(dt: &wasmparser::ComponentDefinedType) -> ComponentTy
30003071 ) )
30013072 }
30023073 wasmparser:: ComponentDefinedType :: Flags ( names) => {
3003- // Flags are represented as a record of bools in the canonical ABI.
3004- // For flat counting and pointer analysis, we use a record representation.
3005- ComponentTypeKind :: Defined ( ComponentValType :: Record (
3006- names
3007- . iter ( )
3008- . map ( |name| {
3009- (
3010- name. to_string ( ) ,
3011- ComponentValType :: Primitive ( PrimitiveValType :: Bool ) ,
3012- )
3013- } )
3014- . collect ( ) ,
3074+ // flags<N> has its own canonical-ABI lowering — packed into
3075+ // `ceil(N/32)` i32 storage words, NOT a record of N bools.
3076+ // Use the dedicated `ComponentValType::Flags` variant so the
3077+ // canonical-ABI helpers compute the correct flat count,
3078+ // size, and alignment (LS-A-20).
3079+ ComponentTypeKind :: Defined ( ComponentValType :: Flags (
3080+ names. iter ( ) . map ( |n| n. to_string ( ) ) . collect ( ) ,
30153081 ) )
30163082 }
30173083 wasmparser:: ComponentDefinedType :: FixedLengthList ( ty, len) => ComponentTypeKind :: Defined (
@@ -4258,4 +4324,107 @@ mod tests {
42584324 assert_eq ! ( super :: align_up( u32 :: MAX , 8 ) , !7u32 ) ;
42594325 assert_eq ! ( super :: align_up( u32 :: MAX - 3 , 8 ) , !7u32 ) ;
42604326 }
4327+
4328+ // ---------------------------------------------------------------
4329+ // LS-A-20 — flags<N> canonical ABI silently modeled as Record<Bool>
4330+ //
4331+ // Prior to the fix, convert_wp_defined_type mapped Flags(names) to
4332+ // ComponentValType::Record(names × Bool), so flat_count = N,
4333+ // canonical_abi_size_unpadded = N, canonical_abi_align = 1 — wrong
4334+ // for any N where ceil(N/32) ≠ N or N ≥ 9 (storage alignment).
4335+ // ---------------------------------------------------------------
4336+
4337+ fn empty_parsed_for_flags ( ) -> ParsedComponent {
4338+ ParsedComponent {
4339+ name : None ,
4340+ core_modules : vec ! [ ] ,
4341+ imports : vec ! [ ] ,
4342+ exports : vec ! [ ] ,
4343+ types : vec ! [ ] ,
4344+ instances : vec ! [ ] ,
4345+ canonical_functions : vec ! [ ] ,
4346+ sub_components : vec ! [ ] ,
4347+ component_aliases : vec ! [ ] ,
4348+ component_instances : vec ! [ ] ,
4349+ core_entity_order : vec ! [ ] ,
4350+ component_func_defs : vec ! [ ] ,
4351+ component_instance_defs : vec ! [ ] ,
4352+ component_type_defs : vec ! [ ] ,
4353+ original_size : 0 ,
4354+ original_hash : String :: new ( ) ,
4355+ depth_0_sections : vec ! [ ] ,
4356+ p3_async_features : vec ! [ ] ,
4357+ }
4358+ }
4359+
4360+ fn flags_ty ( n : usize ) -> ComponentValType {
4361+ ComponentValType :: Flags ( ( 0 ..n) . map ( |i| format ! ( "f{i}" ) ) . collect ( ) )
4362+ }
4363+
4364+ #[ test]
4365+ fn ls_a_20_flags_canonical_abi_matches_spec ( ) {
4366+ let pc = empty_parsed_for_flags ( ) ;
4367+ // flags<1>: flat=1 size=1 align=1
4368+ assert_eq ! ( pc. flat_count( & flags_ty( 1 ) ) , 1 ) ;
4369+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 1 ) ) , 1 ) ;
4370+ assert_eq ! ( pc. canonical_abi_align( & flags_ty( 1 ) ) , 1 ) ;
4371+ // flags<8>: flat=1 size=1 align=1
4372+ assert_eq ! ( pc. flat_count( & flags_ty( 8 ) ) , 1 ) ;
4373+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 8 ) ) , 1 ) ;
4374+ assert_eq ! ( pc. canonical_abi_align( & flags_ty( 8 ) ) , 1 ) ;
4375+ // flags<9>: flat=1 size=2 align=2 (crosses byte boundary)
4376+ assert_eq ! ( pc. flat_count( & flags_ty( 9 ) ) , 1 ) ;
4377+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 9 ) ) , 2 ) ;
4378+ assert_eq ! ( pc. canonical_abi_align( & flags_ty( 9 ) ) , 2 ) ;
4379+ // flags<17>: flat=1 size=4 align=4 (THE bug-trigger case;
4380+ // pre-fix returned flat=17 which crosses the params-ptr
4381+ // threshold at >16 in resolver.rs)
4382+ assert_eq ! ( pc. flat_count( & flags_ty( 17 ) ) , 1 ) ;
4383+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 17 ) ) , 4 ) ;
4384+ assert_eq ! ( pc. canonical_abi_align( & flags_ty( 17 ) ) , 4 ) ;
4385+ // flags<32>: flat=1 size=4 align=4
4386+ assert_eq ! ( pc. flat_count( & flags_ty( 32 ) ) , 1 ) ;
4387+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 32 ) ) , 4 ) ;
4388+ // flags<33>: flat=2 size=8 align=4 (spills to 2 i32 words)
4389+ assert_eq ! ( pc. flat_count( & flags_ty( 33 ) ) , 2 ) ;
4390+ assert_eq ! ( pc. canonical_abi_size_unpadded( & flags_ty( 33 ) ) , 8 ) ;
4391+ assert_eq ! ( pc. canonical_abi_align( & flags_ty( 33 ) ) , 4 ) ;
4392+ }
4393+
4394+ #[ test]
4395+ fn ls_a_20_flags_parser_produces_flags_variant ( ) {
4396+ // The wasmparser-bridge convert_wp_defined_type must now produce
4397+ // ComponentValType::Flags (not Record<Bool>) for `(flags ...)`
4398+ // declarations. We exercise via the wat round-trip pattern used
4399+ // elsewhere in this file.
4400+ let wat = r#"
4401+ (component
4402+ (type $f (flags "a" "b" "c" "d" "e" "f" "g" "h" "i"
4403+ "j" "k" "l" "m" "n" "o" "p" "q"))
4404+ (core module $m (func (export "run") (param i32)))
4405+ (core instance (instantiate $m))
4406+ )
4407+ "# ;
4408+ let bytes = match wat:: parse_str ( wat) {
4409+ Ok ( b) => b,
4410+ Err ( e) => {
4411+ eprintln ! ( "skipping: wat crate cannot parse flags syntax: {e}" ) ;
4412+ return ;
4413+ }
4414+ } ;
4415+ let comp = ComponentParser :: new ( )
4416+ . parse ( & bytes)
4417+ . expect ( "parse should succeed" ) ;
4418+ let flags_ty = comp. types . iter ( ) . find_map ( |t| match & t. kind {
4419+ ComponentTypeKind :: Defined ( v @ ComponentValType :: Flags ( _) ) => Some ( v. clone ( ) ) ,
4420+ _ => None ,
4421+ } ) ;
4422+ assert ! (
4423+ flags_ty. is_some( ) ,
4424+ "(flags ...) must parse to ComponentValType::Flags, not \
4425+ Record<Bool>"
4426+ ) ;
4427+ // And verify flat_count is 1, not 17.
4428+ assert_eq ! ( comp. flat_count( & flags_ty. unwrap( ) ) , 1 ) ;
4429+ }
42614430}
0 commit comments